Diagnosing Firebase 401 / invalid_token Errors When Verifying ID Tokens
When a backend API rejects Firebase Auth ID tokens with 401 or invalid_token, the problem often lies in token expiration, clock skew, project mismatch, or revocation. This guide walks through a structured diagnostic flow, shows how to inspect the token, and lists fixes and escalation steps.
27 Nov 2025, 06:36 UTC

Problem Statement
Clients sign in with Firebase Auth and send the Authorization: Bearer <idToken> header on every API call. The server, however, returns 401 Unauthorized or invalid_token even though the user is visibly signed‑in on the app. The token reaches the backend, but the verification step fails.
Common Causes
| Cause | Typical Error Code | What It Looks Like |
|---|---|---|
| Expired token | auth/id-token-expired | Client’s clock is correct but the token’s exp claim is in the past. |
| Clock skew | auth/id-token-expired | Server’s clock is behind the client; the token appears valid but the server rejects it. |
| Wrong project or tenant | auth/id-token-claims-mismatch | Token’s aud or iss does not match the backend’s projectId (or tenantId). |
| Revoked session | auth/id-token-revoked | Admin SDK detects the token was revoked via revokeRefreshTokens. |
| Malformed or missing header | auth/invalid-argument | Header missing or not prefixed with Bearer. |
| Admin SDK mis‑configured | auth/invalid-argument | SDK initialized with wrong projectId or without tenantId in a multi‑tenant setup. |
Diagnostic Checklist
- Verify the header reaches the service. In the API log, capture the raw
Authorizationheader. Example (Node.js/Express):app.use((req, res, next) => { console.log('Authorization header:', req.headers.authorization); next(); }); - Decode the JWT payload (without verifying). Use
jwt-decodeor a similar library to inspect the claims.
Checkconst decoded = jwtDecode(token); console.log(decoded);aud,iss,exp,auth_time, andtenant_id. - Compare
expto server UTC time. Compute the difference:
Ifconst exp = decoded.exp * 1000; // ms const now = Date.now(); console.log('Token age (ms):', now - exp);now - expis negative and larger than a few minutes, clock skew is likely. - Check project/tenant alignment. In the Firebase console, note the Project ID (and Tenant ID if using Identity Platform). Ensure the Admin SDK is initialized with the same values:
admin.initializeApp({ credential: admin.credential.applicationDefault(), projectId: 'your-project-id', tenantId: 'your-tenant-id', // optional }); - Run
verifyIdTokenwithcheckRevoked=true. Capture the thrown error code.try { const decoded = await admin.auth().verifyIdToken(token, true); } catch (e) { console.error('verifyIdToken error:', e.code, e.message); } - Inspect any network intermediaries. If an LB or WAF strips or rewrites the header, the token may be lost before reaching the backend.
Fixes Tied to Findings
- auth/id-token-expired – Client should call
getIdToken(true)to force a refresh and retry once. Example in JavaScript:firebase.auth().currentUser.getIdToken(true).then((newToken) => { // send newToken in Authorization header }); - Clock skew – Synchronize the server clock via NTP or use a managed service that keeps time accurate. Verify after adjustment.
- auth/id-token-claims-mismatch – Update the backend’s
projectIdand, for multi‑tenant apps, supply the correcttenantIdin the Admin SDK initialization. - auth/id-token-revoked – Instruct the client to sign out and re‑authenticate. Optionally, send a 401 with a custom body indicating revocation.
- Malformed header – Add a pre‑processing step that rejects requests lacking a proper
Authorization: Bearerheader before callingverifyIdToken. - Admin SDK mis‑configured – Re‑initialize the SDK with the correct credentials and project settings. In production, use
applicationDefault()and verify the environment variableGOOGLE_APPLICATION_CREDENTIALS.
Escalation Criteria
- Tokens verify correctly in a local test script but fail in production → investigate network or environment differences.
- Load balancer or WAF logs show header stripping or rewriting → coordinate with infrastructure team.
- Persistent clock drift after NTP correction → raise a platform outage ticket.
- Unresolved revocation or tenant mismatch after code changes → involve the identity‑platform owner or Firebase support.
Practical Engineering Decision
Treat ID tokens as short‑lived credentials (typically 1 hour). Verify them on every request in a central middleware. This guarantees that the backend only accepts fresh, signed tokens and that any token‑related error maps to a consistent HTTP status (401) and error message. Avoid trusting client‑side claims or caching verification results beyond the token’s lifetime.
Example Middleware (Node.js/Express)
const admin = require('firebase-admin');
const jwtDecode = require('jwt-decode');
admin.initializeApp({
credential: admin.credential.applicationDefault(),
projectId: process.env.FIREBASE_PROJECT_ID,
tenantId: process.env.FIREBASE_TENANT_ID, // optional
});
async function firebaseAuthMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({error: 'missing_token'});
}
const idToken = authHeader.split(' ')[1];
// Quick sanity check: decode payload for aud/iss
const decoded = jwtDecode(idToken);
if (decoded.aud !== process.env.FIREBASE_PROJECT_ID) {
return res.status(401).json({error: 'aud_mismatch'});
}
if (decoded.tenant_id && decoded.tenant_id !== process.env.FIREBASE_TENANT_ID) {
return res.status(401).json({error: 'tenant_mismatch'});
}
try {
const verified = await admin.auth().verifyIdToken(idToken, true);
req.user = verified; // attach to request
next();
} catch (e) {
const code = e.code || 'auth/unknown';
return res.status(401).json({error: code});
}
}
module.exports = firebaseAuthMiddleware;
Verification Checklist
- Run the middleware against a freshly minted token in a local dev environment.
- Confirm the server’s system clock is within 5 seconds of an NTP source.
- Check that the
projectIdand (if applicable)tenantIdmatch those in the Firebase console. - Force a token revocation (via the Firebase console) and ensure the API returns
auth/id-token-revoked. - Simulate a header rewrite by an LB and verify that the middleware rejects the request.
Limitations & Caveats
- Decoding a JWT without verifying its signature is not a security check; it is only for diagnostics.
- Firebase client SDKs rotate ID tokens roughly every hour. A backend that accepts stale tokens may see intermittent 401s after rotation.
- Identity Platform (GCIP) multi‑tenant setups change the expected
issandaudvalues; single‑tenant assumptions break. - Admin SDK major releases may alter the default
checkRevokedbehavior or error code names. Pin the SDK version and test upgrades.
Conclusion
By following the ordered diagnostic steps—confirming the header, inspecting claims, checking clock sync, validating project/tenant alignment, and using verifyIdToken with revocation checks—you can pinpoint why a Firebase ID token is rejected. Apply the corresponding fix, and if the issue persists beyond code and environment changes, raise an escalation with the infrastructure or identity‑platform team. Centralizing token verification in middleware ensures a consistent, secure authentication flow for all backend endpoints.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.