This error occurs when a Firebase ID token contains invalid claim values, typically a mismatched "aud" (audience) or "iss" (issuer) claim. Fix it by ensuring tokens come from the same Firebase project and that your service account configuration matches your client configuration.
This error is thrown by Firebase Admin SDK when verifying an ID token that contains invalid or mismatched claims. Specifically, it most commonly occurs when the "aud" (audience) claim in the token doesn't match the expected Firebase project ID, or the "iss" (issuer) claim doesn't match the expected token issuer. Firebase ID tokens are JWT tokens that contain standard OpenID Connect (OIDC) claims including: - `aud`: The audience (your Firebase project ID) - `iss`: The issuer (Firebase's secure token server) - `sub`: The subject (the user ID) - `iat`: Issued at time - `exp`: Expiration time When any of these claims are invalid, malformed, or don't match expectations, Firebase rejects the token.
Ensure your Admin SDK, client SDK, and service account are all configured for the same Firebase project.
Check your Admin SDK initialization:
import { initializeApp, cert } from 'firebase-admin/app';
// This service account MUST be from the correct Firebase project
const serviceAccount = require('./path/to/serviceAccountKey.json');
// Verify the project_id in the service account
console.log('Service Account Project ID:', serviceAccount.project_id);
initializeApp({
credential: cert(serviceAccount),
projectId: serviceAccount.project_id // Explicit project ID
});Check your client SDK configuration (web):
import { initializeApp } from 'firebase/app';
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT.firebaseapp.com",
projectId: "YOUR_PROJECT_ID", // Must match Admin SDK
storageBucket: "YOUR_PROJECT.appspot.com",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
// Verify the project ID matches
console.log('Client Project ID:', firebaseConfig.projectId);Confirm they match:
# Service account project_id
grep project_id serviceAccountKey.json
# Client config projectId
grep projectId src/firebase-config.tsBoth should show the same project ID (e.g., "my-app-12345").
Decode and inspect the token to see which project issued it.
Inspect token claims:
import { getAuth } from 'firebase-admin/auth';
import jwt_decode from 'jwt-decode';
// Get token from client
const idToken = await user.getIdToken();
// Decode token (don't verify yet)
const decoded = jwt_decode<any>(idToken);
console.log('Token Claims:');
console.log(' aud (audience/project):', decoded.aud);
console.log(' iss (issuer):', decoded.iss);
console.log(' sub (user):', decoded.sub);
console.log(' iat (issued at):', new Date(decoded.iat * 1000));
console.log(' exp (expires):', new Date(decoded.exp * 1000));Expected values for project "my-app-12345":
{
aud: "my-app-12345", // Audience should be your project ID
iss: "https://securetoken.google.com/my-app-12345", // Issuer should be Google's token server for your project
sub: "user_uid_here",
iat: 1672531200,
exp: 1672534800
}If the `aud` or `iss` don't match your project, the token is from the wrong Firebase project.
If the token comes from a different project, clear cached credentials and sign in again.
Web/JavaScript:
import { getAuth, signOut } from 'firebase/auth';
const auth = getAuth();
// Sign out to clear cached tokens
await signOut(auth);
// User must sign in again with correct project
console.log('User signed out. They must sign in again.');iOS (Swift):
import FirebaseAuth
// Check current app configuration
if let app = FirebaseApp.app() {
print("Current Firebase App Name:", app.name)
}
// Clear keychain by signing out
do {
try Auth.auth().signOut()
print("User signed out and token cleared")
} catch let signOutError as NSError {
print("Error signing out:", signOutError.code)
}Android (Kotlin):
import com.google.firebase.auth.FirebaseAuth
val auth = FirebaseAuth.getInstance()
auth.signOut()
// Clear credential cache
FirebaseAuth.getInstance().currentUser = nullAfter clearing tokens, users will be asked to authenticate again, and new tokens from the correct project will be generated.
The service account you use for Admin SDK initialization must be from the same Firebase project.
Verify service account source:
# Download service account from Firebase Console
# Firebase Console > Project Settings > Service Accounts > Generate New Private Key
# Check the project_id in the downloaded file
cat serviceAccountKey.json | jq '.project_id'
# Should output: "my-app-12345"Proper Admin SDK initialization:
import { initializeApp, cert } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
// Load the correct service account
const serviceAccountKey = JSON.parse(
process.env.FIREBASE_SERVICE_ACCOUNT_KEY || '{}'
);
// Initialize with explicit project ID
const app = initializeApp({
credential: cert(serviceAccountKey),
projectId: serviceAccountKey.project_id,
databaseURL: `https://${serviceAccountKey.project_id}.firebaseio.com`
});
const auth = getAuth(app);
export { auth };Never use the wrong service account:
// ❌ WRONG - Using service account from different project
const wrongServiceAccount = require('./staging-serviceAccountKey.json');
initializeApp({
credential: cert(wrongServiceAccount) // This is from staging, not production
});
// ✅ CORRECT - Using service account from same project
const correctServiceAccount = require('./production-serviceAccountKey.json');
initializeApp({
credential: cert(correctServiceAccount) // This is from production
});If you have multiple Firebase projects (staging, production), ensure tokens and credentials match the same environment.
Typical multi-environment setup:
// Environment-specific configuration
const firebaseConfig = process.env.NODE_ENV === 'production'
? {
projectId: 'my-app-production',
databaseURL: 'https://my-app-production.firebaseio.com'
}
: {
projectId: 'my-app-staging',
databaseURL: 'https://my-app-staging.firebaseio.com'
};
// Load matching service account
const serviceAccountPath = process.env.NODE_ENV === 'production'
? './serviceAccounts/production.json'
: './serviceAccounts/staging.json';
const serviceAccount = require(serviceAccountPath);
initializeApp({
credential: cert(serviceAccount),
projectId: firebaseConfig.projectId
});Client-side configuration should also match:
// src/firebase-config.ts
const firebaseConfig = process.env.REACT_APP_FIREBASE_PROJECT_ID === 'my-app-production'
? {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY_PROD,
authDomain: "my-app-production.firebaseapp.com",
projectId: "my-app-production",
storageBucket: "my-app-production.appspot.com"
}
: {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY_STAGING,
authDomain: "my-app-staging.firebaseapp.com",
projectId: "my-app-staging",
storageBucket: "my-app-staging.appspot.com"
};
export default firebaseConfig;Never mix service accounts and client configs from different projects.
Add error handling to diagnose claim mismatches and provide clearer error messages.
Implement token validation:
import { getAuth } from 'firebase-admin/auth';
import jwt_decode from 'jwt-decode';
async function verifyAndDecodedToken(idToken: string) {
try {
// Decode without verification first to inspect claims
const unverified = jwt_decode<any>(idToken);
console.log('Token Claims:');
console.log(' Project ID (aud):', unverified.aud);
console.log(' Issuer (iss):', unverified.iss);
console.log(' Expected Project:', process.env.FIREBASE_PROJECT_ID);
// Check if project matches
if (unverified.aud !== process.env.FIREBASE_PROJECT_ID) {
throw new Error(
`Token from wrong project. Expected "${process.env.FIREBASE_PROJECT_ID}" but got "${unverified.aud}"`
);
}
// Now verify with Admin SDK
const decodedToken = await getAuth().verifyIdToken(idToken);
return decodedToken;
} catch (error: any) {
if (error.code === 'auth/invalid-argument') {
console.error('Invalid token claim - project mismatch');
console.error('Error details:', error.message);
// Log for debugging
throw new Error('Authentication failed: token from different Firebase project');
}
throw error;
}
}
// Usage
const uid = await verifyAndDecodedToken(clientToken)
.catch(error => {
console.error('Verification failed:', error.message);
return null;
});Better error messages for users:
async function authenticateRequest(req: Request) {
const idToken = extractTokenFromHeader(req);
try {
const decodedToken = await getAuth().verifyIdToken(idToken);
return { success: true, uid: decodedToken.uid };
} catch (error: any) {
if (error.code === 'auth/invalid-argument') {
return {
success: false,
error: 'Your authentication token is not valid for this service. Please sign in again.',
debug: error.message
};
}
throw error;
}
}Firebase Project Migration:
If you're migrating from one Firebase project to another, this error is common during the transition. Best practice:
1. Keep both projects running in parallel
2. On the client, gradually redirect users to authenticate with the new project
3. Invalidate old tokens from the old project
4. Migrate user data from old to new project
5. Once all users are on the new project, decommission the old project
async function migrateUserToken(oldToken: string) {
try {
// Try to verify with old project
const oldAuth = getAuth(oldFirebaseApp);
const oldDecoded = await oldAuth.verifyIdToken(oldToken);
// Get fresh token from new project
const user = await getAuth(newFirebaseApp).getUser(oldDecoded.uid);
const newToken = await user.getIdToken();
return newToken;
} catch (error) {
// If verification fails, user must re-authenticate
console.error('Token migration failed, user must sign in again');
return null;
}
}Testing with Firebase Emulator:
When using Firebase Emulator Suite locally, the issuer claim is different. Configure your Admin SDK to use the emulator:
import { initializeApp, cert } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
if (process.env.NODE_ENV === 'development') {
// Point to emulator
process.env.FIREBASE_AUTH_EMULATOR_HOST = 'localhost:9099';
}
const app = initializeApp({
projectId: 'demo-project'
});Cross-tenant authentication:
Firebase Auth supports multi-tenancy. If using tenants, ensure tenant configuration is correct:
import { getTenant } from 'firebase-admin/auth';
// Verify token for specific tenant
const tenantAuth = getTenant('tenant_id');
const decodedToken = await tenantAuth.verifyIdToken(idToken);Common claim values:
Understanding what valid claims look like helps diagnose issues:
- aud: Always a Firebase project ID (e.g., "my-app-12345")
- iss: Always "https://securetoken.google.com/{project_id}"
- auth_time: Seconds since epoch when user authenticated
- user_id: Same as sub for Firebase Auth
- firebase.identities: Which sign-in providers were used
- firebase.sign_in_provider: The primary sign-in method
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