Skip to content

onIdTokenChanged never called when token refreshes #2985

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

Closed
joelpoloney opened this issue Apr 24, 2020 · 19 comments · Fixed by #7054
Closed

onIdTokenChanged never called when token refreshes #2985

joelpoloney opened this issue Apr 24, 2020 · 19 comments · Fixed by #7054

Comments

@joelpoloney
Copy link

[REQUIRED] Describe your environment

  • Operating System version: MacOS 10.15.4
  • Browser version: Chrome 81
  • Firebase SDK version: [email protected]
  • Firebase Product: auth

[REQUIRED] Describe the problem

I’m running into an issue with the JS SDK and the onIdTokenChanged method (https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onidtokenchanged). Specifically, it doesn’t appear to get called when the token refreshes.

We use the Firebase Auth JWT in all requests to our API. If I leave a browser open for an extended period of time, the token never refreshes, and ultimately I start getting this error when trying to decode it:

Error: Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.

I’ve done extensive logging around this method and it appears that the token expires and onIdTokenChanged never gets called. Is this expected behavior?

Steps to reproduce:

I have a very vanilla implementation of Firebase Auth. I haven't changed any setting or changed the way persistence is done.

Relevant Code:

firebase.auth().onIdTokenChanged((user) => {
  if (user) {
    user.getIdToken().then((token) => {
      clearCachedClient(token)       
      userCallback()
    })
  } else {
    clearCachedClient(null)
  }
})
andyfusniak added a commit to capturoo/capturoo-dashboard-vue that referenced this issue May 3, 2020
@scottcrossen scottcrossen assigned sam-gc and unassigned scottcrossen May 11, 2020
@scottcrossen
Copy link
Contributor

I'm sad to hear that your experience with using Firebase was poor. To help you relive the good times I've assigned sam to this bug instead.

@sam-gc
Copy link
Contributor

sam-gc commented May 11, 2020

Hi Joel,

The token refreshes lazily when interacting with other Firebase services (or if you call getIdToken(true)). Is your API using another Firebase SDK or are you passing the user token directly to another service? If the latter, you'll need to handle refreshing the token yourself.

@joelpoloney
Copy link
Author

Yeah, I guess that wasn't clear from reading the documentation. You may want to disclose that and even put some more color around it on how tokens get refreshed. Here's what we ended up doing to get this fixed:

const getCurrentUserAuthorization = async () => {
  const currentUser = firebase.auth().currentUser
  if (currentUser) {
    const token = await currentUser.getIdToken()
    return `Bearer ${token}`
  } else {
    return ''
  }
}

Fortunately, the GraphQL library that we're using supports asynchronous headers (it'll resolve them before making a request), so this ended up working. But, I initially assumed that Firebase would be automatically keeping my token refreshed for me.

Maybe update the docs as an action item?

@sam-gc
Copy link
Contributor

sam-gc commented May 11, 2020

Yep, that seems reasonable. I'll follow up with the documentation. I'll also raise some sort of auto-renewal feature request with the team as something to consider in the future.

@sshquack
Copy link

@samhorlbeck Just ran into this issue with auth.onIdTokenChanged(observer) not firing and we ended-up with a getIdToken(forceRefresh) as @joelpoloney mentioned above. It would be great to have the documentation updated to clarify this lazy refresh behavior. Another suggestion would to provide a client-side helper function to trigger a refresh before the idTokenResult.expiration

@kuangthien
Copy link

Does anyone know the lazy time of "onIdTokenChanged" listener?
It's really annoyed :(

@WestonThayer
Copy link

Could you also update the docs with all the different ways User.getIdToken()'s Promise can reject? I'm guessing at least the following cases can exist:

  • The user was deleted
  • The user was disabled
  • The admin SDK revoked their refresh token
  • Any internal error

Reading through the source, it appears there could be more cases, and it's not clear how to identify these errors on the client (what are the error.code values?). I'm looking at fireauth.AuthUser.prototype.getIdToken, which calls into fireauth.StsTokenManager.prototype.getToken. That method will reject with fireauth.authenum.Error.TOKEN_EXPIRED if the refresh token is missing. But it calls into fireauth.RpcHandler.prototype.requestStsToken which can throw fireauth.authenum.Error.NETWORK_REQUEST_FAILED as well as any error from fireauth.RpcHandler.getDeveloperError_, which is just a general error translation function and not really helpful for understanding how requestStsToken can fail.

