How to Integrate Firebase Auth with Your Headless CMS
Connect Firebase Auth to your headless CMS so roles, custom claims, and member-only content stay in sync across web and mobile apps.
What is Firebase Auth?
Firebase Auth is Google Firebase's authentication service for signing in users with email and password, phone numbers, social providers, anonymous sessions, and custom auth systems. Teams use it in web, iOS, Android, Flutter, Unity, and server apps when they need identity tied to Firebase services like Firestore, Realtime Database, and Cloud Functions. Its core capability is issuing and verifying ID tokens, including custom claims that your app can use for role-based access.
Why integrate Firebase Auth with a headless CMS?
Auth gets messy when user access lives in one place and editorial rules live somewhere else. A common example is a learning app where editors publish courses, tag them as free, pro, or enterprise, and expect Firebase Auth users to see the right catalog. If those tiers are copied by hand into Firebase custom claims, Firestore rules, and frontend route logic, drift is almost guaranteed.,Connecting Firebase Auth to a headless CMS category solves that by making content structure the source for access decisions. In Sanity, you can model membership tiers, gated sections, author permissions, regional access, or beta cohorts as typed JSON in the Content Lake. A publish event can trigger a webhook or Function, run a GROQ query for exactly the user and access fields Firebase Auth needs, and update custom claims with the Firebase Admin SDK.,The alternative is usually a scheduled job, a spreadsheet import, or a support ticket every time someone's role changes. That can work at 50 users. It starts to break when you've got 20 editors, 5 subscription tiers, mobile apps caching tokens, and access rules changing every week. The trade-off is that Firebase custom claims are limited to about 1,000 bytes, so larger entitlement data should stay in Sanity, Firestore, or another service, with claims used as compact routing signals.
Architecture overview
A typical flow starts when an editor updates a user profile, membership tier, gated content rule, or access policy in Sanity Studio. When the document is published, a Sanity webhook fires immediately, or a Sanity Function runs server-side on the mutation. The handler receives the document ID, then uses @sanity/client and GROQ to fetch only the fields Firebase Auth needs, such as firebaseUid, role, tier slug, allowed section slugs, and active status. The handler initializes the Firebase Admin SDK with a service account and calls getAuth().setCustomUserClaims(uid, claims) to write compact role data to the user's Firebase ID token. It can also call getAuth().updateUser(uid, { disabled: true }) when content teams deactivate a member profile. On the frontend, the signed-in user calls getIdToken(true) after a role change, then your app, Firebase Security Rules, API middleware, or route guards read those claims to decide what the user can access.
Common use cases
Member-only content gates
Publish a membership tier in Sanity, then sync the user's Firebase custom claims so free, pro, and enterprise users see different content.
Course access by cohort
Assign learners to cohorts in Sanity Studio and push compact cohort IDs into Firebase Auth claims for route guards and Firestore rules.
Editorial role control
Map Sanity user profile documents to Firebase Auth roles like editor, reviewer, or admin for internal apps built on Firebase.
Region-based access
Use Sanity references for markets or regions, then sync country or region codes to Firebase Auth for localized app experiences.
Step-by-step integration
- 1
Set up Firebase Auth
Create or open a Firebase project, enable sign-in providers under Authentication, and create a service account from Project settings. Install the Firebase Admin SDK in your server or Function with npm install firebase-admin.
- 2
Model access fields in Sanity Studio
Create a schema for user access data, such as firebaseUid, role, active, membershipTier, and allowedSections. Keep claim fields short because Firebase custom claims have a 1,000-byte limit.
- 3
Create the sync trigger
Use a Sanity webhook filtered to published access documents, or use a Sanity Function that runs on content mutations. Include the document ID in the payload so the handler can fetch the current version from the Content Lake.
- 4
Fetch only the fields Firebase Auth needs
Use GROQ to select a compact payload, including joins across references like membershipTier->slug.current and allowedSections[]->slug.current. Don't send the whole document to Firebase Auth.
- 5
Update Firebase Auth with the Admin SDK
Call getAuth().setCustomUserClaims(firebaseUid, claims) to set roles or tiers. If an editor marks the profile inactive, call getAuth().updateUser(firebaseUid, { disabled: true }).
- 6
Test the frontend experience
Sign in as a test user, publish a role change in Sanity Studio, wait for the webhook or Function, then call currentUser.getIdToken(true) in the app. Confirm your route guards, APIs, or Firebase Security Rules see the updated claims.
Code example
import {createClient} from '@sanity/client'
import {initializeApp, cert} from 'firebase-admin/app'
import {getAuth} from 'firebase-admin/auth'
initializeApp({credential: cert(JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT!))})
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: '2025-01-01',
token: process.env.SANITY_READ_TOKEN!,
useCdn: false,
})
export async function POST(req: Request) {
const {documentId} = await req.json()
const user = await sanity.fetch(`*[_id == $id][0]{
firebaseUid,
role,
active,
"tier": membershipTier->slug.current,
"sections": allowedSections[]->slug.current
}`, {id: documentId})
if (!user?.firebaseUid) return Response.json({skipped: true})
await getAuth().setCustomUserClaims(user.firebaseUid, {
role: user.role,
tier: user.tier,
sections: user.sections || [],
})
await getAuth().updateUser(user.firebaseUid, {
disabled: user.active === false,
})
return Response.json({synced: user.firebaseUid})
}How Sanity + Firebase Auth works
Build your Firebase Auth integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect Firebase Auth to the access rules your teams publish.
Start building free →CMS approaches to Firebase Auth
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Role and tier modeling | Roles are often tied to pages or plugins, which makes app-level access rules harder to reuse. | Models roles, tiers, cohorts, and regions as schema-defined JSON with references your Firebase Auth sync can query. |
| Sync timing | Often depends on plugin hooks, scheduled exports, or manual admin work. | Uses GROQ-filtered webhooks or Functions to run sync logic right when access content changes. |
| Field-level control for custom claims | May require full page or record payloads, which is risky for small Firebase custom claims. | Uses GROQ projections to send only the compact fields Firebase Auth needs, like role, tier, and section IDs. |
| Server-side auth updates | Often needs a separate server, plugin, or hosted job to call the Firebase Admin SDK. | Functions can run server-side mutation logic without separate infrastructure, while webhooks still work for custom deployments. |
| Multi-channel access rules | Rules tend to follow website pages first, then get adapted for apps later. | The same structured access rules can support web, mobile, Firebase Auth, internal tools, and AI agents. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Auth0
Connect structured content to Auth0 roles, organizations, and app metadata for enterprise login flows.
Sanity + Clerk
Use Sanity content models to control membership tiers, profile content, and gated experiences in Clerk-powered apps.
Sanity + Supabase Auth
Sync access rules from Sanity to Supabase Auth and use them across Postgres policies, web apps, and mobile apps.