Firebase custom claims cannot use OIDC reserved names like "iss", "sub", "aud", "exp", "iat", or Firebase reserved names. This error occurs when you try to set custom user claims with these reserved keywords.
Firebase Authentication tokens comply with the OpenID Connect (OIDC) JWT specification, which reserves certain claim names for standard use. Additionally, Firebase reserves its own claim names for internal functionality. When you attempt to set custom user claims using any of these reserved names, Firebase rejects the operation with this error to prevent token conflicts and security issues. The reserved OIDC claims include identity claims like "iss" (issuer), "sub" (subject), and "aud" (audience), as well as temporal claims like "exp" (expiration), "iat" (issued at), and "auth_time". Using these names in custom claims would overwrite critical token metadata, breaking authentication and authorization logic.
The error message will specify which claim name is reserved. Note the exact name, as you'll need to either remove or rename it.
Common reserved OIDC claims:
- iss (issuer)
- sub (subject)
- aud (audience)
- exp (expiration)
- iat (issued at)
- auth_time (authentication time)
- nonce (prevents replay attacks)
- acr (authentication context)
- amr (authentication methods)
- azp (authorized party)
If the claim is not essential to your application logic, simply delete it from the object you're passing to setCustomUserClaims().
Example - WRONG:
const admin = require('firebase-admin');
admin.auth().setCustomUserClaims(uid, {
role: 'admin',
aud: 'my-app' // ❌ 'aud' is a reserved OIDC claim
})
.catch(error => console.log(error.message)); // auth/reserved-claimsExample - CORRECT:
const admin = require('firebase-admin');
admin.auth().setCustomUserClaims(uid, {
role: 'admin',
// ✅ 'aud' removed - audience is handled by Firebase
})
.then(() => console.log('Custom claims set'))
.catch(error => console.log(error));If you need to store data similar to a reserved claim, use a custom prefix or different naming convention.
Example - Use a custom namespace:
const admin = require('firebase-admin');
// Instead of 'iat' (issued at), use 'assigned_at'
// Instead of 'exp' (expires), use 'token_expires_at'
admin.auth().setCustomUserClaims(uid, {
role: 'admin',
permissions: ['read', 'write', 'delete'],
assigned_at: new Date().toISOString(),
token_expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
})
.then(() => console.log('Custom claims set'))
.catch(error => console.log(error));This approach lets you store similar data without conflicting with reserved OIDC claims.
While fixing the reserved names, ensure your entire custom claims payload doesn't exceed 1000 bytes. Firebase enforces this strict size limit.
const admin = require('firebase-admin');
const customClaims = {
role: 'admin',
permissions: ['read', 'write', 'delete'],
department: 'engineering',
level: 5,
};
// Check size (rough estimate - Firebase uses exact JSON serialization)
const size = JSON.stringify(customClaims).length;
console.log(`Claims size: ${size} bytes`); // Should be < 1000
if (size > 1000) {
console.warn('Custom claims exceed 1000 bytes!');
} else {
admin.auth().setCustomUserClaims(uid, customClaims)
.then(() => console.log('Custom claims set successfully'))
.catch(error => console.error('Error:', error));
}After successfully setting custom claims, the user's existing tokens won't be updated automatically. You need to force a token refresh on the client side.
// Client-side (Web SDK)
import { getAuth } from 'firebase/auth';
const auth = getAuth();
const currentUser = auth.currentUser;
if (currentUser) {
// Force refresh to get updated claims in token
currentUser.getIdToken(true)
.then(idToken => {
console.log('Token refreshed with new claims');
// Use the new token for API calls
})
.catch(error => console.error('Failed to refresh token:', error));
}// iOS
let user = Auth.auth().currentUser
user?.getIDTokenForcingRefresh(true) { idToken, error in
if let error = error {
print("Error refreshing token:", error)
return
}
print("Token refreshed with new claims")
}// Android
FirebaseAuth mAuth = FirebaseAuth.getInstance();
FirebaseUser user = mAuth.getCurrentUser();
user.getIdToken(true)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
String idToken = task.getResult().getToken();
Log.d("Auth", "Token refreshed with new claims");
} else {
Log.e("Auth", "Error refreshing token", task.getException());
}
});Custom claims should only be used for access control (role-based or attribute-based authorization), not for storing user data. Storing large amounts of data in custom claims increases your ID token size and can impact performance since tokens are sent with every authenticated request.
For role-based access control (RBAC), store role definitions in Firestore and only use custom claims for simple role identifiers (e.g., role: 'admin'). For attribute-based access control (ABAC), consider using a separate database table or Firestore collection to store user permissions, then query it at request time rather than embedding everything in custom claims.
The 1000-byte limit is a hard Firebase limit across all SDKs and platforms. Plan your claims structure accordingly, removing unnecessary fields and using short key names if needed.
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
Callable Functions: UNAUTHENTICATED - Invalid credentials
How to fix "UNAUTHENTICATED - Invalid credentials" in Firebase Callable Functions
messaging/message-rate-exceeded: Overall sending rate too high
Overall sending rate too high in Firebase Cloud Messaging