Auth0 Home Blog Docs

Difference between scopes and permissions in access token

Hello,

im not sure if i misunderstood something or if i chose the wrong implementation for my use case.
I will try to explain my problem. I have configured a SPA in Auth0. Users log in and get back an access token which the SPA uses to call an API on behalf of the user. The API is also configured in Auth0 and implemented by me. I enabled RBAC in the Auth0 dashboard´s API and configured a role with two permissions which i assigned to the test-user. If the test-user logs in to the SPA with ‘aud’ set to the identifier of the API he gets back a token which looks something like this:

{
  "iss": "***",
  "sub": "test-user",
  "aud": [
    "https://identifier/of/my/api",
    "https://***/userinfo"
  ],
  "iat": ***,
  "exp": ***,
  "azp": "***",
  "scope": "openid profile email",
  "permissions": [
    "use:privateroute1",
    "use:privateroute2"
  ]
}

So far so good i guess. But now i also configured a Machine to Machine Application to access the same API. The M2M application has access to the same two permissions the test-user has access to.
If the M2M App requests a token to access the API the token looks something like this:

{
  "iss": "***",
  "sub": "***",
  "aud": "identifier/of/my/api",
  "iat": ***,
  "exp": ***,
  "azp": "***",
  "scope": "use:privateroute1 use:privateroute2",
  "gty": "client-credentials"
}

I dont understand why the permissions “use:privateroute1 use:privateroute2” are displayed differently in the two access tokens. I find this very inconvenient because it is a hassle to validate the different tokens in the backend API. And i dont think this way is the “best practice” way.

My future plan is to add many APIs behind a single logical API and some Machine to Machine Applications all connected to each other with an even larger size of Scopes/Permissions (still confused which term to use :confused: ) based on Groups/Roles/Users. What would be the best way to implement such a use case to have a global access token validation system every backend API can implement easily?

Thank you very much in advance
Robin

Hey there @robinco, welcome to the Auth0 Community!

I apologize for the delay in response. I would like to take a deeper look at what may be going on here, when you get a chance can you direct message me your tenant name? Thanks in advance!

After looking at this with our team, we could benefit from a HAR capture of the User login flow along with a snippet of the Machine to machine token request. If you could please capture these details and send it over in a direct message I would appreciate it. Please be sure to select “Preserve log” to catch redirects and scrub the file of user passwords before passing, thanks!

Also, we noticed you are using old Authorization extension, we would recommend updating it and republishing the rule.

I might have missed some DMs in between, but even with the RBAC Core (not Authorization extension - doesn’t actually make a difference): I was able to reproduce the behaviour described by the OP.

In M2M settings, the scopes set for a client for accessing an API are just that: scopes, they’re not reflected as permissions in the permissions claim. Not sure why it’s not reflected when the “Enable RBAC” flag is turned on. Something for us to check internally with the product team.

The workaround that I see at the moment is to put the permissions in a custom claim, this way it’s the same for M2M access tokens as well as with other grant types:

You would create both a Hook (for Client Credentials Exchange) as well as a Rule.

The hook looking like this:

access_token['https://namespace/permissions'] = scope;

and the Rule accordingly:

The permissions in the rule would need to be read via node-sdk in the Rule, using the ‘getUserPermissions’ method.

function (user, context, callback) {
  var ManagementClient = require('auth0@2.17.0').ManagementClient;
  var management = new ManagementClient({
    token: auth0.accessToken,
    domain: auth0.domain
  });

  management.getUserPermissions({ id: user.user_id }, function (err, permissions) {
    if (err) {} // Handle error.
    var permissionNames = [];
    permissions.forEach(function(obj) { permissionNames.push(obj.permission_name); });    
    context.accessToken['https://namespace/permissions'] = permissionNames;
    callback(null, user, context);
  });
}

In both ways of requesting access tokens (M2M and issued on behalf of user) you will then always have this one custom claim that you can use to validate against in the backend/API. I know it feels really just like a workaround for the moment, but this way you would have a consistent claim to check.

{
  "https://namespace/permissions": [
    "use:privateroute1",
    "use:privateroute2"
  ],
  "sub": "yDqmPNzTrvsqDFpLE6XwrPSzkQqSzBtI@clients",
  [...]
  "scope": "use:privateroute1 use:privateroute2",
  "gty": "client-credentials"
}
2 Likes

Thanks for providing a workaround for the problem!
I will look into this and discuss possibilities with the team.

Not sure why it’s not reflected when the “Enable RBAC” flag is turned on. Something for us to check internally with the product team.

I would be interested in getting an update about this topic, if possible.