This error occurs when verifying a Firebase ID token that has been revoked due to account changes like password resets or explicit token revocation. Fix by forcing a token refresh or re-authenticating the user.
The `auth/id-token-revoked` error is thrown by Firebase Admin SDK when attempting to verify an ID token that has been invalidated. This happens when you call `verifyIdToken()` with the `checkRevoked: true` option and the token has been marked as revoked by Firebase. Firebase automatically revokes ID tokens when critical account changes occur, such as password updates, email address changes, or when an administrator explicitly calls `revokeRefreshTokens()` on the user's account. While ID tokens are stateless JWTs that can't be directly revoked, Firebase tracks revocation by comparing the token's issue time against a `tokensValidAfterTime` timestamp stored for each user. The revocation check requires an additional network call to Firebase servers to verify the token's status against the user's current revocation timestamp. Without this check, a structurally valid ID token would still be accepted even after being revoked, which could pose security risks in scenarios involving compromised credentials or required session termination.
Check your error logs to confirm you're receiving the specific Firebase error code:
// Node.js Admin SDK example
const admin = require('firebase-admin');
try {
const decodedToken = await admin.auth().verifyIdToken(
idToken,
true // checkRevoked = true
);
console.log('Token valid for user:', decodedToken.uid);
} catch (error) {
if (error.code === 'auth/id-token-revoked') {
console.log('ID token has been revoked');
// Handle revoked token
}
}# Python Admin SDK example
from firebase_admin import auth
try:
decoded_token = auth.verify_id_token(id_token, check_revoked=True)
print('Token valid for user:', decoded_token['uid'])
except auth.RevokedIdTokenError:
print('ID token has been revoked')
# Handle revoked tokenIf you see this specific error code, the token has been invalidated by Firebase.
Request a new ID token from Firebase Authentication by forcing a refresh:
// JavaScript/Web SDK
import { getAuth } from 'firebase/auth';
const auth = getAuth();
const user = auth.currentUser;
if (user) {
try {
// Force token refresh
const newIdToken = await user.getIdToken(true); // true = force refresh
console.log('New token obtained:', newIdToken);
// Retry your API request with the new token
const response = await fetch('/api/protected', {
headers: {
'Authorization': `Bearer ${newIdToken}`
}
});
} catch (error) {
if (error.code === 'auth/user-token-expired') {
// Token can't be refreshed - requires re-authentication
console.log('Refresh token also revoked, need to re-authenticate');
}
}
}// iOS Swift
Auth.auth().currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
if let error = error {
print("Failed to refresh token:", error.localizedDescription)
// Requires re-authentication
return
}
if let idToken = idToken {
print("New token obtained:", idToken)
// Use new token for API requests
}
}If the refresh token was also revoked, this will fail and require full re-authentication.
If token refresh fails, sign the user out and prompt them to log in again:
// JavaScript/Web SDK
import { getAuth, signOut } from 'firebase/auth';
async function handleRevokedToken() {
const auth = getAuth();
try {
// Attempt to refresh token
const newToken = await auth.currentUser.getIdToken(true);
return newToken;
} catch (error) {
// If refresh fails, sign out and redirect to login
await signOut(auth);
// Clear any local storage
localStorage.removeItem('userSession');
// Redirect to login with message
window.location.href = '/login?reason=token_revoked';
}
}// Server-side response (Express)
app.use(async (req, res, next) => {
try {
const idToken = req.headers.authorization?.split('Bearer ')[1];
const decodedToken = await admin.auth().verifyIdToken(idToken, true);
req.user = decodedToken;
next();
} catch (error) {
if (error.code === 'auth/id-token-revoked') {
return res.status(401).json({
error: 'TOKEN_REVOKED',
message: 'Your session has been revoked. Please sign in again.',
requiresReauth: true
});
}
next(error);
}
});Show a user-friendly message explaining they need to log in again due to security or account changes.
Set up automatic token refresh and re-authentication flow in your application:
// React example with automatic retry
import { useEffect, useState } from 'react';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
function useAuthToken() {
const [token, setToken] = useState(null);
const [error, setError] = useState(null);
const auth = getAuth();
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (user) => {
if (user) {
try {
const idToken = await user.getIdToken(true);
setToken(idToken);
} catch (err) {
setError(err);
// Redirect to login if token can't be obtained
window.location.href = '/login?session_expired=true';
}
} else {
setToken(null);
}
});
return () => unsubscribe();
}, [auth]);
return { token, error };
}// API interceptor example (Axios)
import axios from 'axios';
import { getAuth } from 'firebase/auth';
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 &&
error.response?.data?.error === 'TOKEN_REVOKED') {
const auth = getAuth();
const user = auth.currentUser;
if (user) {
try {
// Try to get new token
const newToken = await user.getIdToken(true);
// Retry original request with new token
error.config.headers.Authorization = `Bearer ${newToken}`;
return axios.request(error.config);
} catch (refreshError) {
// Refresh failed, redirect to login
await auth.signOut();
window.location.href = '/login';
}
}
}
return Promise.reject(error);
}
);This provides a seamless experience by attempting automatic recovery before forcing re-authentication.
Consider the security vs. performance trade-offs of enabling revocation checks:
// Without revocation check (faster, less secure)
const decodedToken = await admin.auth().verifyIdToken(idToken);
// With revocation check (slower, more secure)
const decodedToken = await admin.auth().verifyIdToken(idToken, true);When to use checkRevoked: true
- High-security operations (financial transactions, admin actions)
- After password reset or account changes
- When tokens have been explicitly revoked
- Infrequent authentication checks (login, sensitive operations)
- Banking, healthcare, or other regulated applications
When to skip it:
- High-traffic public endpoints checked on every request
- Low-risk operations where latency matters
- Applications with short-lived ID tokens (< 5 minutes)
- Read-only operations on non-sensitive data
// Hybrid approach based on operation type
async function verifyToken(idToken, operationType) {
const checkRevoked = [
'payment',
'settings_change',
'admin_action',
'delete_account'
].includes(operationType);
return await admin.auth().verifyIdToken(idToken, checkRevoked);
}The revocation check adds a network call to Firebase (typically 50-200ms latency), so use it judiciously based on your security requirements.
Understanding ID Token vs Refresh Token Revocation:
ID tokens are stateless JWTs that can't be truly revoked since Firebase doesn't maintain a server-side session. When you call revokeRefreshTokens(uid), Firebase updates the user's tokensValidAfterTime field. Existing ID tokens remain structurally valid until expiration (typically 1 hour), but the checkRevoked: true option compares the token's iat (issued at) claim against this timestamp.
// What happens during revocation check
{
iat: 1640000000, // Token issued at timestamp
// Firebase compares: iat < user.tokensValidAfterTime
// If true, token is considered revoked
}Multi-Device Session Management:
When a user changes their password on one device, all other devices will encounter auth/id-token-revoked on their next verification (if using checkRevoked: true). Design graceful handling:
// Server-side revocation monitoring
app.post('/api/sensitive-operation', async (req, res) => {
try {
const decoded = await admin.auth().verifyIdToken(
req.headers.authorization.split('Bearer ')[1],
true // Always check revocation for sensitive ops
);
// Proceed with operation
await performSensitiveOperation(decoded.uid);
res.json({ success: true });
} catch (error) {
if (error.code === 'auth/id-token-revoked') {
// Log for security monitoring
logger.warn('Revoked token used', {
uid: error.message,
ip: req.ip,
endpoint: req.path
});
return res.status(401).json({
code: 'TOKEN_REVOKED',
message: 'Your credentials were recently changed. Please sign in again.',
action: 'REAUTHENTICATE'
});
}
throw error;
}
});Performance Optimization with Caching:
For high-traffic applications, implement periodic revocation checks instead of on every request:
// Redis-backed revocation cache
const redis = require('redis').createClient();
async function verifyWithCache(idToken, uid) {
const cacheKey = `token_verified:${uid}`;
const lastCheck = await redis.get(cacheKey);
const now = Date.now();
// Check revocation at most once per minute
const shouldCheckRevoked = !lastCheck || (now - lastCheck) > 60000;
const decoded = await admin.auth().verifyIdToken(
idToken,
shouldCheckRevoked
);
if (shouldCheckRevoked) {
await redis.setex(cacheKey, 60, now); // Cache for 60 seconds
}
return decoded;
}Security Event Monitoring:
Track revocation events for security analytics:
// Monitor suspicious revocation patterns
async function trackRevocationEvent(uid, context) {
await analytics.track({
event: 'token_revoked',
userId: uid,
properties: {
reason: context.reason, // password_change, manual_revoke, etc.
affectedDevices: context.deviceCount,
timestamp: new Date()
}
});
// Alert if unusual pattern detected
const recentRevocations = await getRecentRevocations(uid);
if (recentRevocations.length > 3) {
await securityAlert({
level: 'HIGH',
message: 'Multiple token revocations detected',
userId: uid
});
}
}Handling Revocation During Active Sessions:
If you need to immediately terminate active sessions, combine token revocation with real-time notifications:
// Real-time session termination
import { getFirestore } from 'firebase-admin/firestore';
async function forceLogoutAllDevices(uid) {
// Revoke all tokens
await admin.auth().revokeRefreshTokens(uid);
// Notify active sessions via Firestore
await getFirestore()
.collection('userSessions')
.doc(uid)
.set({
forceLogout: true,
timestamp: Date.now()
});
}
// Client-side listener
const db = getFirestore();
const userSessionRef = doc(db, 'userSessions', currentUser.uid);
onSnapshot(userSessionRef, (snapshot) => {
if (snapshot.data()?.forceLogout) {
// Immediately sign out
signOut(auth);
window.location.href = '/login?reason=forced_logout';
}
});Callable Functions: INTERNAL - Unhandled exception
How to fix "Callable Functions: INTERNAL - Unhandled exception" in Firebase
auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options
How to fix "auth/invalid-hash-algorithm: Hash algorithm doesn't match supported options" in Firebase
Hosting: CORS configuration not set up properly
How to fix CORS configuration in Firebase Hosting
auth/reserved-claims: Custom claims use reserved OIDC claim names
How to fix "reserved claims" error when setting custom claims in Firebase
Callable Functions: UNAUTHENTICATED - Invalid credentials
How to fix "UNAUTHENTICATED - Invalid credentials" in Firebase Callable Functions