Given that getIdToken()'s Promise rejecting doesn't automatically sign out a user, might also be good to include a note on how to best handle each error (i.e. signing out the user yourself or prompting them for their password).

It'd be helpful to update the sample code here https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients with the various situations as well.

@sam-gc
Copy link
Contributor

sam-gc commented Jan 27, 2021

@sshquack This is something we've not yet had a chance to do. The request is being tracked internally (b/178638982)

@kuangthien There lazy refresh that occurs when the Auth library is being used by other Firebase SDKs is dynamic based on when the token is set to expire. You can read through the relevant code here: https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/proactiverefresh.js

@WestonThayer Thanks for the suggestion. Would you mind opening a separate feature request?

@WestonThayer
Copy link

@samhorlbeck sure, opened #4358

@kuangthien
Copy link

kuangthien commented Jan 28, 2021

@sshquack This is something we've not yet had a chance to do. The request is being tracked internally (b/178638982)

@kuangthien There lazy refresh that occurs when the Auth library is being used by other Firebase SDKs is dynamic based on when the token is set to expire. You can read through the relevant code here: https://github.com/firebase/firebase-js-sdk/blob/master/packages/auth/src/proactiverefresh.js

@WestonThayer Thanks for the suggestion. Would you mind opening a separate feature request?

Thnx u for very detailed explaination.
Actually I had another temporary solution :D I am using Firebase Realtime DB to store some values realted to authentication states. Then in client side, I just :
ref('path_public_uid_metadata').on('value',()=>{ // this callback will fire immediatly })

And in serverside, whenever the user change s.thing, it also have to write into that Firebase Realtime DB path :D

@tabekg
Copy link

tabekg commented Dec 6, 2022

the problem is still relevant

@druny
Copy link

druny commented Jan 24, 2023

same for me

@prameshj
Copy link
Contributor

A docs change is still pending here, we will look into that. The idToken can be manually refreshed with forceRefresh: true.

@annp87
Copy link

annp87 commented Feb 21, 2023

Same for me, the document should be update.

@sam-gc: It seems we need to enable Security Token Service API to let getIdToken(true) auto refresh, right?

@prameshj
Copy link
Contributor

It seems we need to enable Security Token Service API to let getIdToken(true) auto refresh, right?

That's correct.

@amin79
Copy link

amin79 commented Mar 24, 2023

@annp87 could you please explain how to do that?

@annp87
Copy link

annp87 commented Mar 24, 2023

@amin79: From my understanding you've just need to enable Token Service API from google cloud API, then you should call getIdTokenID(true) as https://firebase.google.com/docs/auth/admin/verify-id-tokens#web.

@evelant
Copy link

evelant commented Mar 29, 2023

Perhaps this is a bug or undocumented behavior with the emulators, but when I call getIdToken(true) to force a refresh onIdTokenChanged does not fire.

In my use case I just want a callback any time the firebase token refreshes so that I can refresh another custom token that depends upon the firebase token.

@sam-gc @scottcrossen seeing as this has been open for nearly 3 years and the behavior is still unclear could you possibly jot down some notes about the behavior here in a comment? That would at least provide some useful reference until whenever the docs might get updated.

@prameshj
Copy link
Contributor

Perhaps this is a bug or undocumented behavior with the emulators, but when I call getIdToken(true) to force a refresh onIdTokenChanged does not fire.

In my use case I just want a callback any time the firebase token refreshes so that I can refresh another custom token that depends upon the firebase token.

Sam Scott Crossen seeing as this has been open for nearly 3 years and the behavior is still unclear could you possibly jot down some notes about the behavior here in a comment? That would at least provide some useful reference until whenever the docs might get updated.

The docs were updated in #7054, hence marking as closed.

Can you confirm if this specific issue only happens in emulators?

this.auth._notifyListenersIfCurrent(this);
shows that the idToken listeners will be invoked if a new token was issued as part of the force refresh. I wonder if emulator returns the same token.

@firebase firebase locked and limited conversation to collaborators Apr 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.