jwt
Decode Firebase JWT Tokens — Step by Step
What is a Firebase ID token?
When a user signs in through Firebase Authentication — with email/password, Google, GitHub, or any other provider — Firebase issues a short-lived JWT called an ID token. This token is a RS256-signed JSON Web Token that identifies the user and encodes their authentication state. Your backend uses this token to verify who the user is before processing API requests.
Firebase ID tokens expire after one hour. The Firebase client SDK automatically refreshes them using a long-lived refresh token stored in the browser or mobile keystore. If your backend receives an expired ID token, it will reject it with a 401, which triggers the client to fetch a fresh token and retry.
Firebase ID tokens are different from Firebase custom tokens and refresh tokens. This guide covers ID tokens — the JWTs you send in Authorization headers to your API. Custom tokens are used to authenticate with the Firebase SDK itself.
Firebase token structure
A Firebase ID token has the standard three-part JWT structure: header.payload.signature. The header specifies the algorithm (always RS256 for Firebase) and a key ID (kid) that identifies which of Firebase's rotating public keys was used to sign this token.
The payload contains the standard JWT claims plus Firebase-specific extensions. Here is a representative Firebase ID token payload after decoding:
{
"iss": "https://securetoken.google.com/your-project-id",
"aud": "your-project-id",
"auth_time": 1716825600,
"user_id": "abc123xyz456",
"sub": "abc123xyz456",
"iat": 1716825600,
"exp": 1716829200,
"email": "user@example.com",
"email_verified": true,
"firebase": {
"identities": {
"google.com": ["109876543210987654321"],
"email": ["user@example.com"]
},
"sign_in_provider": "google.com"
}
}Key claims to understand
Each claim in the Firebase token serves a specific purpose. Knowing what each field means helps you debug authentication problems quickly:
- sub / user_id — the unique Firebase UID for this user; both fields contain the same value; use sub for consistency across providers
- email — the user's email address; only present if the user has an email in their Firebase profile
- email_verified — boolean; always verify this if your app requires confirmed emails
- iss — issuer URL; must match https://securetoken.google.com/<your-project-id>; mismatch indicates a wrong-environment token
- aud — audience; must match your Firebase project ID; prevents tokens from one project being used against another
- exp — Unix timestamp when the token expires; compare to Date.now() / 1000
- iat — Unix timestamp when the token was issued; useful for detecting tokens issued before a password reset or session revocation
- firebase.sign_in_provider — which provider the user used to sign in: 'password', 'google.com', 'github.com', etc.
Getting the token in your app
To inspect a Firebase ID token during development, retrieve it from the currently signed-in user using the Firebase client SDK:
import { getAuth } from 'firebase/auth';
async function getIdToken(forceRefresh = false) {
const auth = getAuth();
const user = auth.currentUser;
if (!user) {
console.log('No user signed in');
return null;
}
// forceRefresh: true → always fetch a fresh token from Firebase
// forceRefresh: false → return the cached token if it has more than 5 min remaining
const token = await user.getIdToken(forceRefresh);
console.log('Firebase ID token:', token);
// Paste this token into Just Formatter JWT Decoder to inspect it
return token;
}
// In a browser console on your app page:
// getAuth().currentUser?.getIdToken().then(t => console.log(t))Once you have the token string, you can paste it into any JWT decoder to inspect its claims without needing the Firebase Admin SDK.
Decoding a Firebase token manually
For debugging purposes — checking claims, verifying expiry, confirming the project ID in aud — you can decode a Firebase token without the Admin SDK using the same base64url decoding technique that applies to any JWT:
function decodeFirebaseToken(idToken) {
const [, payloadB64] = idToken.split('.');
const padded = payloadB64.replace(/-/g, '+').replace(/_/g, '/');
const pad = (4 - padded.length % 4) % 4;
const json = atob(padded + '='.repeat(pad));
const payload = JSON.parse(json);
return {
uid: payload.sub,
email: payload.email ?? null,
emailVerified: payload.email_verified ?? false,
displayName: payload.name ?? null,
provider: payload.firebase?.sign_in_provider ?? 'unknown',
projectId: payload.aud,
issuedAt: new Date(payload.iat * 1000).toISOString(),
expiresAt: new Date(payload.exp * 1000).toISOString(),
isExpired: Math.floor(Date.now() / 1000) > payload.exp,
rawClaims: payload,
};
}This function decodes without verifying the signature — it is for debugging and inspection only. In your backend, always use the Firebase Admin SDK or the firebase-admin verifyIdToken() method to verify the signature and claims before trusting the token.
Common Firebase token debugging scenarios
Three situations account for the majority of Firebase auth bugs developers investigate with a JWT decoder:
- Token expired — check exp claim; if isExpired is true, the client-side SDK should have refreshed it automatically; investigate why it did not (network failure, sign-out race condition)
- Wrong project — aud does not match the project ID your backend is configured for; common when mixing staging and production Firebase projects in the same app
- Email not verified — email_verified is false but your backend rejects unverified emails; confirm the user completed the verification email flow
For each scenario, paste the raw token into Just Formatter's JWT Decoder. The decoder shows the exp claim as a human-readable timestamp (e.g., 'Expires: 2026-05-27 15:00:00 UTC — 47 minutes ago') which immediately tells you whether the token is valid or expired without any calculation.
Verifying Firebase tokens in your backend
For production backend verification, use the Firebase Admin SDK. It fetches Firebase's public keys automatically, caches them, handles key rotation, and validates all required claims (iss, aud, exp, iat). Manual verification is appropriate for edge environments where the Admin SDK cannot run.
// Node.js backend with firebase-admin
import { initializeApp, cert } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
initializeApp({ credential: cert(serviceAccount) });
async function verifyIdToken(idToken) {
try {
const decoded = await getAuth().verifyIdToken(idToken);
// decoded.uid, decoded.email, decoded.email_verified, etc.
return decoded;
} catch (err) {
if (err.code === 'auth/id-token-expired') {
throw new Error('Token expired — client should refresh and retry');
}
if (err.code === 'auth/argument-error') {
throw new Error('Malformed token — not a valid JWT');
}
throw err;
}
}The Admin SDK's verifyIdToken automatically checks the signature, expiry, issuer, and audience. It also handles Firebase's public key rotation, which happens every few hours — a detail that would require extra implementation work in a manual verifier.
