How to automatically refresh expired id token using Firebase Admin SDK #2471
Replies: 3 comments
-
|
add onIdTokenChanged event handler using auth instance the call this api route this will get triggered once the id token in the client expired and firebase explicitly creates a new one then make your fetch with the new idToken to always hava updated token |
Beta Was this translation helpful? Give feedback.
-
|
This issue is probably related to your question, but no answer yet |
Beta Was this translation helpful? Give feedback.
-
|
The Firebase Admin SDK cannot refresh an ID token server-side — that is by design, since the Admin SDK operates with elevated privileges and ID tokens are issued only to authenticated clients. The refresh must originate from the client. Here is the complete pattern for SvelteKit (works the same in Next.js or any SSR framework): Client side — listen for token refresh// src/lib/firebase/client.ts
import { getAuth, onIdTokenChanged } from 'firebase/auth';
export function startTokenSync() {
const auth = getAuth();
onIdTokenChanged(auth, async (user) => {
if (user) {
// Firebase auto-refreshes the token every ~55 minutes.
// onIdTokenChanged fires whenever the token is renewed.
const freshToken = await user.getIdToken();
// Send it to your server so it can update the session cookie.
await fetch('/api/session/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken: freshToken }),
});
} else {
// User signed out — clear the cookie on the server
await fetch('/api/session/logout', { method: 'POST' });
}
});
}Call Server side — refresh endpoint// src/routes/api/session/refresh/+server.ts
import { json } from '@sveltejs/kit';
import { adminAuth } from '$lib/firebase/admin';
export async function POST({ request, cookies }) {
const { idToken } = await request.json();
// Verify the new token is legitimate before issuing a new cookie
await adminAuth.verifyIdToken(idToken);
const expiresIn = 60 * 60 * 24 * 5 * 1000; // 5 days
const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn });
cookies.set('session', sessionCookie, {
path: '/',
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: expiresIn / 1000,
});
return json({ status: 'refreshed' });
}Middleware — handle the expired cookie gracefully// src/hooks.server.ts
import { adminAuth } from '$lib/firebase/admin';
export async function handle({ event, resolve }) {
const session = event.cookies.get('session');
if (!session) return resolve(event);
try {
const decoded = await adminAuth.verifySessionCookie(session, true);
event.locals.user = decoded;
} catch (err: any) {
if (err.code === 'auth/session-cookie-expired') {
// Cookie expired — clear it and let the client's onIdTokenChanged
// fire a refresh automatically once the page reloads.
event.cookies.delete('session', { path: '/' });
}
event.locals.user = null;
}
return resolve(event);
}Why this works without redirecting to loginFirebase clients automatically refresh ID tokens every ~55 minutes in the background. The key insight: the server cannot initiate a refresh, but it does not need to. The client always has a valid token as long as the Firebase session is alive; the server just needs a way to receive the new token when it arrives. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I have a SvelteKit app which uses Firebase Authentication. The auth flow is like this
/api/login/+server.tsI will verify the id token given above and set my own SvelteKit cookieThe reason I don't use onAuthStateChanged is it only works on the client and will make a flash between logged-out and logged-in user
+hooks.server.ts(a middleware file in SvelteKit) I will verify the id token on every request like thisThe problem I'm having is when
verifySessionCookiethrows an error ofauth/session-cookie-expiredI couldn't find any method to request a new id token on the server. In addition, I don't want to redirect the user to the login page which is annoying. How do I achieve this?Beta Was this translation helpful? Give feedback.
All reactions