Note My app is an Angular8 app, that uses the
AuthServiceprovided in the Auth0 Angular Tutorial.
After users first logged into my app, they must complete their profile with some information. On Auth0, upon signup, I initialize every account with an app_metadata named complete with a value of false.
When a user complete their profile, the backend that recevies this completed profile sends a request to the Auth0 Management API to change this app_metadata.complete value from false to true.
But the frond end is not aware of this update, since it's happening behind it's back.
I tried making a method that calls getTokenSilently and ignoring the cache, in order to get the latest version of my user's tokens, then calling the getUser method to retrieve the user's profile from the tokens.
Except that the profile is still the old one, with the complete: false value.
I need a way to somehow trigger a refresh of the currently logged in user info.
Something along the line of getTokenSilently({ ignoreCache: true}), but for the decoded user profile (e.g. getUser({ ignoreCache: true }) or getUser({ forceRefresh: true ])).
The workaround I have now, is when the backend finishes updating the user account on Auth0, the front end calls the login method of the AuthService. That restart a complete login process, but since my user already logged in before, it behave like an SSO login, and the app is simply reloaded.
That works... but I'd like to avoid this full reload for a more streamlined user experience.
I'd like to add that I don't dismiss the possibility this is already achievable and I just don't saw how 馃槄
@Tazaf Looking at the code, getTokenSilently does replace all the items in the cache with all the properties in the ID token. So, a couple of things:
complete claim into? ID token or access token?complete: true in it and that the value is being updated correctly through the management API?complete should be false and when it transitions totrueThanks
I am having the same issue as @Tazaf.
A Rule adds Auth0 Core Roles to my user at login.
accessTokenClaims[namespace + 'roles'] = assignedRoles;
context.accessToken = accessTokenClaims;
Programatically I am adding new roles to the user. In my SPA calling getTokenSilently({ ignoreCache: true}) is NOT rerunning the rule and NOT picking up the new roles.
Am I doing something incorrectly?
@patricknee If you inspect the returned token after the call to getTokenSilently when you expect your rule to run, can you determine whether:
This will give us a clue as to where the problem lies.
Hi, @stevehobbsdev
Which token type are you putting the complete claim into? ID token or access token?
The claim is being added to the ID Token. Here's the rule code:
function addUserAppMetadataToIdToken(user, context, callback) {
const namespace = configuration.hubble_api_namespace;
context.idToken[ `${namespace}/app_metadata` ] = user.app_metadata;
callback(null, user, context);
}
Have you confirmed that the token has complete: true in it and that the value is being updated correctly through the management API?
Yep, that behavior is confirmed. The call to the Management API does indeed update the profile, since the workflow works as expected upon app reload.
Do you have HAR file you can share that exibits the behavior? Try to include all the calls, including when complete should be false and when it transitions to true
I... have to admit I have no idea what's an HAR file 馃槥
Anyway, I'll try ASAP what you asked @patricknee and come back with the results
Thanks @Tazaf. Bear in mind that getTokenSilently will just return the access token. So in your case you will want to examine the ID token, which you can get back using (await auth0.getIdTokenClaims()).__raw.
Just wanted to mention that in my own testing, these scenarios work. So I have a custom rule which applies a custom claim to my access token (or ID token, both work). I turn it off and call getTokenSilently. I then turn it on, get the token again and I can see my custom claim in there.
There are a few points of failure here in both of your cases so it would be good to nail down where, but right now it looks like the SDK itself is doing the right thing. Please get back to me with confirmation of the above though, and I can investigate further.
I am trying to rebuild a simple app replicating (or resolving) the problem.
I have forked the repository:
https://github.com/auth0-samples/auth0-angular-samples.git
to:
https://github.com/patricknee/auth0-angular-samples.git
The premise of the test is I will add a role to my user via the Auth0 Dashboard (in my application this occurs via the Management API), then push "Update Profile" button and see whether I can force and update, after which the decoded token should have the new role.
In my fork I have made the following changes:
1) Inserted keys for my tenant in auth0_config.json
2) npm i @auth0/angular-jwt
3) Modified home.component.html with:
a) Button calling updateProfile()
b) Display token, decodedToken
4) modified home.component.ts with:
a) updateProfile() function
5) Added these files to .gitignore :
a) auth0_config.json
b) .idea folder (IDE)
My Auth0 Rule to add the roles to the idToken and accessToken is the following:
function (user, context, callback) {
// TODO: implement your rule
const namespace = 'http://opinionatedstack.com/';
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[namespace + 'roles'] = assignedRoles;
idTokenClaims[namespace + 'app_metadata'] = user.app_metadata;
context.idToken = idTokenClaims;
accessTokenClaims[namespace + 'roles'] = assignedRoles;
accessTokenClaims[namespace + 'app_metadata'] = user.app_metadata;
context.accessToken = accessTokenClaims;
callback(null, user, context);
}
I am having difficulty with:
With line 27, the code works, but of course the token comes from the cache.
With line 26, the call to getTokenSilently$ doesn't work, returning an error "consent_required". (I have been working with Auth0 for ~2 years and I either haven't seen this or don't recall how to resolve this error)
This line 26 is blocking fully replicating the issue I'm having.
Just to confirm, this is the block of code you're referring to with the line numbers?
async updateProfile() {
try {
this.token = await this.auth.getTokenSilently$({ignoreCache: true}).toPromise();
// line 26: this.token = await this.auth.getTokenSilently$().toPromise();
this.decodedToken = this.jwtHelper.decodeToken(this.token);
} catch (e) {
alert( JSON.stringify(e));
}
}
I see on line 25 you're ignoring the cache there, so this should fetch the token from the server.
With line 26, the call to getTokenSilently$ doesn't work, returning an error "consent_required"
You can get this when something has changed in the scopes you're requesting from the authz server compared to the last time you logged in. To fix it, you need to go through an interactive login flow first, before authenticating silently (Auth0 will then ask you to confirm the permissions you're asking for).
Steve,
Looks like my line numbers were off by one. Maybe I deleted something before commit.
The ignoreCache call is triggering consent_required, but given that I logged in the application immediately before calling getTokenSilently$, it isn't clear where scopes are changing...
Are you seeing this error with the getTokenSilently({ignoreCache: true}) line?
Patrick
@stevehobbsdev
From the logs, the failed call to getTokenSilently$({ignoreCache:true}) has the same scopes as the successfully logged in user. See log and decoded token below. I don't think changing scope is the reason I am unable to call getTokenSilently$({ignoreCache:true}) in the sample application.
Failed Silent Auth Log:
{
"date": "2019-11-14T08:55:58.159Z",
"type": "fsa",
"description": "Consent required",
"client_id": "xxx",
"client_name": "Opinionated Stack Demo",
"ip": "93.57.72.236",
"user_agent": "Chrome 78.0.3904 / Mac OS X 10.14.6",
"details": {
"body": {},
"qs": {
"client_id": "xxx",
"redirect_uri": "http://localhost:4200",
"scope": "openid profile email",
"response_type": "code",
"response_mode": "web_message",
"state": "bmRHRUpxbm9jSXZ4eGFXZk9qckhfcUZ1Q2pJaXlpOEVPLnhhM251Yn51dw==",
"nonce": "2FQ~3vkeDOyfs0PCWTXvtxZbe0DyL0zi6r85u~Y9naP",
"code_challenge": "fWPT-tf3uhuTKfqS6-SaP-9GotnqMZ2gYdEO6nBD0Z0",
"code_challenge_method": "S256",
"prompt": "none",
"auth0Client": "xxx="
},
"connection": null,
"error": {
"message": "Consent required",
"oauthError": "consent_required",
"type": "oauth-authorization"
},
"session_id": "flIHaFT-tapAIiXtKYpqFMdZI-HgjdPt",
"session_connection": "Username-Password-Authentication"
},
"hostname": "opin-stack-demo.auth0.com",
"user_id": "auth0|xx",
"user_name": "[email protected]",
"audience": "https://opin-stack-demo.auth0.com/userinfo",
"scope": [
"openid",
"profile",
"email"
],
"auth0_client": {
"name": "auth0-spa-js",
"version": "1.5.0"
},
"log_id": "90020191114085601203000201342208078448173434826282500194",
"_id": "90020191114085601203000201342208078448173434826282500194",
"isMobile": false
}
Decoded Token after successful login:
{
"http://opinionatedstack.com/roles": [
"Admin",
"BasicSubscriber",
"User"
],
"http://opinionatedstack.com/app_metadata": {
"stripe_customer_id": "cus_G8tNrZlQ14We8N",
"paid_until": "2019-11-13T09:16:13.000Z"
},
"iss": "https://opin-stack-demo.auth0.com/",
"sub": "auth0|xx",
"aud": [
"https://test.opinionatedstack.com",
"https://opin-stack-demo.auth0.com/userinfo"
],
"iat": 1573722186,
"exp": 1573722366,
"azp": "xx",
"scope": "openid profile email",
"permissions": [
"post:read",
"roles:read",
"test:getTokenSilently",
"users:read",
"users:write"
]
}
I am temporarily working around this problem by reloading the entire SPA, which correctly picks up the updated state (added roles, in my case) from Auth0 without forcing a new login.
However, I'm still unable to get this basic call working in the basic example application. Any idea why it is not working?
Sounds like you've found something that will get you moving for now. I haven't had a chance to dive into this too deeply yet; I'm a little challenged for time today, but I hope to get a look at this tomorrow for you.
@patricknee I'm trying to replicate your issue, and I have uncovered a bug - which might be related - relating to specifying options to getTokenSilently. Because you've specified an options object, it causes the SDK to not send an audience value or scopes, meaning you don't get an access token in JWT format returned to you.
To rule it out in case this is affecting you, could you please manually specify the audience value in the call to getTokenSilently where you ignore the cache? Something like this:
await getTokenSilently({ ignoreCache: true, audience: '<your audience here>' })
This will be fixed anyway but would be good to try it out in your case.
@patricknee @Tazaf Any luck trying out my suggestion above?
Hi @stevehobbsdev
Thanks for the heads up. I've been caught up with other tasks lately. I'm trying this thread's suggestions right now. I'll come back when I have some results.
I've got a question, though.
You said that getTokenSilently() does not apply to the IdToken, only the AccessToken, and that I should use getIdTokenClaims()'s __raw property to inspect the IdToken.
But... getIdTokenClaims() does not refresh the token, right, it only get it from the last cached token?
There's no ignoreCache option for this method, and after trying it in my use case, the IdToken's content has not changed.
This is the snippet I'm executing after having updated the user's profile on Auth0 (note that I use the AuthService that's provided by the Angular2 tutorial in the documentation of Auth0, hence the need to get the client):
this.auth.auth0Client$.subscribe(async client => {
console.log((await client.getIdTokenClaims()).__raw);
});
This prints out the encoded token which, when decoded through the https://jwt.io/ tool, contains the deprecated values (complete: false).
Do I have to call getTokenSilently({ignoreCache: true}) anyway before getIdTokenClaims() ?
@Tazaf Yes you're right. Right now, if you want to refresh the ID token you would have to call getTokenSilently({ ignoreCache: true }) first in order to refresh the cache. getIdTokenClaims() just returns what it already knows about the token and does not refresh it, as you've observed.
Not saying that's entirely ideal or obvious, but what we're discussing here is a potential new use-case and we could make this easier for you in the future, depending on the outcome of this issue.
What I'm interested in is if you can call getTokenSilently first, _ignoring the cache_ but without specifying an audience value and observe the results. _Then_ try specifiying an audience in the getTokenSilently call and see what happens.
This would be super useful for me too.
Refreshing the access token using getTokenSilently({ignoreCache:true}) and then getUser() is working for me, but would certainly prefer to not have to refresh my accessToken, am using react hooks and my data retrieval on the page is re-triggered if the access token changes with a useMemo, would prefer not to have to reload my data just because the users profile has changed
I have the same issue and getTokenSilently({ignoreCache: true}) doesn't work with 3rd party identity providers such as Google OAuth so recommended approach doesn't work in such scenarios. There is already a bug report for that where it is mentioned that it is not considered a bug but I think it is at least a design flaw in JS SDK. (I am using Angular SDK)
doesn't work with 3rd party identity providers such as Google OAuth
Can you elaborate on what you're seeing? It does work with third-party providers, but under certain circumstances:
@stevehobbsdev Sorry for my misunderstanding. It actually worked when I used my own Google developer credentials.
Closing this for now due to lack of activity. Feel free to continue the discussion if you are still having issues.
Most helpful comment
@patricknee I'm trying to replicate your issue, and I have uncovered a bug - which might be related - relating to specifying options to
getTokenSilently. Because you've specified an options object, it causes the SDK to not send an audience value or scopes, meaning you don't get an access token in JWT format returned to you.To rule it out in case this is affecting you, could you please manually specify the
audiencevalue in the call togetTokenSilentlywhere you ignore the cache? Something like this:This will be fixed anyway but would be good to try it out in your case.