Auth0 Home Blog Docs

Authorization Extension - Groups not in JWT

Hey guys, I’m working on a project to setup a server for my university. It contains a user system (for which I’m using Auth0) where I need to be able to put users into different groups and access that information on my client. I tried solving my problem for 5 hours now, but nothing seems to work. Please keep in my mind that my background is not professional, I would be very glad if you could help me. I’m using Nodejs for my server, accessing auth0 with passport.

Problem:
The requested idToken does not contain the users groups.
I tried:
Scope: I wrote groups in the scope, nothing changed. I keep it there, because I don’t know if it matters for the token. EDIT: Deleted groups in the scope like @kim.maida told me to.
Changed the authorization rule as written in other threads about similar problems. It now looks the following:

// ….
// Update the user object.
    user.groups = data.groups;
    
    const namespace = 'https://mydomain.com/claims/';
    context.accessToken[namespace + 'groups'] = data.groups;
    context.idToken[namespace + 'groups'] = data.groups;

    // Store this in the user profile (app_metadata).
    saveToMetadata(user, data.groups, data.roles, data.permissions, function(err) {
      
      return callback(err, user, context);
    });
// …

Now, when trying the rule with my test user, I get the following output:

{
  "https://mydomain:com/claims/groups": [
    "Optimierung",
    "Admin"
  ]
}

Instead of

{}

Still, there was no change in the issued idToken.

I also tried other small things, but I can’t remember all of them. Hopefully you guys can help me, I’m growing very desperate.

Edit: I am not using OIDC Dynamic Application Registration.

Hello @mowolf! Some users have been experiencing issues with Rules not being called: Rules are not being called. Looking at your code (which looks good), it seems that this may be your issue. We’re working hard to resolve it!

Regarding scopes, please remove groups from scopes. To learn more about scopes and their usage, please check out this blog article: https://auth0.com/blog/on-the-nature-of-oauth2-scopes/

1 Like

Hey @kim.maida , thanks for your help. Sadly, I tried deleting and recreating my application but it changed nothing. I will keep trying different things because it is very important to me, but if someone has a different ideas, please tell. For further information, I did everything like this guide told me and then followed the tutorial for the authorization extension. That’s basically all I did, which is why I don’t know what I might have done wrong.

After recreating the application, something DID change! I now found the information about the user’s groups not in the JWT or profile, but in the _json and _raw portion of the output, as 'https://mydomain:com/claims/groups': [ 'abc' ]
Is there any way without brute force to get the groups as another key:value pair in the profile portion of the output?

1 Like

Hi,

I can’t get group information in the ID token. I can’t see in the group info in app_data for the user either. I’ve burnt 4 hours already.

Help.

