Firebase Auth Internals: Tokens, Sessions, and Forced Logout
While working on a hobby project that used Firebase Authentication with multi-tenancy, I spent a lot of time understanding how Firebase actually manages sessions. The abstractions are good, but they hide behavior that matters when you need to implement things like forced logout or cross-tab session sync.
Here’s what I learned.
Two Tokens, Two Purposes
When a user logs in, Firebase issues two things:
- ID token: A short-lived JWT (1 hour) containing user claims. This is what you send to your backend for verification.
- Refresh token: A long-lived token stored by the SDK. This is used to silently get new ID tokens when the current one expires.
The ID token is stateless. Firebase does not store it server-side. It validates incoming tokens by checking the signature against its public keys, not by database lookup. This is fast and scalable, but it means Firebase cannot directly revoke an ID token — it simply waits for it to expire.
The refresh token is stateful. Firebase stores it. When you call signOut(), the SDK deletes the refresh token from the client, and Firebase can also revoke it server-side via the Admin SDK.
Automatic Token Refresh
The Firebase SDK handles refresh automatically. Before making an authenticated request, it checks if the ID token is expired. If it is, it sends the refresh token to:
POST https://securetoken.googleapis.com/v1/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=<token>
Firebase returns a new ID token. The SDK updates its internal state. The user stays logged in without any explicit code on your part.
You can also trigger a manual refresh:
const freshToken = await firebase.auth().currentUser.getIdToken(true);
The true argument forces a refresh even if the current token hasn’t expired yet.
What signOut() Actually Does
When you call signOut():
- The refresh token is deleted from the client (localStorage, cookie, or secure storage depending on platform).
- No new ID tokens can be issued.
- The current ID token remains technically valid until its 1-hour expiry.
The user is effectively logged out from that device. But if they have a session on another device, that other session keeps working until its own refresh token is used or its ID token expires.
Forced Logout with the Admin SDK
If you need to immediately invalidate all sessions for a user — for example, when you suspend their account or they report a breach — use the Admin SDK:
await admin.auth().revokeRefreshTokens(uid);
This updates the user’s tokensValidAfterTime timestamp in Firebase. Any refresh tokens issued before that timestamp are now rejected.
The catch: existing ID tokens still work until they expire (up to 1 hour). To close that window, you need to check tokensValidAfterTime on the server side:
const decodedToken = await admin.auth().verifyIdToken(idToken);
const user = await admin.auth().getUser(decodedToken.uid);
const tokensValidAfter = new Date(user.tokensValidAfterTime).getTime() / 1000;
if (decodedToken.iat < tokensValidAfter) {
throw new Error("Token revoked");
}
This adds a database lookup per request, but it’s the only way to enforce immediate revocation.
Cross-Tab Logout
Firebase’s SDK does not natively broadcast logout across tabs. If the user signs out in Tab A, Tab B still has the old ID token in memory and thinks the user is logged in.
A simple fix using the storage event:
// When logging out, write a signal
localStorage.setItem("logout", Date.now().toString());
// In every tab, listen for that signal
window.addEventListener("storage", (event) => {
if (event.key === "logout") {
firebase.auth().signOut();
window.location.href = "/login";
}
});
The storage event only fires in other tabs, not the one that made the change. So Tab A calls signOut() normally and sets the flag; Tab B picks up the event and signs out too.
Front-Channel vs. Back-Channel Logout
OIDC defines two ways to propagate logout across apps:
- Front-channel logout: The identity provider embeds logout URLs in iframes in the user’s browser. Only works in the browser that initiated the logout; does nothing for other devices.
- Back-channel logout: The identity provider sends a server-to-server POST to each app’s logout endpoint. Works regardless of the browser and reaches all devices — but requires your server to expose and handle a secure endpoint.
Firebase does not natively implement either. You need to build your own session revocation layer (like the Admin SDK approach above) if you need cross-device forced logout.
The Practical Summary
For most apps, Firebase’s automatic token management is enough:
- ID tokens expire in 1 hour.
- The SDK refreshes them silently.
signOut()clears the client session.
If you need stronger guarantees:
- Use
revokeRefreshTokens()for immediate revocation server-side. - Pair it with an
iatcheck againsttokensValidAfterTimeto handle the 1-hour window. - Use the
storageevent for cross-tab logout.
If you need cross-device forced logout, you need a centralized session store on your backend — Firebase alone does not give you that.