Overview
Firebase Authentication uses RS256 signing with Google’s SecureToken JWKS for token verification. This guide shows you how to integrate Firebase Auth with Revstack.
Prerequisites
A Firebase project
Firebase Authentication enabled
Your Firebase project ID
Installation
npm install @revstackhq/auth
Configuration
1. Get your Firebase project ID
From the Firebase Console:
Go to Project Settings (gear icon)
Note your Project ID (e.g., my-firebase-project)
2. Build the auth contract
Create an auth contract using your Firebase project ID:
import { buildAuthContract } from "@revstackhq/auth" ;
const authContract = buildAuthContract ( "firebase" , {
projectId: "my-firebase-project" ,
});
The contract builder automatically:
Sets the JWKS URI to Google’s SecureToken endpoint: https://www.googleapis.com/service_accounts/v1/jwk/[email protected]
Sets the issuer to https://securetoken.google.com/my-firebase-project
Sets the audience to your project ID
Configures RS256 verification strategy
Verification
Initialize the verifier
import { RevstackAuth } from "@revstackhq/auth" ;
const auth = new RevstackAuth ( authContract );
Verify tokens
In your API routes or middleware:
export async function authMiddleware ( req , res , next ) {
const session = await auth . validate ( req . headers . authorization );
if ( ! session . isValid ) {
return res . status ( 401 ). json ({ error: session . error });
}
// Attach user info to request
req . userId = session . userId ; // Firebase user UID
req . claims = session . claims ; // Full JWT payload
next ();
}
Token structure
Firebase Auth ID tokens contain standard claims plus Firebase-specific fields:
{
"iss" : "https://securetoken.google.com/my-firebase-project" ,
"sub" : "abcd1234efgh5678ijkl" ,
"aud" : "my-firebase-project" ,
"auth_time" : 1516239022 ,
"iat" : 1516239022 ,
"exp" : 1516242622 ,
"email" : "[email protected] " ,
"email_verified" : true ,
"firebase" : {
"identities" : {
"email" : [ "[email protected] " ]
},
"sign_in_provider" : "password"
},
"user_id" : "abcd1234efgh5678ijkl"
}
Accessing Firebase claims
You can access Firebase-specific claims using typed interfaces:
interface FirebaseClaims {
email ?: string ;
email_verified ?: boolean ;
phone_number ?: string ;
name ?: string ;
picture ?: string ;
firebase : {
identities : Record < string , string []>;
sign_in_provider : string ;
};
user_id : string ;
}
const session = await auth . validate < FirebaseClaims >( token );
if ( session . isValid ) {
console . log ( session . userId ); // "abcd1234efgh5678ijkl"
console . log ( session . claims . email ); // "[email protected] "
console . log ( session . claims . firebase . sign_in_provider ); // "password", "google.com", etc.
}
Custom claims
Firebase allows you to set custom claims on user tokens via the Admin SDK. These claims are accessible in the verified session:
// Setting custom claims (Firebase Admin SDK)
import { getAuth } from "firebase-admin/auth" ;
await getAuth (). setCustomUserClaims ( userId , {
role: "admin" ,
plan: "pro" ,
});
// Accessing custom claims (Revstack)
interface CustomFirebaseClaims {
role ?: string ;
plan ?: string ;
}
const session = await auth . validate < CustomFirebaseClaims >( token );
if ( session . isValid ) {
console . log ( session . claims . role ); // "admin"
console . log ( session . claims . plan ); // "pro"
}
Custom claims in Firebase are set server-side using the Admin SDK. They appear directly in the token payload without any prefix.
Sign-in providers
Firebase supports multiple authentication providers. You can check which provider was used:
const session = await auth . validate ( token );
if ( session . isValid ) {
const provider = session . claims . firebase . sign_in_provider ;
switch ( provider ) {
case "password" :
console . log ( "Email/password sign-in" );
break ;
case "google.com" :
console . log ( "Google sign-in" );
break ;
case "github.com" :
console . log ( "GitHub sign-in" );
break ;
// ... other providers
}
}
Complete example
Here’s a full Express.js integration:
import express from "express" ;
import { buildAuthContract , RevstackAuth , AuthErrorCode } from "@revstackhq/auth" ;
// Build contract once at startup
const authContract = buildAuthContract ( "firebase" , {
projectId: process . env . FIREBASE_PROJECT_ID ! ,
});
const auth = new RevstackAuth ( authContract );
const app = express ();
// Auth middleware
app . use ( async ( req , res , next ) => {
const authHeader = req . headers . authorization ;
if ( ! authHeader ) {
return res . status ( 401 ). json ({ error: "Missing authorization header" });
}
const session = await auth . validate ( authHeader );
if ( ! session . isValid ) {
if ( session . errorCode === AuthErrorCode . TOKEN_EXPIRED ) {
return res . status ( 401 ). json ({
error: "Session expired. Please sign in again." ,
code: "session_expired"
});
}
return res . status ( 401 ). json ({ error: session . error });
}
// Store user info on request
req . userId = session . userId ;
req . email = session . claims . email ;
req . emailVerified = session . claims . email_verified ;
next ();
});
// Protected route
app . get ( "/api/profile" , ( req , res ) => {
res . json ({
userId: req . userId ,
email: req . email ,
emailVerified: req . emailVerified ,
});
});
app . listen ( 3000 );
Frontend integration
When calling your API from a Firebase-enabled frontend:
import { getAuth } from "firebase/auth" ;
const auth = getAuth ();
async function callAPI () {
const user = auth . currentUser ;
if ( ! user ) {
throw new Error ( "Not authenticated" );
}
// Get the ID token
const token = await user . getIdToken ();
const response = await fetch ( "/api/profile" , {
headers: {
Authorization: `Bearer ${ token } ` ,
},
});
return response . json ();
}
Token refresh
Firebase ID tokens expire after 1 hour. The Firebase SDK automatically refreshes them, but you should handle token refresh in your API calls:
import { getAuth } from "firebase/auth" ;
const auth = getAuth ();
async function callAPIWithRetry () {
const user = auth . currentUser ;
if ( ! user ) {
throw new Error ( "Not authenticated" );
}
// Force refresh if needed
const token = await user . getIdToken ( /* forceRefresh */ false );
const response = await fetch ( "/api/profile" , {
headers: {
Authorization: `Bearer ${ token } ` ,
},
});
// If token expired, refresh and retry
if ( response . status === 401 ) {
const freshToken = await user . getIdToken ( true ); // Force refresh
return fetch ( "/api/profile" , {
headers: {
Authorization: `Bearer ${ freshToken } ` ,
},
});
}
return response ;
}
Email verification enforcement
You can enforce that users have verified their email addresses:
const session = await auth . validate ( token );
if ( ! session . isValid ) {
return res . status ( 401 ). json ({ error: session . error });
}
if ( ! session . claims . email_verified ) {
return res . status ( 403 ). json ({
error: "Please verify your email address before accessing this resource."
});
}
// Continue with verified user
Environment variables
Store your Firebase configuration in environment variables:
# .env
FIREBASE_PROJECT_ID = my-firebase-project
Testing
To test your integration:
Sign in via your Firebase-enabled frontend
Obtain the ID token using user.getIdToken()
Send it in the Authorization header: Bearer <token>
Verify the token is validated correctly
You can also generate custom tokens for testing using the Firebase Admin SDK:
import { getAuth } from "firebase-admin/auth" ;
const customToken = await getAuth (). createCustomToken ( userId , {
// Optional custom claims
role: "admin" ,
});
// Exchange the custom token for an ID token in your frontend
Troubleshooting
”Issuer mismatch” error
Verify that:
Your Firebase project ID is correct
The token was issued by Firebase (not a different service)
You’re using the project ID, not the project name
”Invalid signature” error
Check that:
The token was issued by Firebase Authentication
The token hasn’t been tampered with
The JWKS endpoint (Google’s SecureToken service) is accessible
”Audience validation failed” error
Ensure:
The projectId in your contract matches your Firebase project
The token’s aud claim matches your project ID
Network errors
If you get network errors when fetching the JWKS:
Verify your server can access https://www.googleapis.com
Check for firewall rules blocking outbound HTTPS requests
Ensure your DNS can resolve googleapis.com
Firestore integration
If you’re using Firestore alongside Firebase Auth, you can use the verified user ID to query user-specific data:
import { getFirestore } from "firebase-admin/firestore" ;
const db = getFirestore ();
// After validating the token
const session = await auth . validate ( token );
if ( session . isValid ) {
// Query user's subscriptions
const subscriptionsRef = db
. collection ( 'subscriptions' )
. where ( 'userId' , '==' , session . userId );
const snapshot = await subscriptionsRef . get ();
const subscriptions = snapshot . docs . map ( doc => doc . data ());
}
Next steps
Auth Overview Learn more about JWT verification in Revstack
Error Handling Handle authentication errors gracefully