/*

var audience = ‘’;
audience = audience || (context.request && context.request.query && context.request.query.audience);
if (audience === ‘urn:auth0-authz-api’) {
return callback(new UnauthorizedError(‘no_end_users’));
}

audience = audience || (context.request && context.request.body && context.request.body.audience);
if (audience === ‘urn:auth0-authz-api’) {
return callback(new UnauthorizedError(‘no_end_users’));
}

getPolicy(user, context, function(err, res, data) {
if (err) {
console.log(‘Error from Authorization Extension:’, err);
return callback(new UnauthorizedError('Authorization Extension: ’ + err.message));
}

if (res.statusCode !== 200) {
  console.log('Error from Authorization Extension:', res.body || res.statusCode);
  return callback(
    new UnauthorizedError('Authorization Extension: ' + ((res.body && (res.body.message || res.body) || res.statusCode)))
  );
}

// Update the user object.
user.groups = data.groups;

// Store this in the user profile (app_metadata).
saveToMetadata(user, data.groups, data.roles, data.permissions, function(err) {
  return callback(err, user, context);
});

});

// Convert groups to array
function parseGroups(data) {
if (typeof data === ‘string’) {
// split groups represented as string by spaces and/or comma
return data.replace(/,/g, ’ ').replace(/\s+/g, ’ ‘).split(’ ');
}
return data;
}

// Get the policy for the user.
function getPolicy(user, context, cb) {
request.post({
url: EXTENSION_URL + “/api/users/” + user.user_id + “/policy/” + context.clientID,
headers: {
“x-api-key”: configuration.AUTHZ_EXT_API_KEY
},
json: {
connectionName: context.connection || user.identities[0].connection,
groups: parseGroups(user.groups)
},
timeout: 5000
}, cb);
}

// Store authorization data in the user profile so we can query it later.
function saveToMetadata(user, groups, roles, permissions, cb) {
user.app_metadata = user.app_metadata || {};
user.app_metadata.authorization = {
groups: groups,
};

auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
.then(function() {
  cb();
})
.catch(function(err){
  cb(err);
});

}
}

Is there something I need to do to assign the rule to the default application or are all rules processed by all applications? If this rule is executing should the app_metadata for the user be updated when the use logs in?

Hi,
I didn’t have the " Persist groups in the user’s application metadata." check box enabled for the configuration of the extension. I’m now seeing the data update in the app_data info against the user after login.

So the rule is executing, nothing is showing in the id_token though.

This is the app_data on the “user details” tab.

{
“authorization”: {
“groups”: [
“ALC-DA4-Users”
]
}
}

I’ve put a query in with support, we are looking to use a paid subscription, that’s not going to happen if it’s broken.

POST /oauth/token HTTP/1.1

Host: arcinfra.au.auth0.com:443

User-Agent: F5 OAuth Client

Accept: /

Proxy-Connection: Keep-Alive

oauth_dns_resolver_name: /Common/forward_all-dns

oauth_serverssl_name: /Common/serverssl-ssldump

Content-Length: 307

Content-Type: application/x-www-form-urlencoded

client_id=HoFVpJ1WEPAv1v6VSdeJaXF5ArAMVcLm&client_secret=HutBVSXzi5mtnauqpZzRlSNMKHqU6W32xKnHnbyieanczzOncQsfd6mERFMJIv1K&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fdevapp04.arcinfra.com%2Foauth%2Fclient%2Fredirect&scope=openid%20offline_access%20email%20profile%20groups&code=NZZyvl7O6Jp_hEQCHTTP/1.1 200 OK

Date: Fri, 22 Mar 2019 02:13:06 GMT

Content-Type: application/json

Content-Length: 1311

Connection: keep-alive

X-Auth0-RequestId: 7a22a74553244cc7ffab

X-RateLimit-Limit: 30

X-RateLimit-Remaining: 29

X-RateLimit-Reset: 1553220787

Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache

Strict-Transport-Security: max-age=15724800

X-Robots-Tag: noindex, nofollow, nosnippet, noarchive

{"access_token":"pan2M7pIJqOPX1GyhVQTSFp4NLVFqljj","refresh_token":"9kDB3pjQuMVMOcfYugjdJlW0mMQF3f6G1qAfcbXmDxJJE","id_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlJFVkdRMFF3T1RKR01UQTRNREJDTkRsRlFqSTBOVEF4UkVaR016WTFRekF3UTBFNFJFVTVSQSJ9.eyJuaWNrbmFtZSI6ImFsY29hLnVzZXIyIiwibmFtZSI6ImFsY29hLnVzZXIyQGFsY29hLmNvbSIsInBpY3R1cmUiOiJodHRwczovL3MuZ3JhdmF0YXIuY29tL2F2YXRhci9kN2UwMzM0MWE2NWRkOGJlNDA4ZjJmNmVhODJkMjA4Mj9zPTQ4MCZyPXBnJmQ9aHR0cHMlM0ElMkYlMkZjZG4uYXV0aDAuY29tJTJGYXZhdGFycyUyRmFsLnBuZyIsInVwZGF0ZWRfYXQiOiIyMDE5LTAzLTIyVDAyOjAxOjE2LjI3OVoiLCJlbWFpbCI6ImFsY29hLnVzZXIyQGFsY29hLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL2FyY2luZnJhLmF1LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1YzkzNTY5YTY0MDg4MDExNDFmZjVlM2IiLCJhdWQiOiJIb0ZWcEoxV0VQQXYxdjZWU2RlSmFYRjVBckFNVmNMbSIsImlhdCI6MTU1MzIyMDc4NiwiZXhwIjoxNTUzMjU2Nzg2LCJub25jZSI6IjVmOU52ZVR5S205R1JVRnlQYjBKRlZBIn0.hlj7RMly2-Z0HVv3EuGUOFrYDl9JxNcDeo6WNOkzuE4In7zELzSv84xh6HPoTvQyD8RATelP7Iz4ZOd_CkLyFidnmPON1ssDIjbMZ8bPDLhYtftXivFzHY79ufa4QT8ztoZP1lx5xtjAQsFO7tPsSgrfoMkMPgAZA0I0KZS2izTUFjdttPeDAStp48M-KVnjcyzhEwJn6wY47RVXi-VIgIgsEez4c7wdDt_j3Q_EpyOJEr4GsLK1Ry0Xgq0hvyqq4LVaQlqPk73lV2puUX2hW4E8t8P3aNtfrtfuTsPUf4Fzjso9OtcSLZX1-WJDBpUhtwv9mGg1tGKwXUjLOPgtmg","scope":"openid profile email offline_access","expires_in":86400,"token_type":"Bearer"}

Hi everyone. I feel that this thread touches a few different points , so I’ll try to comment on each part separately to help the troubleshooting process.

The authorization extension rule

The authorization extension rule will query the authorization extension’s API for the user’s calculated groups, permissions and roles, and put them in the user object:

    // Update the user object.
    user.groups = mergeRecords(user.groups, data.groups);
    user.roles = mergeRecords(user.roles, data.roles);
    user.permissions = mergeRecords(user.permissions, data.permissions);

(mergeRecords will merge the retrieved info with whatever the identity provider is sending).

Now, putting information in the user object doesn’t necessarily means it will be sent to the application or stored anywhere.

Storing the information in the user’s app_metadata

Optionally, the rule can store the information in the user’s app_metadata. Storing info in the app metadata is only helpful if you plan to retrieve the user profile using the management API v2 (/api/v2/users/{user_id}). It’s not really a great idea, though, as you will be storing in the user profile some data that is really applicable to a combination of user + application (each application might get different permissions).

In the majority of cases applications get the user information using the OIDC way: either from the ID Token or by using the /userinfo endpoint.

Making authorization information available to the application

If you want to send authorization information to the application that requested the token, you will need to use custom claims. I’d recommend putting this logic in a separate rule, making sure that it’s listed after the authorization extension rule. It would do something like this:

function (user, context, callback) {

    const namespace = 'https://mydomain.com/claims/';
    // we are using the 'groups' property from the user object 
    // that was already set in the authorization extension rule
    context.idToken[namespace + 'groups'] = user.groups;

    // you can use custom claims for permissions and roles
    // if you want, and also put these claims in the accessToken.
}

You can put the information in the idToken (the token that is used by the application) or in the accessToken (the token used to access APIs), depending on your requirements.

@paul.fisher I think you might be missing this step.

Retrieving the claims from the token

If you put a rule like the above, the ID Token will look like this:

{
  "nickname": "john.doe",
  "name": "john.doe@gmail.com",
  [...],
  "https://mydomain.com/claims/groups": ["group1", "group2"]
}

The groups array might be a simple string if only one group is present, or the claim might not be present at all if there were no groups for the user.

Exactly how you parse the ID token and get the claim back will depend on the SDK used on the application.

I appreciate the assistance, below is the conversation with support, the biggest bonus would be to remove the checkbox/gui for a function that doesn’t seem to work in OIDC compliant and non OIDC compliant mode.

" I guess it was the check box that said “Add groups to users tokens”, that had me fooled into thinking it was going to add groups to users tokens.

4 hours of my life gone, seriously, I did use your rule and I have it working but this does need to be updated. I turned off the OIDC compliance and I’m pretty sure it wasn’t working in that configuration either…

Can I also suggest you add some comments to the rule you published on the git hub, the first thing I did was replace your generic namespace with arcinfra.au.auth0.com which is an illegal namespace and had me lost in the woods for another 30 minutes.

I appreciate the support, It’s not all to waist I have a much better understanding of how the framework is functioning, it’s good.

Thanks Paul

auth0.com , webtask.io and webtask.run are Auth0 domains and therefore cannot be used as a namespace identifier.

If you need to add custom claims to the Access Token, you can use the code sample above with the following change: use context.accessToken in place of context.idToken .

Please note that adding custom claims to tokens through this method will also let you obtain them when calling the /userinfo endpoint. However, rules run when the user is authenticating, not when /userinfo is called.

Any non-Auth0 HTTP or HTTPS URL can be used as a namespace identifier, and any number of namespaces can be used. The namespace URL does not have to point to an actual resource, it’s only used as an identifier and will not be called by Auth0.

https://auth0.com/docs/api-auth/tutorials/adoption/oidc-conformant#further-reading"

By default authorization extension does not add the claim in the token. You can create a rule to add the custom claim in the token.
https://gist.github.com/Tanver-Hasan/99b6fbd30e3855544c00b32a6b31b5eb

Hi again Paul. Thanks for the feedback, I’ll work on the PRs to try to make things easier for the next person who works on this.

I honestly missed the text within “token Contents” section in the authorization extension. This comes from previous versions of the authentication pipeline, where everything in the user profile ended up in the ID token by default (without the need to use custom claims). This definitely needs work: it should clarify that the toggles mean “add the information to the user object” (so that the information is available on rules), and link to https://auth0.com/docs/extensions/authorization-extension/v2/rules#add-custom-claims-to-the-issued-token for instructions on how to put that information in custom claims.

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