Scope/Permission not included in JWT

I have been following Auth0’s Authorization in .NET Core webapi tutorial but ran into an issue on the final step with adding the read:message policy to the [Authorize] annotation. When I add the property I get a 403 Forbidden response and without it a 200 OK response. During debugging I saw there was no scope claim for read:message so the HasScopeHandler was failing on the first check. Perhaps I’ve misunderstood but I thought the claim would be a part of the access token JWT. When copying the access token into the jwt.io decoder I cannot see the “scope” claim like I thought I would. So first off. Should there be a claim/scope/permission(i’m using these synonymously here) included with the access token?

I guessed that I may need a user that had been assigned the read:message permission so I went ahead and created a new user, and assigned the permission. I got stuck here though because I don’t now how to authenticate with this user since the have a username and password not a client id and secret that the API I setup has. I tried to just replace the client_id with the user’s Auth0 id and it’s password but that always returned 401 Unauthorized { "error": "access_denied", "error_description": "Unauthorized" }

Not sure where to go from here. I’m either completely confused about how Auth0 works, or there is something else missing in the apps middleware that gets the claim for the user. There is a analogous question I asked on StackOverflow here.

1 Like

Should there be a claim/scope/permission(i’m using these synonymously here) included with the access token?

Yes, there should. Your understanding of the concept here is correct.

When you make the authorization request from within your client application towards your .NET API, did you ask for the read:message scope there? Which technology stack is your client based on? Is that also .NET? (If you could let me know, I can point you to a proper quickstart for the client part).

The scopes requested could/should look something like this:
openid profile email read:messages
for example.

I guessed that I may need a user that had been assigned the read:message permission so I went ahead and created a new user, and assigned the permission.

Short answer: this isn’t really necessary. (I think for your case, it’s not needed but something else is missing → the requested scopes in your authorization request made from the client).

Long answer: Not necessarily, or let’s say it depends whether you’re using the so-called RBAC (Role Based Access Control), either “RBAC Core” or our “Authorization Extension”. But let’s assume you’re not using either of this, to keep things simple and not confuse you here (because there’s a slight difference between scope and permissions claims then and how it’s been handled by SDKs, etc. Ignore it for the moment - let’s solve the original issue first).

1 Like

I’ve just been using Postman to test the .NET API. Here is the equivalent curl commands though.

curl --request POST \
  --url https://<tenant-id>.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{"client_id":"<client-id>","client_secret":"<sorry-its-secret","audience":"https://testapp/","grant_type":"client_credentials"}'
curl -I -X GET \
  --header 'Authorization Bearer <access_token>' . \
  http://myapi.com/health \

Ok, so just to confirm: in the Postman example you’re using the client credentials grant ("grant_type":"client_credentials"), which is used for M2M (Machine to Machine communication), so not on behalf of a user.

I’m not sure if this is what you intended to do initially (because you mentioned a real user).

In the M2M scenario (client credentials grant), you would define the scopes that should go into the access token here:

Dashboard > APIs > Machine to Machine Applications > Your Client App > Scopes

When you then run your Postman example again then, you should see the selected scopes in the access token.

However, as said, this client credentials grant is for M2M. If it should be on behalf of a user, you would use the Authorization Code Grant instead - different flow.

See

1 Like

Adding the scopes to that API fixed the problem I was having. Thanks for the explanation. I never thought to click on the arrow (screenshot attached) to expand the M2M application to see scopes. I honestly thought clicking on the app name and that took me to the same view.

Reading about it I guess I’m looking for the Authorization Code Flow since I want to implement RBAC based on a users login credentials. Right now everything is just prototyping to see if I’d like to use the full solution.

1 Like

Ok, just keep in mind that if you’re using the Authorization Code Flow, and on behalf of a user, it’s a bit different. You would ask for the specific scopes in the authorization request in the client application, as I outlined in my first comment.

I missed the part about asking for specific scopes. So I could have my single client and when a user logins to MyApp I would be able to get an access token with the specific scopes for that user and in that way the users session would be limited to only the scopes they have been given? I’d have to manage what scopes a given user should have within the app but that’s to too bad.

Yes, that’s correct (all expect for the last sentence, because there is a solution for this, so you don’t need to necessarily handle this in your app).

If you later let me know the technology stack you’re using for the client, I can point you to a proper Quickstart (Angular, React, Vue, Javascript, NodeJs, Java, Python, etc.)

I’d have to manage what scopes a given user should have within the app but that’s to too bad.

Well, this is where the RBAC concept would come in. You could define roles and assign permission to these roles, as well as assigning these roles to users then. This can all be done within Auth0 (Dashboard > Users & Roles > Roles).

.NET Core 2.1 as the API, EntityFramework Core 2 with SqlServer db and a React.js Frontend.

Ok, so for React, check out the quickstarts / tutorials on

Note, there are two on the left hand side: 01-Login and 02-Calling an API. Both will be useful.

They’re also on Github at GitHub - auth0-samples/auth0-react-samples: Auth0 Integration Samples for React Applications

You can add a rule as shown in this page to have a scope added to the access token : Auth0 Configuration (SPAs + API)

function (user, context, callback) {
  var permissions = user.permissions || [];
  var requestedScopes = context.request.body.scope || context.request.query.scope;
  var filteredScopes = requestedScopes.split(' ').filter( function(x) {
    return x.indexOf(':') < 0;
  });

  var allScopes = filteredScopes.concat(permissions);
  context.accessToken.scope = allScopes.join(' ');

  callback(null, user, context);
}
2 Likes

Thanks a lot for sharing that knowledge @sungeunbae with the rest of community!

1 Like

This rule confuses me. It seems to filter out scopes in a valid format. It is educational in terms of how to interact with scopes in your access token, if that was its purpose, but can be pretty misleading otherwise.

var permissions = [];
var requestedScopes = 'openid profile email read:messages';
var filteredScopes = requestedScopes.split(' ').filter( function(x) {
  return x.indexOf(':') < 0;
});

var allScopes = filteredScopes.concat(permissions);
var scope = allScopes.join(' ');
console.log(scope);
// -> openid profile email

The docs linked claim “The code above will ensure that all Access Tokens will only contain the properly-formatted scopes (e.g., action:area or delete:timesheets ) which are valid according to a user’s permissions.”

But actually you need to flip the condition in the filter if you want to fulfil that contract. As written it excludes properly-formatted scopes. Just a bug? Also what does according to a user’s permission mean? This code just adds those scopes to a user’s existing permissions.

So I’m not sure if I’m misunderstanding something or are the docs just plain wrong?

Or put another way, the inclusion of this rule was the precise reason why scopes were not being included in the JWT.