Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trigger a refresh for the getUser method #274

Closed
Tazaf opened this issue Nov 7, 2019 · 24 comments
Closed

Trigger a refresh for the getUser method #274

Tazaf opened this issue Nov 7, 2019 · 24 comments

Comments

@Tazaf
Copy link

Tazaf commented Nov 7, 2019

Describe the problem you'd like to have solved

Note My app is an Angular8 app, that uses the AuthService provided 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.

Describe the ideal solution

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 ])).

Alternatives and current work-arounds

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 😅

@stevehobbsdev
Copy link
Contributor

@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:

  • Which token type are you putting the complete claim into? ID token or access token?
  • Have you confirmed that the token has complete: true in it and that the value is being updated correctly through the management API?
  • 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 totrue

Thanks

@patricknee
Copy link

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?

@stevehobbsdev
Copy link
Contributor

@patricknee If you inspect the returned token after the call to getTokenSilently when you expect your rule to run, can you determine whether:

  • The rule is being applied, the returned token has the new claim but the SDK isn't updating its cache
  • The returned token doesn't contain the new claim at all

This will give us a clue as to where the problem lies.

@Tazaf
Copy link
Author

Tazaf commented Nov 12, 2019

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

@stevehobbsdev
Copy link
Contributor

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.

@patricknee
Copy link

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.

@stevehobbsdev
Copy link
Contributor

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).

@patricknee
Copy link

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

@patricknee
Copy link

patricknee commented Nov 14, 2019

@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": "pnetsmail-ms@yahoo.com",
  "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"
  ]
}

@patricknee
Copy link

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?

@stevehobbsdev
Copy link
Contributor

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.

@stevehobbsdev
Copy link
Contributor

stevehobbsdev commented Nov 19, 2019

@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.

@stevehobbsdev
Copy link
Contributor

@patricknee @Tazaf Any luck trying out my suggestion above?

@Tazaf
Copy link
Author

Tazaf commented Nov 27, 2019

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.

@Tazaf
Copy link
Author

Tazaf commented Nov 27, 2019

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() ?

@stevehobbsdev
Copy link
Contributor

stevehobbsdev commented Nov 27, 2019

@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.

@ed-sparkes
Copy link

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

@ckalan
Copy link

ckalan commented May 7, 2020

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)

@stevehobbsdev
Copy link
Contributor

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:

  • You must be using your own keys and not Auth0's developer keys, or
  • You must be using the new login experience rather than the classic "Lock" experience.

@ckalan
Copy link

ckalan commented May 13, 2020

@stevehobbsdev Sorry for my misunderstanding. It actually worked when I used my own Google developer credentials.

@stevehobbsdev
Copy link
Contributor

Closing this for now due to lack of activity. Feel free to continue the discussion if you are still having issues.

@prostakov
Copy link

Having the same problem - the only way to force refresh on user data is to reload the page.

Please consider adding method getUser({ ignoreCache: true }) or getUser({ forceRefresh: true ])

@frederikprijck
Copy link
Member

frederikprijck commented Sep 12, 2023

@prostakov using getTokenSilently({ cacheMode: 'off'}) should work. There is no such thing as getting the user from Auth0 using our SDK, instead we get a set of tokens and use the id token to extract the information and build a user that you can retrieve from our SDK. If you want to refresh the user, you should refresh the token and it should reflect in the updated user.

Are you experiencing issues when trying to use getTokenSilently like that?

@prostakov
Copy link

Ohhh, I see it. Sorry, should have caught up on the entire thread.

Yes, getAccessTokenSilently({ignoreCache: true}) does work, it does indeed pull the fresh id token and properly refreshes the user$ observable.

A bit counter-intuitive though :)

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants