Silent Authentication not returning correct idToken augmented in a rule

In my SPA, I am calling checkSession() method as per the docs, and although it works, the authResult doesn’t contain any of the custom claims/data added by my rules, that i successfully receive on explicit login.

I’ve just checked and the rule using Real-time Webtask Logs and i can see that the rule is in fact firing, and that the correct data is being set in the user object.

However the Id token that is received back from the call does not containing the populated app_metadata and user_metadata claims. These are successfully received within the id_token after an explicit login.


I am using the hosted login page to do the initial authentication along with auth0.js 8.10.1

auth0.authorize() calls the login page.

my checkSession is called …

refreshSession() {
        var authInstance =this
        this.auth0.checkSession({
            audience: 'my audience url',   
            responseType: 'token id_token',
            scope: 'openid profile email api'
        }, function (err, authResult) {
            console.log(authResult)
            authInstance.setSession(authResult)                      
        })
  }

and my rule is …

 function (user, context, callback) {
 
   user.app_metadata = user.app_metadata || {};
  
   // the user's requested scope
   var requested_scopes_string = context.request.query.scope || '';  
   var requested_scopes = requested_scopes_string.split(' ');

 var subscriptionEndOfDays = 3376684800000; 
   
   // if the user requested API access, then figure
   // out which custom openid connect header to add
   if (requested_scopes.indexOf("api") >= 0) 
   {
      if(context.accessToken) 
      {             
        //remove any api scopes from scope array 
        var cleansedArray  = requested_scopes.filter(function(item)
          {
             return item !== "api";
          });
      
        requested_scopes = cleansedArray;
      }
   }     

  // give admin users 120 years subscription
   if (user.app_metadata.isAdmin)
   {
     user.app_metadata.subscriptionExpiry = subscriptionEndOfDays;
   }
                    
         //test user has valid subscription set the flag 
    if(user.app_metadata.subscriptionExpiry && user.app_metadata.subscriptionExpiry > new Date().getTime())   
        {
          user.app_metadata.hasSubscription= true;
          requested_scopes.push("api");   
        } 
   else     
   {
     user.app_metadata.hasSubscription = false;
   }

   context.accessToken.scope = requested_scopes;
      
   var namespace = 'https://myhost:eu:auth0:com/';
   context.idToken[namespace + 'app_metadata'] = user.app_metadata;
   context.idToken[namespace + 'user_metadata'] = user.user_metadata;
   
   callback(null, user, context);
 }

Updated my rule to have the following code at the end…

       console.log("access token: ");
       console.log(context.accessToken);
       console.log("id token: ");
       console.log(context.idToken);
       
       callback(null, user, context);

which outputs the correct token into the realtime log.

access token:
08:00:16: 
{ scope:  'openid', 'profile', 'email', 'api' ] }

id token:
08:00:16: 
{ 'https://domain:eu:auth0:com/app_metadata': 
   { isAdmin: true,
     subscriptionExpiry: 3376684800000,
     hasSubscription: true },
  'https://domain:eu:auth0:com/user_metadata': {} }

So to summarize, the rule is firing, it appears that the correct context is being prepared and passed to the callback. however when i receive the result via the webAuth.checkSession({}) and log it out to the console. i am getting an access token without the api scope and an id token without the app_metadata claims which are in an OIDC compliant form.

You mention app_metadata and user_metadata claims which leads me to assume that the initial authentication is being performed through legacy authentication endpoints that do not conform to the OpenID Connect specification. Long story short legacy endpoint allowed non-standard claims (like app_metadata property) to be returned directly while the current OIDC compliant authentication methods require any custom claims to be added with a namespace.

I did a quick test in a SPA usig Auth0.js authorize for initial authentication and then checkSession and my custom claim was added and available in both responses. For reference, here’s the rule I used:

function (user, context, callback) {
  context.idToken"https://claims.example.com/ref"] = "X01";

  callback(null, user, context);
}

The context.idToken construct is the correct way to add claims in an OIDC compliant authentication flow which is also the recommended flow. See the reference documentation for more details. For a more detailed answer about the exact root cause you should update the question with relevant code snippets for the Auth0.js code used to authenticate, perform silent authentication and also the relevant rule.


Repeated my tests with the following rule to be more similar to yours:

function (user, context, callback) {
  var namespace = 'https://{my_domain}:eu:auth0:com/';

  context.idToken[namespace + 'app_metadata'] = user.app_metadata;
  context.idToken[namespace + 'user_metadata'] = user.user_metadata;

  callback(null, user, context);
}

However, I could still not reproduce the issue; I did my tests with Auth0.js 8.11.2 and 8.10.1 versions. When using the 8.10.1 my application logged a somewhat different response (didn’t include idTokenPayload) however, the ID tokens (independently of version being used) always contained the custom claims.

