Firebase Custom Claims
Custom claims let you attach extra information to a user's authentication token. This information travels with every request the user makes to Firebase services. You use claims to implement roles — like admin, moderator, or premium user — and then enforce those roles in Firestore security rules or your server code.
Claims as a VIP Badge
Imagine a music festival with multiple zones: general admission, backstage, and VIP lounge. Everyone gets a wristband at entry. VIP guests get a wristband that also has a gold star printed on it. Security at each zone checks the wristband color and whether the gold star is present.
Custom claims work the same way. The ID token is the wristband. Custom claims are additional markings on the wristband. Every Firebase service that checks the token can read those markings without calling the database to verify the user's role.
User's ID Token with Custom Claims:
{
uid: "abc123",
email: "alice@example.com",
email_verified: true,
// --- Custom Claims ---
admin: true,
role: "editor",
premiumUser: true
}
Setting Custom Claims — Server Side Only
You set custom claims using the Firebase Admin SDK on a trusted server. You cannot set claims from browser code — this is intentional. If users could set their own claims, anyone could give themselves admin access.
Cloud Functions are the most common place to set claims:
// Cloud Function that sets admin claim
const { onCall } = require("firebase-functions/v2/https");
const admin = require("firebase-admin");
admin.initializeApp();
exports.setAdminClaim = onCall(async (request) => {
// Only allow existing admins to promote others
if (!request.auth.token.admin) {
throw new Error("Only admins can promote users.");
}
const { targetUid } = request.data;
// Set the admin claim on the target user
await admin.auth().setCustomUserClaims(targetUid, { admin: true });
return { message: "Admin claim set for user: " + targetUid };
});
Setting Claims Directly from Admin SDK
// In a Node.js script or Cloud Function
const admin = require("firebase-admin");
async function makeUserAdmin(uid) {
await admin.auth().setCustomUserClaims(uid, {
admin: true,
role: "superuser"
});
console.log("Claims set for:", uid);
}
// Example: make a specific user an admin
makeUserAdmin("abc123xyz");
Claims are key-value pairs. Values can be booleans, strings, numbers, or arrays. The total size of all custom claims combined must stay under 1000 bytes.
Reading Claims in the Browser
After claims are set on the server, the client needs to force-refresh its ID token to receive the new claims. Tokens cache for one hour by default.
import { auth } from "./firebase";
async function getClaimsFromToken() {
const user = auth.currentUser;
if (!user) return;
// Force token refresh to get latest claims
const token = await user.getIdTokenResult(true);
console.log("Claims:", token.claims);
if (token.claims.admin) {
console.log("This user is an admin.");
showAdminPanel();
} else {
console.log("Regular user.");
}
}
The true argument in getIdTokenResult(true) forces a fresh token fetch from Firebase instead of using the cached version. Always force refresh after setting new claims so the user sees the effect immediately.
Enforcing Claims in Firestore Security Rules
Custom claims appear in the request.auth.token object inside Firestore security rules. This lets you restrict database access based on roles without an extra database lookup:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Only admins can read the admin panel data
match /adminData/{docId} {
allow read, write: if request.auth.token.admin == true;
}
// Only premium users can access premium content
match /premiumContent/{docId} {
allow read: if request.auth.token.premiumUser == true;
}
// Regular authenticated users can read public posts
match /posts/{postId} {
allow read: if request.auth != null;
}
}
}
Enforcing Claims in Cloud Functions
Inside a Cloud Function, read claims from the request's auth token:
const { onCall } = require("firebase-functions/v2/https");
exports.deleteAllPosts = onCall(async (request) => {
// Check if the caller has the admin claim
if (!request.auth || !request.auth.token.admin) {
throw new Error("Only admins can delete all posts.");
}
// Proceed with admin-only operation
const db = admin.firestore();
const posts = await db.collection("posts").get();
const batch = db.batch();
posts.forEach((doc) => batch.delete(doc.ref));
await batch.commit();
return { deleted: posts.size };
});
Removing or Updating Claims
To remove a claim, set it to null. To update a claim, call setCustomUserClaims again with the new values. Note that calling setCustomUserClaims replaces all existing claims with the new object you provide — include all claims you want to keep.
// Replace all claims — user is no longer admin, but keeps editor role
await admin.auth().setCustomUserClaims(uid, {
admin: false,
role: "editor"
});
// Remove all custom claims
await admin.auth().setCustomUserClaims(uid, null);
Viewing User Claims in the Console
The Firebase Console does not display custom claims in the Users table. To view a user's current claims, use the Admin SDK:
const userRecord = await admin.auth().getUser(uid);
console.log("Custom claims:", userRecord.customClaims);
Key Takeaway
Custom claims attach role and permission data directly to a user's authentication token. Set them server-side with the Admin SDK — never from browser code. Read them in the browser with getIdTokenResult after a forced token refresh. Enforce them in Firestore security rules using request.auth.token.yourClaimName. Custom claims are the most efficient way to implement role-based access control in Firebase because they travel with the token rather than requiring a separate database lookup on every request.
