Problem with EnityAutotize attributes

I am making a call to my xdata server and the following is being returned:

XData server request error. Uri: http://127.0.0.1:2015/tenovus/Region Status code: 403 Forbidden | fMessage::XData server request error. Uri: http://127.0.0.1:2015/tenovus/Region Status code: 403 Forbidden FJSError::Error: XData server request error. Uri: http://127.0.0.1:2015/tenovus/Region Status code: 403 Forbidden fHelpContext::0 FErrorResult::[object Object]
at http://localhost:8000/GAShopApp/GAShopApp.js [69786:17]

In the server I have all the DefaultEntitySetPermissions on and then on my Region Entity I have

  [EntityAuthorizeScopes(SCOPE_ADMIN, EntitySetPermissionsAll)]
  [EntityAuthorizeScopes(SCOPE_ADMINUSER, EntitySetPermissionsRead)]

The JWT has

JWT.Claims.SetClaimOfType<string>('scope', SCOPE_ADMINUSER)

I have created the Even XDataServerEntityList to check that the scope is being received, but it doesn't even seem to be getting that far.

Any ideas? Thanks

Hi,

Have you added XData.Security.Attributes unit to the interface uses clause?

Regards,

Yes, I've done that in both the Entity definition files and the server container unit.

If I remove the attributes all the methods are fine.

just read the topic title :laughing:

This looks like the key thing. From what you said, the attributes are being enforced, it's the JWT that is not correct. Have you added the JWT middleware to your server?

Yes the middleware is set on the XDataServer.

I'm afraid you would have to provide more information, like a sample project reproducing the issue.

That's always a problem, because if I make a simple app it will probably work. Is there an early event where I can intercept the JWT being received? There must be somewhere before the rejection where it would be available? Or on the rejection maybe? Or somewhere in the source to put a break point?

You can put a generic middleware right after the JWT middleware and check if the JWT claim is there correctly set. Use the generic middleware to inspect the request at any point and see if the Authorization header is being sent and the JWT middleware is properly translating the header into the User.Claims property.

Just stepped through the JWT middleware processing and the JWT is received and the claim for scope = administrator is in there and added to User.Claims.

Delved down further into this.

If I have just one scope set and I have the user scope claim set to SCOPE_ADMIN

[EntityAuthorizeScopes(SCOPE_ADMIN, EntitySetPermissionsAll)]

then it succeeds. If I have two set

[EntityAuthorizeScopes(SCOPE_ADMIN, EntitySetPermissionsAll)]
 [EntityAuthorizeScopes(SCOPE_ADMINUSER, EntitySetPermissionsRead)]

It succeeds on the first one but then carries on and passes back a fail when it assesses the second one and it fails in TScopesRequirement.Check on AvailableScope = TargetScope.

It seems to be caused in as it doesn't exit when a success is found and continues until the failure. Although that is strange as the SCOPE_ADMINUSER has read access and this is a call for List.

function TXDataRequestHandler.HasPermission(User: IUserIdentity;
  Requirements: TList<IAuthorizationRequirement>): Boolean;
var
  Requirement: IAuthorizationRequirement;
  Context: IXDataAuthorizationContext;
  ReqResult: IRequirementResult;
begin
  if Requirements.Count = 0 then
    Exit(True);
  
  Context := TXDataAuthorizationContext.Create(Self);
  for Requirement in Requirements do
  begin
    ReqResult := Requirement.Check(Context);
    if not ((ReqResult <> nil) and ReqResult.Succeeded) then 
      Exit(False);
  end;
  Result := True;
end;

Ah, indeed, it even confused myself. Security attributes are applied as an "AND" logic. For a permission to be allowed, it has to pass all attributes where it is mentioned. So for Read permission you have to have both SCOPE_ADMIN and SCOPE_ADMINUSER, in your setup there.

so I am accessing as SCOPE_ADMIN which has full access. Surely that should be enough? I shouldn't need to give my scope claim SCOPE_ADMINUSER as well? That's all way too confusing. Surely if the claim is for SCOPE_ADMIN it only has to succeed for that?

Then you should use it like this:

[EntityAuthorizeScopes(SCOPE_ADMIN, EntitySetPermissionsAll)] 
[EntityAuthorizeScopes(SCOPE_ADMIN + ',' + SCOPE_ADMINUSER, EntitySetPermissionsRead)]

The mechanism is described here: Authentication and Authorization | TMS XData documentation.

I'll have a read.

This could get messy for granular permissions :rofl:

1 Like