Swagger and JWT and XData 5.8

I seem to be getting tripped up testing out JWT and the latest version of XData and Swagger. In previous versions, I could get a JWT from say a login endpoint. I could then click the "Authorize" button at the top of the Swagger UI page, and paste the JWT, including the "Bearer" prefix, but without quotes. Just like this:

Bearer abcxyz....

Currently, doing this seems to generate an error when accessing any endpoint afterwards:

Exception Class: EJSONConversionException 
Message: JSON Incompatible type. Expected Int64

If I put it in quotes or leave off the Bearer and the quotes, I don't get an error, but I also don't get anywhere - access is forbidden. I think something changed in Swagger, where they don't want the Bearer prefix, but then XData does, so this isn't working as expected?

I was originally having all kinds of trouble with connecting via a TMS WEB Core application, but I think that is working, so the access forbidden thing doesn't seem to be an issue outside of Swagger.

Hmm... I spoke too soon. I'm having some real trouble getting JWTs to work anywhere. Forbidden (403) or a status return code of 0. Some pre-5.8 apps that were working fine now don't in the current version. I've already looked at the two big breaking changes - the longer secret and dealing with Response.Result via await Blob.text() but not sure what to do about the JWT situation?

Are you sure that is not a problem with the longer secret? That's the only thing I can think of that could break in 5.8.

If this is from Web Core application, have you inspected the requests in the Network Tab (developer tools)? Usually there is more information there in the response body.

Yes, primarily a web core application. And I've got a really long secret, no worries there.

I just set the 'Secret' property on the XDataServer JWT Middleware though, not via any function calls. And when I create the JWT, I use this, which is what I've been doing for a while:

      JWT := TJWT.Create;
      try
        // Setup some Claims
        JWT.Claims.Issuer := 'XData SurveyServer';
        JWT.Claims.IssuedAt := IssuedAt;
        JWT.Claims.Expiration := IssuedAt + 1;
        JWT.Claims.SetClaimOfType<string>( 'acc', qry.FieldByName('account_id').asString );
        JWT.Claims.SetClaimOfType<string>( 'eml', Username );
        JWT.Claims.SetClaimOfType<string>( 'iat', FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', IssuedAt));

        // Generate the actual JWT
        Result := 'Bearer '+TJOSE.SHA256CompactToken(ServerContainer.XDataServerJWT.Secret, JWT);
      finally
        JWT.Free;
      end;

I can't get it to work via Swagger either, which I think is making it hard for me to really troubleshoot where the problem is. I can see the network activity in the console and I can see the Authentication values in the header, but as soon as it is there, things go sideways. It is as if XData doesn't know what to do when a JWT is present? It isn't validating anything I guess, so probably a good place to start. Any suggestions for where to troubleshoot?

Are you able to send some screenshots, specifically of the requests in Network Tab, with JWT, Headers, Response body...?

Another thing to check is:

  1. Forbid anonymous requests in JWT middleware
  2. Set secret using OnGetSecret event
  3. Put a generic middleware after the JWT middleware to and check if code reaches there, and if Request.User is set.

I will see what I can do. I usually use Swagger for this, but I don't think it is passing the authorization header properly, so that's a major troubleshooting tool off the table. Does it currently work for you, using Swagger and JWTs to get an authorized request? What do you put in the login field?

I've tried 1) and 2) already, so I'll give 3) a try and see what else I can post for you.

Well, Swagger aside, maybe I'm next in line to struggle with CORS. Here's an example of what is going wrong. I make a connection (the $model request I presume), and then I call the Login endpoint and get a JWT, and then I make the Survey call which passes the preflight but not the actual request?

Here's the Survey preflight

The failed request

And the console.log messages telling me what is wrong without telling me what is wrong :face_with_raised_eyebrow:

So what causes a preflight to succeed and not the actual request? Status code: 0? If I turn off authentication (remove the [Authorize] attribute in the endpoint) things work fine.

Yes, it works and I put the usual "Bearer " + .
You can try in demo JwtAuthDemo.groupprojfor example (folder \demos\jwtauthdemo). You would just need to enable Swagger and allow for anonymous access to it using the OnForbidRequest event.

The response has a content-length of 91. What is the content of response body of the failed request?

Is your CORS middleware before the JWT middleware?

Can you debug the server and check if coder reaches implementation of your Survey operation?

What is the list of middleware you have in your server?

Well. I tried the jwtauthdemo and it worked as expected.
I created a new project using the XData template and it worked as expected.

Which is odd, as the project I'm working on is exactly that. So I'll have to just recreate it step-by-step and see where it went sideways. I wasn't very far into it (demo project for the blog) so that's why I was thinking there was something amiss. The good news is that this means that I should be able to figure out what it was at some point today :+1:

Thank you for the feedback. Please let me know if I can help you further with this.

It is a curiosity. I only got as far as creating a JWT in the recreation of the previous project and I've already got the same problem. Bizarre. Will post more information when I have something constructive to share.

Aha!

Found the issue: JWT reserved claims.

In my code I was populating the JWT like this:

        JWT.Claims.SetClaimOfType<string>( 'acc', 'some account string');
        JWT.Claims.SetClaimOfType<string>( 'eml', 'some email string' );
        JWT.Claims.SetClaimOfType<string>( 'iat', FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', Now));

I was wanting to store the "issued at" time as readable part of the claims. Turns out that this is a JWT reserved claim, and it is expected to be set as a normal Unix timestamp (int64). If I change this to a different claim, like this, all my problems go away.

        JWT.Claims.SetClaimOfType<string>( 'acc', 'some account string');
        JWT.Claims.SetClaimOfType<string>( 'eml', 'some email string' );
        JWT.Claims.SetClaimOfType<string>( 'now', FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', Now));

Presumably either XData or Swagger, or perhaps both, started to access this claim in XData 5.8 where they hadn't been accessing it previously, and threw up an error when it wasn't an int64 value and called it a day. And the house of cards collapsed completely. How fun is that?!

All good now! Perhaps this is something to add to the XData documentation.

1 Like

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