Using JWT Token Information in My GraphQL Queries

Hi there,

Hope you're all doing great!

As my project is nearing completion, I'm reaching out with what might just be my final query about the GraphQL interface I've been working on. :smile:

In my previous interactions within this support community, I've received great assistance from Wagner Landgraf. He helped me to successfully integrate (JWT-based) security into my GraphQL project. Quick sidenote: I'm currently using Web Broker, but I'm definitely eyeing the possibility of incorporating TMS Sparkle down the road. You can find all the details, along with the sources, right here: GraphQL endpoint handling authorization with JWT via HTTP Headers - #7 by wlandgraf

At this point, I'm wrestling with a final challenge. It's all about leveraging a specific value from the payload of the JWT and seamlessly integrating it into the GraphQL queries. My initial thought was to use a global variable to store this value, but it seems like that might not be the smartest approach. In fact, I'm a bit stuck on determining the best way to proceed. I've been poring over the source code shared in my previous forum post, and I've made a few additions to address this concern. Check out the attached files for more insight.

Here's a snippet of what I've added to FormUnit1, aimed at capturing the value from the JWT:

implementation

uses
  GraphQL.Bookshelf;

procedure TForm1.DoParseAuthentication(AContext: TIdContext; const AAuthType, AAuthData: String; var VUsername, VPassword: String;
  var VHandled: Boolean);
var
  LToken: string;
  LJWT: TJWT;
  LBuilder: IJOSEConsumerBuilder;
  LConsumer: IJOSEConsumer;
  LCustomerEmail: String;
begin
  VHandled := False;
  if SameText(AAuthType, 'Bearer') and (AAuthData <> '') then
  begin
    LToken := AAuthData;
    try
      LJWT := TJOSE.DeserializeOnly(LToken);
      try
        // Build consumer
        LBuilder := TJOSEConsumerBuilder.NewConsumer
          .SetVerificationKey(SHARED_SECRET)
          .SetExpectedAlgorithms([TJOSEAlgorithmId.HS256]);

        // Recommended to set expected audience and issuer
        LBuilder.SetSkipDefaultAudienceValidation;
//        LBuilder.SetExpectedAudience(True, 'http://my-api-url');
//        LBuilder.SetExpectedIssuers(True, 'http://url-of-jwt-issuer');

        LConsumer := LBuilder.Build;
        LConsumer.Process(LToken);

        // Getting the value of customer_email
        LCustomerEmail := GetFieldValueFromJSON(LJWT.Claims.JSON.ToString, 'customer_email');
        GraphQL.Bookshelf.CustomerEmailFromJWT := LCustomerEmail;
        OutputDebugString(PChar('customer_email: ' + LCustomerEmail)); // just for debugging; remove this line later
        // --end

        VHandled := True;
      finally
        LJWT.Free;
      end;
    except
      on E: EJOSEException do
        raise EIdHTTPUnsupportedAuthorisationScheme.Create(E.Message);
      on E: EInvalidJWTException do
        raise EIdHTTPUnsupportedAuthorisationScheme.Create(E.Message);
      else
        raise;
    end;
  end;
end;

function TForm1.GetFieldValueFromJSON(AJSONString: String; AFieldName: String): String;
var
  LJSONObject: TJSONObject;
begin
  LJSONObject := TJSONObject.ParseJSONValue(AJSONString) as TJSONObject;
  try
    Result := LJSONObject.GetValue(AFieldName).Value;
  finally
    LJSONObject.Free;
  end;
end;

Subsequently, I have added this in the unit GraphQL.Bookshelf to use the value from the JWT:

var
  CustomerEmailFromJWT: String;

function TMutation.CreateAuthor(Name: string): TAuthor;
begin
  Result := TAuthor.Create(Name + ' (' + CustomerEmailFromJWT + ')');
  try
    AuthorList.Add(Result)
  except
    Result.Free;
    raise;
  end;
end;

While this seems to be working like a charm, I do have a couple of questions bubbling up:

  1. Are there any concerns about potential hiccups when multiple users engage with the GraphQL interface simultaneously using the solution I have chosen here?
  2. Could there possibly be a different or better approach to tackle this challenge?

Massive thanks in advance for your guidance and expertise.

Warm Regards,
Stefan van Roosmalen
testproject.zip (11.0 KB)

Yes, there are. And the problem is exactly what you mentioned, the fact that when multiple simultaneous users, they might be sharing the same global variable and the code will crash or fail.

What you can do is use threadvar variables.
They are global but only in the context of the thread. For a second thread, there will be another "instance" of such variable.

Since you can't use strings in threadvars, what you can do is declare a threadvar as an object, for example TMyThreadContext or whatever name you want to give. Create an instance of such object in BeforeDispatch event, assign it to the thread var, set the properties you want (like CustomerEmail) and then destroy the object and clean the variable value in the AfterDispatch event. This should work.

Thanks for your feedback.
Actually, using a String value was just the easiest way as long as I did not use threadvar, but it was a small step to use an Integer (CustomerID) instead of a String (CustomerEmail) in the final project.
I have used a threadvar now. It works like a charm.
Thanks again.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.