You may want to capture a full HTTP trace of both the authentication and renew happening (ideally with something other than Chrome which omits response bodies god knows why). You can check Fiddler as possible tool to capture the trace and then also feel free to share it in a password protected file; you can share the password only to @auth0.com emails through sharelock.io service.

Thanks for the response. As said, this same rule populates everything ok during the login but not on the refreshSession.

Just a thought.
Just wondering if something else is amiss as the requested custom scope of ‘api’ is not coming through in the access token in the call to checkSession however it does during the standard login flow in the same rule.
Steve

Thanks for the response. As said, this same rule populates everything ok during the login but not on the refreshSession.

Just a thought.
Just wondering if something else is amiss as the requested custom scope of ‘api’ is not coming through in the access token in the call to checkSession however it does during the standard login flow in the same rule.
Steve

@stephenbinge see the update to my answer; I repeated the tests with a configuration more similar to yours but still had no luck reproducing it.

@jmangelo
Many thanks for your help.

Please see attached a fiddler trace with a txt file annotating the applicable lines.

Here’s the password using sharelock as requested.

https://sharelock.io/1/0gd2xgNjzK0h7pVqKkxRUTCQkQz6Hiyf8M9pY_EQamM.c5ko5s/JREXi3AZkHkzk7t2FbofURE3Lm7M9F-jdNOl41yjWXLQ95sAS-/_w4-B_63GiiKd2k.bzF-HY_pazDahZdYqUkT2A

link text

@jmangelo
Many thanks for your help.

Please see attached a fiddler trace with a txt file annotating the applicable lines.

Here’s the password using sharelock as requested.

https://sharelock.io/1/0gd2xgNjzK0h7pVqKkxRUTCQkQz6Hiyf8M9pY_EQamM.c5ko5s/JREXi3AZkHkzk7t2FbofURE3Lm7M9F-jdNOl41yjWXLQ95sAS-/_w4-B_63GiiKd2k.bzF-HY_pazDahZdYqUkT2A

link text

Thanks for providing this, I already took a look at the capture so I have now a better understanding of the steps being taken. From the first review there is nothing suspicious that I have noted for now, however, I’ll still have to do a second review at the same time I try to reproduce this in my account (now with even more similar steps to yours). I should be able to do this tomorrow and provide another update, but feel free to ping me later this week if I haven’t.

thanks @jmangelo,

I am edging closer towards my launch date so really appreciate your attention, but my work around is to force users to re-authenticate after paying via the subscription payment gateway to force the right claim ‘api’ inside the access token via the rule. So its not a massive problem, however it is less than optimal for the user experience.

I was wondering if it might be easier for you to debug, if I setup an example on something like jsfiddle.net and shared privately with you.

Steve

thanks @jmangelo,

I am edging closer towards my launch date so really appreciate your attention, but my work around is to force users to re-authenticate after paying via the subscription payment gateway to force the right claim ‘api’ inside the access token via the rule. So its not a massive problem, however it is less than optimal for the user experience.

I was wondering if it might be easier for you to debug, if I setup an example on something like jsfiddle.net and shared privately with you.

Steve

If you can setup an online sample showing this it would greatly help in troubleshooting; I just tried now again with a rule exactly like yours an by updating app_metadata entries after login and did not reproduce. It was just some initial tests so did not ensure that every parameter was like yours, but an online sample would be awesome. In addition, can you try to repro with only that rule enabled? (in case you have others)

@jmangelo SORTED!
The last sentence led to the issue (rules), although i’d appreciate it if you can can confirm my understanding here…

I had disabled all subsequent rules apart from one that in my testing scenario i knew didnt amend the User or Context…

function(user, context, callback) {
    
  if (context.stats.loginsCount > 1) {
  console.log("returning callback");
    return callback(null, user, context);
  } 

...

My login count is always be more than one, so I thought that this would simply callack and return and not change anything the previous rule had set.

It seems there was a misunderstanding on my part, and that even though the rules run in a set order, any amendments to the user or context are not passed to the next rule.

Can you confirm this is a accurate ? e.g each rule just gets the version of the user and context at the point of authentication and without any previous augmentation by previous rules.

I assume not, but is there anyway this can be achieved ?

It seems logical that you could make incremental changes that are passed through into each rule, and also from one of your documents (GitHub - auth0/rules: Rules are code snippets written in JavaScript that are executed as part of the authentication pipeline in Auth0)

An App initiates an authentication request to Auth0 (Step 1), Auth0 routes the request to an Identity Provider through a configured connection (Step 2). The user authenticates successfuly (Step3), the user object that represents the logged in user is then passed through the rules pipeline and returned to the app (Step 4).

Anyway, thanks so much for your help.

@jmangelo, can you offer any insight on this ?
thanks,
Steve