How do you make app_metadata accessible in the callback?

I’m trying to access app_metadata but just can’t find how to do it in the docs.

I’m building a Node API and when /users/callback is hit, it gets a Profile with these attributes:
displayName, emails, id, name, nickname, picture, user_id, _json and _raw

app_metadata is not available. I tried adding this rule:

    function (user, context, callback) {
      const namespaces = ['http://localhost:3000', 'https://domain.com'];
      namespaces.forEach(namespace => {
        context.accessToken[namespace + '/permissionLevel'] = user.app_metadata.permissionLevel;
        context.idToken[namespace + '/permissionLevel'] = user.app_metadata.permissionLevel;
      });
      callback(null, user, context);
    }

which doesn’t seem to be doing anything.

Could someone just give me a basic example of how to access metadata after the user logs in?

Are you sure there is actually any app_metadata in the user profile you’re fetching? You can check that in the user profile > raw json in the Dashboard.

Unless you added app_metadata before somehow, i.e. through a rule, there shouldn’t be anything in there, usually.

Because other than that, your rule looks fine. Just wondering why you need two namespaces with the same value. Note that the name space can be any URI, doesn’t even have to really exist nor does it need to match your app/client URL.

Can you post what the decoded JWT token payload (either one) looks like. Does it not contain the permissionLevel claim at all, or is it just empty?

Thanks for the swift reply. I checked and the raw json includes:

“app_metadata”: {
“permissionLevel”: 1
}

I also have a hook that ensures every user has at least permissionLevel in their app_metadata.

About the rule being a bit weird, yeah, I tried to make it as broad as possible. I didn’t know if I needed the accessToken or the idToken and I wasn’t sure about the domain so I cover all the possibilities and then remove the unnecessary ones. None of them seem to work though.

When you say “It didn’t work”, what do you mean exactly? Is the permissionLevel not included in the ID or access token? Did you check the content of the tokens? (i.e. via jwt.io)

Regarding the difference between ID and access token, see:

I’m using passport and express on the back end. When Auth0 logs the user in, I have it redirect to my API callback where I use

passport.authenticate('auth0', (err, user, info) => {
    req.logIn(user, err => {
        res.redirect('/')
   }
}(req, res, next)

(I’m simplifying the code a little, leaving out error handling etc.)

When I try to check req.user, it only has the fields I listed above. It might have something to do with the way I have passport set up but I did that according to the docs so that shouldn’t be a problem.

So I’m not directly interacting with any tokens, I have passport do that for me using Auth0Strategy.

I see. Let me try to replicate the issue; not a big Node developer, but I will check with the Quickstart that we have.

Update: it’s definitely something with the passport / Auth0Strategy, the rule is fine.

That’s great, thank you.

Here’s my Passport setup, just in case that helps:

const {domain, clientID, clientSecret, callbackURL} = require('./config/auth0');
const passportStrategy = new Auth0Strategy(
{domain, clientID, clientSecret, callbackURL, state: false},
(accessToken, refreshToken, extraParams, profile, done) => done(null, profile)
)
passport.use(passportStrategy);
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));

As said, I’m not too familiar with passport, but it seems that the custom claims are ommitted, while they are actually returned in the raw data and in the _json object.

You can try with this strategy, which should return all claims in a flat format (not nested as before), including the permissionLevel.

const {domain, clientID, clientSecret, callbackURL} = require('./config/auth0');

const passportStrategy = new Auth0Strategy(
  {domain, clientID, clientSecret, callbackURL, state: false},
  function (accessToken, refreshToken, extraParams, profile, done) {
    return done(null, profile._json);
  }
);

passport.use(passportStrategy);
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));

Ohhhh yeah it works. That’s really strange, I could have sworn I checked _json like a hundred times and I didn’t see it there. Well anyway, it works now, thank you so much for all the help.

Just out of curiosity, why does the idToken key need to include a URI? I tried to make it work without it but it just refuses to accept a normal name.

Glad it works now.

Custom claims must have an URI or URN because that’s the standard for namespaced formats. Why that is chosen, I don’t know.

The claim name must conform to a namespaced format to avoid possible collisions with standard OIDC claims.

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