Extracting the JWT from the Request

I'm working on a solution to update the JWT expiry after calls to the server. The problem I am having is getting the auth header. So in the EntityInserted event I have Args.Handler.Request.Headers.Get('authorization') but this fails to retrieve anything.

The inbuilt JWT stuff works ok.

Any ideas?

Hard to tell with so little information. It should work. In any case, if the JWT middleware works, why do you need to get the Authorization header?

1 Like

Hi,

Well, you would have to generate a new token on each request, and send it back to the client.

After the JWT Middleware has validated the request, you will be able to read all Claims from 'TXDataOperationContext.Current.Request.User.Claims'

HTH,

The aim is to update the token to extend the period of validity:

class function TJWTUtils.UpdateJWT(const AJWT: TJWT): string;
var
  JWT: TJWT;
  I: Integer;
  Pair: TJSONPair;
  Value: TJElement;
begin
  JWT := TJWT.Create;
  try
    JWT.Claims.JWTId := AJWT.Claims.JWTId;
    JWT.Claims.IssuedAt := AJWT.Claims.IssuedAt;
    JWT.Claims.Expiration := TTimeZone.local.ToUniversalTime(IncHour(Now, 2));

    for I := 0 to AJWT.Claims.JSON.Count - 1 do
    begin
      Pair := AJWT.Claims.JSON.Pairs[I];
      Value := TJElement.FromJSONValue(Pair.JsonValue);

      if Value.IsString then
        JWT.Claims.SetClaimOfType<string>(Pair.JsonString.Value, Value.AsString)
      else if Value.IsInteger then
        JWT.Claims.SetClaimOfType<Integer>(Pair.JsonString.Value, Value.AsInteger)
    end;

    JWT.Claims.Issuer := TokenIssuer;
    Result := TJOSE.SHA256CompactToken(JWTSecret, JWT);
  finally
    JWT.Free;
  end;

end;

Of course a JWT.Assign would make life easier. :grinning:

Yes, that's where I started, but then thought might be better to go straight to the source.

Imho this is the way to go. You would have to do the same thing as the JWT Middleware anyway to decode the token. So why not just read the User.Claims.

You can write a custom JWT Middleware extending the Sparkle one, "injecting" your code for creating the new token and setting the response readers with it. Or a new middware that runs after JWT.

Regards,

2 Likes

I'm using the User to create the updated JWT and pushing this into the Response object headers

Args.Handler.Response.Headers.AddValue('jwtupd', lJWT);

But it's not being found when I search for it on the webcore app in TXDataWebConnection.OnResponse.

using

Args.Response.Headers.GetIfExists('jwtupd', LToken)

Stepped through the code, but it doesn't seem to be there.

Any ideas?

Have you checked the actua response returned from the server in the "Network" tab of the browser console? Do you have a screenshot of it? Is the header there in response?

Out of curiosity, can you tell me where exactly are you calling this?

Regards,

In an XDataServer Event, well it's passed from an event:

procedure TServerContainer.XDataServerEntityInserted(Sender: TObject; Args:
    TEntityInsertedArgs);
var
  lEntityHelper: TEntityHelper;
begin
{$IFNDEF IMPORTING}
  lEntityHelper := FindEntityHelper(Args.Entity, Args);
  try
    lEntityHelper.AfterInsert;
    if lEntityHelper.UpdateJWT(eaInsert) then
         UpdateJWT(Args);
  finally
    lEntityHelper.Free;
  end;
{$ENDIF}
end;
procedure TServerContainer.UpdateJWT(Args: TXDataModuleArgs);
var
lJWT: String;
begin
  {$IFNDEF IMPORTING}
  lJWT := TJWTUtils.UpdateJWT(Args.Handler.Request.User);
  if lJWT <> '' then
     Args.Handler.Response.Headers.AddValue('jwtupd', lJWT);
  {$ENDIF}
end;
class function TJWTUtils.UpdateJWT(AUser: IUserIdentity): string;
var
  JWT: TJWT;
  lClaim: TUserClaim;
  lExpiry: TDateTime;
begin
  JWT := TJWT.Create;
  try
    for lClaim in AUser.Claims do
    begin
      if lClaim.Name = TReservedClaimNames.ISSUED_AT then
        JWT.Claims.IssuedAt :=  lClaim.AsEpoch
      else if lClaim.Name = TReservedClaimNames.JWT_ID then
         JWT.Claims.JWTId := lClaim.AsString
      else if lClaim.Name = TReservedClaimNames.EXPIRATION then
         continue
      else if lClaim.IsString then
        JWT.Claims.SetClaimOfType<string>(lClaim.Name, lClaim.AsString)
      else
        JWT.Claims.SetClaimOfType<Integer>(lClaim.Name, lClaim.AsInteger);
    end;
    JWT.Claims.Expiration := TTimeZone.local.ToUniversalTime(IncMinute(Now, ValidPeriod));
    Result := TJOSE.SHA256CompactToken(JWTSecret, JWT);
  finally
    JWT.Free;
  end;
end;

It is in the Response Headers

access-control-allow-origin: *
Content-Length: 5831
Content-Type: application/json
Date: Sat, 29 Jan 2022 11:06:36 GMT
jwtupd: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6ImFkbWluaXN0cmF0b3IiLCJmaXJzdG5hbWUiOiJSdXNzZWxsIiwidXNlcmlkIjo1MSwianRpIjoiMDIxNDUwNTg2OEE0NEFBQTkzOEY3MENEQjE1NjI0RTIiLCJzaG9wIjoiU3lzdGVtYXRpYyBNYXJrZXRpbmcgTGltaXRlZCIsInJvbGUiOiJTdXBlclVzZXIiLCJpc3MiOiJHaWZ0QWlkZXIgUGF0cm9uIFNlcnZlciIsImlhdCI6MTY0MzQ1NDM4NCwiZnVsbG5hbWUiOiJSdXNzZWxsIFdlZXRjaCIsImxvY2F0aW9udHlwZSI6IlN5c3RlbSIsImV4cCI6MTY0MzQ1NTI5Nn0.Hn7jhaKXFgjuW5nmBc4tJFnv9cejzefGsPRW6QNOmYs
Server: Microsoft-HTTPAPI/2.0
xdata-version: 2

It fails in

function THttpHeaders.Exists(const HeaderName: string): boolean;
begin
  Result := FHeaders.ContainsKey(LowerCase(HeaderName));
end;

FMap in TStringMap doesn't seem to contain the headers

Capture

Are you able to send a project reproducing the issue?

I'll try but I'm busy and it's quicker to do workarounds that creating demo apps.

1 Like