ID token refresh fails when using Persistence.Cookie auth persistence method
Operating System
MacOS Sequoia Version 15.1.1
Environment (if applicable)
All browsers
Firebase SDK Version
11.6.1
Firebase SDK Product(s)
Auth
Project Tooling
React app with Next.js
Detailed Problem Description
Saw that there was a new Persistence.Cookie strategy for Auth in beta and decided to give it a try, since it should fix some Auth persistence issues we have in Safari. I'm mostly following the implementation from this PR.
It works great, except if a user logs in and doesn't close their browser for an hour, and the ID token expires, Firebase fails to refresh it. This logs the user out. Other persistence strategies don't seem to have this issue. I'm seeing that the error code is user-token-expired.
This seems to be because:
- When
PersistenceUserManager.getCurrentUseris called, the user is created using the stored cookie value. This user hasrefreshToken = nullsince the cookie value is just a string with the ID token and does not have the refresh token. - When the ID token is about to expire or expired and
getIdTokenrefreshed, this line fails since the refresh token isnull.
Steps and code to reproduce issue
- Set auth persistence to
browserCookiePersistence:
auth.setPersistence(browserCookiePersistence);
- Set up Next.JS middleware to handle browser cookie persistence (example)
- Without closing the browser tab/window, wait an hour so that the ID token expires (or lower the refresh interval so that the force refresh happens more quickly)
Expected result:
User remains logged in, and ID token is refreshed. Firebase makes a request to /__cookies__ endpoint to refresh the ID token.
Actual behavior
The ID token expires and is not refreshed. The error code received is user-token-expired. A DELETE request is sent to /__cookies__ endpoint to log the user out.
I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
@PhastPhood thanks for giving this a spin. I'll be looking into this as I do my next push on Cookie Persistence.
I've found some concerning race conditions with setPersistence, curious if you'd have better experience if you passed browserCookiePersistence in as an option to initializeAuth.
If you're using popups and autoinit+emulators that does mean a bit more gymnastics:
import { initializeAuth, browserPopupRedirectResolver, connectAuthEmulator } from "firebase/auth";
import { getDefaults } from "@firebase/util";
const popupRedirectResolver = typeof window === 'undefined' ? undefined : browserPopupRedirectResolver;
export const auth = initializeAuth(app, {
popupRedirectResolver,
persistence: browserCookiePersistence,
});
const authEmulatorHost = getDefaults()?.emulatorHosts?.auth;
if (authEmulatorHost) {
connectAuthEmulator(auth, `http://${authEmulatorHost}`);
}
@jamesdaniels Thanks for taking a look and adding this feature, really appreciate it!
Some context: We currently are using indexedDBLocalPersistence. However, this has some issues on Safari – Safari wipes indexedDB after 7 days of not interacting with a site. The majority of our users are on mobile iOS and do not use our site weekly, so this means that they are constantly getting logged out, and it's one of our most frequent user complaints. We would love to start using cookie persistence, which does not have this issue.
Ideally, I would like to not log out any users who currently have their auth persisted in indexedDB, and we don't use popups, so my current approach while I've been testing has been:
Auth initialization
initializeAuth(getApp(), {
persistence: [browserCookiePersistence, indexedDBLocalPersistence],
});
Before a new sign-in
auth.setPersistence(
isSafari? browserCookiePersistence : indexedDBLocalPersistence
);
I have noticed two issues with the above approach so far:
- If a user logs out from an indexedDB session, doesn't close the window, and now logs in using browserCookiePersistence while in the same session, it doesn't make the call to
__cookies__and doesn't persist the auth state in indexedDB or browser cookies - If a user logs in for the first time, it still calls the
__cookies__end point even if wesetPersistence(indexedDBLocalPersistence)right before.
I'll give it a shot with just initializing with browserCookiePersistence.
Thanks again for the help! This feature would really alleviate some issues with our user experience!
Ah gotcha, yeah I'll work in the upgrade path for browserCookiePersistence so that works as expected & continue to hunt for the setPersistence race condition that has so far eluded me.
Small update on this – I didn't realize that auth.setPersistence was an async function which was causing some race conditions 😅. After fixing that, I think the main issue that I'm seeing is just the ID token not handling refreshing in the same way that indexedDB does