Application roles in token both for user flows and machine flow

Hello

This is wee bit complex.We have an existing environment of hundreds of apps and services that use different IdPs. Now we want to migrate to Auth0 as we expect to have too many users for our old solution. However we would like to preserve how we work with application roles - the roles are contained in the access token for machine-to-machine flow and in the identity token for user flows. The claim we use is simply “{someprefix}roles” so the token for a user or an application looks like this:

{
  "myroles": [
    "operator",
    "projects:read",
    "projects:write"
  ],
  …
  other claims
}

The only way I found so far is having logical APIs for every application where the permissions will be defined as a representation of the application roles. For users there will be also roles that will be mapped to permissions 1:1. And there are two Actions, one for user flows and one for machine flows, that inject these permissions/roles to the custom roles claim.

Example for user flow (JSON for better readability)

Application

{
	"AppName": "TestAppUserFlow",
	"ClientId": "AAAAA"
}

Logical API

{
	"ApiName": "TestAppUserFlow",
	"ApiIdentifier": "testAppUserFlow",
	"Permissions": [ "operator", "projects:read", "projects:write"]
}

Roles

{
    {
        "RoleName": "AAAAA::operator",
        "Permissions":
        [
            "operator"
        ]
    },
    {
        "RoleName": "AAAAA::projects:read",
        "Permissions":
        [
            "projects:read"
        ]
    },
    {
        "RoleName": "AAAAA::projects:write",
        "Permissions":
        [
            "projects:write"
        ]
    }
}

User

{
	"RolesAssigned":
    [
        "AAAAA::operator",
		"AAAAA::projects:read"
    ]		
}

Action

{
    exports.onExecutePostLogin = async (event, api) => {
        var clientId = event.client.client_id;
        
        if (event.authorization) 
        {
          var clientWiseRoles = new Array();
          event.authorization.roles.forEach(role => 
          {
            if (role.startsWith(clientId + "::"))
            {
              clientWiseRoles.push(role.split("::")[1])
            }
          });
          
          api.idToken.setCustomClaim("approles", clientWiseRoles);
          api.accessToken.setCustomClaim("approles", clientWiseRoles);
          
        }
      };
}

Code request

{
    "client_id":"AAAAA",
    "response_type":"code",
    "redirect_uri":"https://localhost/",
    "response_mode":"query",
    "scope":"openid offline_access profile",
    "state":"12345",
    "audience":"testAppUserFlow"
}

Token request

{
    "client_id":"AAAAA",
    "scope":"openid offline_access profile",
    "code":"EWKJSJi56MHCZQurFly_2ydEGVilx-9H3gKEJHJaJIdei",
    "redirect_uri":"https://localhost/",
    "grant_type":"authorization_code",
    "client_secret":"****************************************",
    "audience":"testAppUserFlow"
}

Token response

{
  "approles": [
    "projects:read",
    "operator"
  ],
  "nickname": "john.doe",
  "name": "john.doe@contoso.com",
  "updated_at": "2024-01-29T08:08:19.212Z",
  "iss": "https://somedomain.eu.auth0.com/",
  "aud": "6wwXydkYjkshb3u7*****",
  "iat": 1706520431,
  "exp": 1706556431,
  "sub": "auth0|6499a4d532*******",
  "sid": "NOy_qXCPJ_lG4sAc*********"
}

Example for machine flow (json for better readability)

Application

{
	"AppName": "TestAppMachineFlow",
	"ClientId": "BBBBB"
}

Logical API

{
	"ApiName": "TestAppMachineFlow",
	"ApiIdentifier": "testAppMachineFlow",
	"Permissions": [ "operator", "projects:read", "projects:write" ]
}

Client application

{
	"PermissionsAssigned": [ "projects:write"]
}

Action

{
    exports.onExecuteCredentialsExchange = async (event, api) => {
        var clientId = event.client.client_id;
        
        var clientWiseRoles = new Array();
      
        
        event.accessToken.scope.forEach(role => 
        {
          if (role.startsWith(clientId + "::"))
          {
            clientWiseRoles.push(role.split("::")[1])
          }
        });
          
        api.accessToken.setCustomClaim("approles", clientWiseRoles);
      };
}

Token request

{
    "client_id":"BBBBB",
    "scope":"",
    "grant_type":"client_credentials",
    "client_secret":"*************************************",
    "audience":"testAppMachineFlow"
}

Token

{
  "approles": [
    "owner"
  ],
  "iss": "https://somedomain.eu.auth0.com/",
  "sub": "s475wirEc9LclpiW*******************",
  "aud": "testAppMachineFlow",
  "iat": 1706520822,
  "exp": 1706607222,
  "azp": "s475wirEc9Lc********",
  "scope": "s475wirEc9Lc***********::owner",
  "gty": "client-credentials"
}

Risks

  • As I use permissions of the logical API for application roles and it is only possible to use the audience for just one API, I basically cannot use application roles and scopes of real APIs simultaneously.
  • I need to use Actions. Potential performance issue with hundreds thousands users?
  • Feels like a workaround for something Auth0 might support natively somehow.

Is there a better way how to work with application roles with Auth0?

Hi @mbalicky,

Welcome to the Auth0 Community!

You have the correct approach for working with applications roles for both your Authorization Code and M2M flows.

Using Actions is the recommended and best way to append custom claims to your tokens and shouldn’t have issues with scaling. Additionally, please note that triggering an authentication flow is still subjected to the Rate Limit Policy and Rate Limit Configurations.

Finally, Auth0 does not support multiple audiences. If you need this behavior, you should instead use scopes to represent multiple APIs while using a single audience.
(Reference: Access tokens with multiple audiences - #2 by richard.dowinton)

Hope this helps!

Let me know if you have any questions.

Thanks,
Rueben

Hi Reuben,

thanks a lot, this definitely helped.

1 Like

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