How to retrieve user metadata in the id_token?

Hi,

I try to authenticate a user with its username and password. I want to retrieve the JWT in response and find in it his permissions (stored in app_metadata).

But the id_token returned does not contain the user_metadata or app_metadata.

I tried with the Java driver and HTTP call.

Java :

        AuthAPI auth = new AuthAPI("my-domain.auth0.com", "my_client_id", "my_secret_id");
        AuthRequest request = auth.login(username, password)
                .setScope("openid app_metadata user_metadata");
        try {
            TokenHolder holder = request.execute();
            return holder;
        } catch (Auth0Exception e) {
            throw new AuthentException("Error authenticating " + username, e);
        }

HTTP :

        final String req = "{"
                + "\"username\":\"test@domain.com\","
                + "\"password\":\"test\","
                + "\"scope\":\"openid app_metadata user_metadata\","
                + "\"client_id\":\"my_client_id\","
                + "\"client_secret\":\"my_secret_id\","
                + "\"grant_type\":\"password\""
                + "}";
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(req, headers);

        ResponseEntity<String> response = template.exchange("https://my-domain.auth0.com/oauth/token", HttpMethod.POST, entity, String.class);

The id_token returned contains only :

{
  "email": "test@domain.com",
  "email_verified": true,
  "iss": "https://my-domain.auth0.com/",
  "sub": "auth0|xxx",
  "aud": "my_client_id",
  "exp": 1497744462,
  "iat": 1495116462
}

I tried to add a rule :

function (user, context, callback) {
  var namespace = 'https://my-domain.auth0.com/';
  if (context.idToken && user.user_metadata) {
    context.idToken[namespace + 'user_metadata'] = user.user_metadata;
  }
  if (context.idToken && user.app_metadata) {
    context.idToken[namespace + 'app_metadata'] = user.app_metadata;
  }
  callback(null, user, context);
}

And a hook :

module.exports = function(client, scope, audience, context, cb) {
  var access_token = {};
  access_token.scope = scope;
  access_token.scope.push('user_profile');
  cb(null, access_token);
};

But nothing adds the metadata to the id_token.

How could I retrieve these metadata ?

Thanks.

4 Likes

You do not need the Hook to achieve this, this can be removed/disabled.

Your Rule code looks fine - can you try adding console.log() statements in your Rule to see whether the user_metadata is being attached to the context.idToken. If possible, please also capture a HAR file for me to review: Generate and Analyze HAR Files

Please remember to remove any passwords or sensitive information from the HAR before attaching it. If you’d like it to be private, please upload it to a cloud storage service and restrict access to the link to @auth0.com email addresses using Sharelock.io.

@prashant :

I already had console.log and I see the metadata added to the idToken :

function (user, context, callback) {
  var namespace = 'https://dev-lecra-services.eu.auth0.com/';
  context.idToken = context.idToken || {};
  console.log("context.idToken before : " + JSON.stringify(context.idToken));
  context.accessToken = context.accessToken || {};
  if (user.user_metadata) {
    context.idToken[namespace + 'user_metadata'] = user.user_metadata;
  }
  if (user.app_metadata) {
    context.idToken[namespace + 'app_metadata'] = user.app_metadata;
  }
  console.log("context.idToken after : " + JSON.stringify(context.idToken));

  callback(null, user, context);
}

wt logs :

    context.idToken before : {}
    context.idToken after : {"https://my-domain.auth0.com/user_metadata":{"lastname":"ambre","firstname":"lectra"},"https://my-domain.auth0.com/app_metadata":{"company":{"id":"123456789_A","name":"lectra fashion"},"test":"toto","features":{"versioning":false}}}

And the decrypted token received :

{
  "email": "test@domain.com",
  "email_verified": true,
  "iss": "https://my-domain.auth0.com/",
  "sub": "auth0|xxx",
  "aud": "my_client_id",
  "exp": 1497805769,
  "iat": 1495177769
}

I think I can’t create a HAR file, as I don’t have a web UI, but testing my microservice with Postman : sending a body request to my POST endpoint.

I found that the /oauth/ro endpoint is working : Authentication API Explorer

final String req = "{"
                + "\"username\":\"ambre@domain.com\","
                + "\"password\":\"test\","
                + "\"scope\":\"" + settings.getScope() + "\","
                + "\"connection\":\"Username-Password-Authentication\","
                + "\"client_id\":\"" + settings.getClientId() + "\","
                + "\"grant_type\":\"password\""
                + "}";
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(req, headers);

        ResponseEntity<String> response = template.exchange("https://my-domain.auth0.com/oauth/ro", HttpMethod.POST, entity, String.class);

Do you know the equivalent in java driver 1.0.0 ?

I get the same behavior… using oauth/ro, I only have to add the user_metadata scope (no rule needed) to get all of the user_metadata (in a field of that name) into the JWT payload.

If I try to use the oauth/token endpoint, I get a valid login token, but no user_metadata, even if user_metadata is added to the scope. Using the rule above, I still get nothing added to the JWT payload.

Since the oauth/ro endpoint is marked as deprecated in the postman collection (but not actually in the docs though, which saddens me), I too would like an answer as to how I can actually put user metadata into the the JWT payload using the non-deprecated oauth/token endpoint.

1 Like

BTW, I don’t see the rule’s addition to the JWT payload in either case (ro or token endpoints).

Okay, I’ve got it. I opened a ticket and got the answer. You cannot use any auth0 url in the namespace. I’m now using https://{my app}.{my company}.com/ as the namespace, and now it works, with the conditions noted below.

Additionally I worked out the following on my own. I was using the oauth/ro endpoint which is marked deprecated in the postman collection, because the oauth/token endpoint didn’t return user_metadata when the user_metadata scope was set, and I needed that metadata. However, now that I am inserting the data into the payload, I don’t need the raw user_metadata.

But I see the opposite occurring. The inserted data doesn’t show up in the oauth/ro endpoint but it does in the oauth/token endpoint.

Conclusions:

  • Hoist all of the data you need to include into the payload via the rule.
  • Use a namespace distinct from any auth0 url.
  • Use the oauth/token endpoint to obtain the token.
3 Likes

Wahou ! Thanks a lot ! It is working with this rule :

function (user, context, callback) {
   var namespace = 'https://my-domain.my-company.com/';
   if (context.idToken && user.user_metadata) {
     context.idToken[namespace + 'user_metadata'] = user.user_metadata;
   }
   if (context.idToken && user.app_metadata) {
     context.idToken[namespace + 'app_metadata'] = user.app_metadata;
   }
   callback(null, user, context);
 }

Very strange bug Auth0 :wink: And you should document it ! @prashant

1 Like

Glad it worked…

For me, hoisting the entire user_metadata into the token isn’t what I want. I can always get the user profile for that. I needed two metadata fields, so I do a rule like this:

function (user, context, callback) {
  var namespace = 'https://myapp.mycompany.com/';
  if (context.idToken && user.user_metadata) {
    context.idToken[namespace + 'kind] = user.user_metadata.kind;
    context.idToken[namespace + 'ref'] = user.user_metadata.ref;
  }
  callback(null, user, context);
}

https://auth0.com/docs/api-auth/tutorials/adoption/scope-custom-claims

Thanks for the suggestions, this is now documented:

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.

For those looking to add metadata to a user’s ID token, Actions are now the recommended - Please see the following post for details:

:rocket: