1

What happens when an ID token expires, and when does firebase use the refresh token to get a new id token?

I have a very basic Android app, in which a user can login with email and password. That part works and after they login I can check for the currentUser and sure enough it has a user object (and it’s isAuthenticated property is true). At this point that’s all the app does (I’m just starting it). The user can login and see a “Welcome” screen. But then I found something weird. I deleted the user from the Firebase console. When I start the app back up, the currentUser is still there and still logged in (isAuthenticated = true). That’s not all that surprising. The ID token doesn’t expire for 1 hour, so it would make sense that the user is still logged in for a period of time. But, I waited a LONG time (over 24 hours) and the user is STILL logged in. That surprised me, because after an hour the id token should’ve expired and a refresh token used to get a new id token (which would fail since the user no longer exists). But that hasn’t happened. So that leads me to the above two questions.

When does firebase use the refresh token to get a new id token? I can kind of understand why my user might not get logged out. I don’t have any functionality in my app (yet) where the user can DO something that would trigger a token refresh. OK, but what kinds of activities will trigger a refresh? Or do I need to make a call to refresh the token? If so, how do I know when to make that call? How do I know when the ID token is expired?

Also, in my scenario above, once the ID token does expire, I was surprised that the user object still shows isAuthenticated = true. What happens once the ID token expires? Is there some way to know it’s expired?

Below is a simple screen that demonstrates what I've described. When it loads it checks the User object to see if it exists (and if it's isAnonymous property is true or false).

It also uses a LaunchedEffect to check again after 3 seconds (in case it takes firebase a little bit to initialize and try to refresh the token).

@Composable
fun HomeScreen(onLoginClick: ()->Unit = { /* navigate to login screen */ }) {

    LaunchedEffect(Unit) {
        Firebase.auth.addAuthStateListener { auth ->
            Log.d("LOGIN", "Current User: ${auth.currentUser}")
        }
    }

    var isLoggedIn by remember {
        val loginState = !(Firebase.auth.currentUser?.isAnonymous ?: true)
        Log.d("LOGIN", "Is logged in: $loginState")
        mutableStateOf(loginState)
    }

    LaunchedEffect(Unit) {
        delay(3_000)
        val loginState = !(Firebase.auth.currentUser?.isAnonymous ?: true)
        Log.d("LOGIN", "Is logged in after delay: $loginState")
        isLoggedIn = loginState
    }

    Column {
        if (isLoggedIn) {
            Text("Welcome, Authenticated User!")
        } else {
            Button(onClick = { onLoginClick() }) {
                Text("Log In")
            }
        }
    }
}

When I call the above composable with a logged in user it displays the text "Welcome, Authenticated User!". I also see two log messages for "is logged in: true", and "is logged in after delay: true".

Then I go in to the firebase console and delete the user. I also close the app. Now I wait an entire day (24+ hours). I launch the app again and it displays:

"Welcome Authenticated User" and the same two log messages, ""is logged in: true", and "is logged in after delay: true".

I would expect it to display the Login button and the log messages to contain "false" for the isLoggedIn property, because either the currentUser is null, or it's isAnonymous property is true. But neither is the case. Even though the user hasn't existed for over a day in Firebase, currentUser is still set and it's isAnonymous property is false.

The Output log is:

14:16:29.448 LOGIN  D  Is logged in: true
14:16:29.944 LOGIN  D  Current User: com.google.firebase.auth.internal.zzx@bd4236a
14:16:32.757 LOGIN  D  Is logged in after delay: true
Bradleycorn
  • 980
  • 6
  • 9

1 Answers1

0

Firebase ID tokens are minted to be valid for an hour. The Firebase Authentication SDK requests a fresh ID token from the server about 5 minutes before the current token expires.


If that doesn't seem to be happening for you, edit your question to show the minimal code with which you can reproduce the behavior. I recommend including logcat output from around when Firebase should be refreshing the token, as that'll show any errors from the SDK


Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • "the Firebase Authentication SDK requests a fresh ID token from the server about 5 minutes before the current token expires". What if the app is not running at the time, and the user launches it sometime later, AFTER the token expiry time has passed? Also, [an answer on a similar question](https://stackoverflow.com/questions/49656489/is-the-firebase-access-token-refreshed-automatically?rq=1) seems to contradict this: "On its own the Auth service will not proactively refresh (as this is an expensive operation and it may be unnecessary if there is no consumer of the new token)." – Bradleycorn Aug 18 '23 at 13:52
  • 1
    In that case the app will refresh the ID token upon restart. Please edit your question to show the minimal code that reproduces the behavior you're seeing though, as an interactive Q&A is not helpful for others. – Frank van Puffelen Aug 18 '23 at 14:18
  • Added some sample code to show what I'm seeing in the app. – Bradleycorn Aug 19 '23 at 17:39
  • When the app restarts, the Firebase SDK will try to refresh the ID token. If the device is connected to the internet, that should lead to failure to get a fresh ID token and thus the user no longer being signed in. The delays make it harder than necessary, so I recommend using an auth state listener as I've shown here: https://stackoverflow.com/collectives/google-cloud/articles/68104924/listen-for-authentication-state-in-android. I wonder if that fires one or twice in your use-case. – Frank van Puffelen Aug 19 '23 at 17:48
  • I updated the example code to include an AuthListener, and included the output displayed in logcat. The listener only fires once, and does have a user. Also I can remove the code with the delay and double check of the currentUser. I only added that for this demo just to be certain it wasn't case of "checking too soon after launch". – Bradleycorn Aug 19 '23 at 18:23
  • In Android you **should** not be able to check too soon, but a bug in the SDK is always possible of course. The auth state listener would've shown that though. Given that it only fires once, it's clear the user is and stays logged in. You might want to use an ID token listener too, to see your client is getting a new ID token from the server - but we're definitely into troubleshooting land at this point. – Frank van Puffelen Aug 19 '23 at 19:49
  • Yeah, I added an IdTokenListener as well, and it's basically the same as the auth listener. It fires once, and it has a user object. I'm also building an iOS version of the same app, and it has the same behavior. Perhaps it's time to file a bug report with the Firebase team. – Bradleycorn Aug 19 '23 at 20:26
  • Yeah, that could be. Just make sure to include an MCVE, something **minimal** that they can run in a debugger without modification to see what is goign on. – Frank van Puffelen Aug 19 '23 at 21:59