Skip to Content
GuidesMigrating from Firebase

Migrating from Firebase Auth

Firebase Authentication is a popular choice for getting started quickly, but as applications grow, teams often need enterprise features that Firebase does not offer: enterprise SSO (SAML/OIDC), SCIM provisioning, fine-grained authorization, organizations with B2B multi-tenancy, compliance certifications, and self-hosted deployment options. Auris provides all of these while matching Firebase Auth’s developer experience.

This guide walks you through exporting users from Firebase, handling the password hash transition, replacing the Firebase SDK with the Auris SDK, and migrating custom claims and security rules.

Feature Mapping

Firebase Auth FeatureAuris EquivalentNotes
Email/Password LoginHosted Login PagesOAuth2 PKCE flow, tenant-branded UI
Social Providers (Google, Facebook, etc.)Social Login9 providers, same OAuth2 flow
Phone Auth (SMS)SMS OTPTwilio provider, 2FA and passwordless
Anonymous AuthNot availableUse magic links for low-friction onboarding
Custom ClaimsCustom JWT ClaimsPer-application, 5 value types, admin-configurable
Firebase Admin SDKManagement Client (@auris/js)M2M client credentials, user/role/org CRUD
Security RulesFGA (Fine-Grained Authorization) + RBACZanzibar-style ReBAC, more powerful than Security Rules
Email Link Sign-InMagic LinksToken-based, auto-signup support
Multi-Factor AuthMulti-Factor AuthTOTP, SMS, WebAuthn (Firebase only supports SMS + TOTP)
Firebase UIHosted Login PagesFully managed, customizable branding
User Management (Console)Auris ConsoleFull admin console with roles, permissions, sessions
ID Token VerificationJWT Verification (JWKS)RS256 with JWKS endpoint, @auris/js verifier
Blocking FunctionsActions Engine6 trigger points, sandboxed JS, visual editor

Features Auris Adds Beyond Firebase

FeatureDescription
Enterprise SSOSAML 2.0 + OIDC federation for corporate IdPs
SCIM 2.0 ProvisioningAutomated user sync with Okta, Azure AD, etc.
Organizations B2BMulti-org with member roles and invitations
Fine-Grained Authorization (FGA)Zanzibar-style relationship-based access control
Roles & PermissionsTri-state RBAC with per-application scoping
Custom DomainsWhite-label auth pages under your domain
Log StreamingExport audit logs to Datadog, Splunk, S3
WebhooksHMAC-signed real-time event notifications
Rate LimitingTiered rate limiting with standard headers
Attack ProtectionIP rules, brute-force lockout, CAPTCHA, suspicious login detection

Migration Steps

Step 1: Export Users from Firebase

Use the Firebase Admin SDK to export all users. Firebase provides a listUsers method that pages through all users:

// export-firebase-users.ts import admin from 'firebase-admin' import fs from 'fs' admin.initializeApp({ credential: admin.credential.cert('./service-account-key.json'), }) interface ExportedUser { uid: string email: string emailVerified: boolean displayName: string phoneNumber?: string disabled: boolean customClaims?: Record<string, unknown> passwordHash?: string passwordSalt?: string providerData: Array<{ providerId: string; uid: string }> createdAt: string } async function exportAllUsers(): Promise<ExportedUser[]> { const users: ExportedUser[] = [] let nextPageToken: string | undefined do { const result = await admin.auth().listUsers(1000, nextPageToken) for (const user of result.users) { users.push({ uid: user.uid, email: user.email || '', emailVerified: user.emailVerified, displayName: user.displayName || '', phoneNumber: user.phoneNumber, disabled: user.disabled, customClaims: user.customClaims, passwordHash: user.passwordHash, passwordSalt: user.passwordSalt, providerData: user.providerData.map((p) => ({ providerId: p.providerId, uid: p.uid, })), createdAt: user.metadata.creationTime, }) } nextPageToken = result.pageToken console.log(`Exported ${users.length} users...`) } while (nextPageToken) return users } exportAllUsers().then((users) => { fs.writeFileSync('firebase-users-export.json', JSON.stringify(users, null, 2)) console.log(`Total: ${users.length} users exported`) })

Firebase exports password hashes using a modified scrypt algorithm (Firebase scrypt). These hashes cannot be directly verified by Auris since Auris uses bcrypt. You will need to handle password migration using the lazy migration pattern described below.

Step 2: Handle Password Hash Migration

Firebase uses a custom scrypt variant for password hashing that is not compatible with standard bcrypt used by Auris. There are two approaches:

The lazy migration pattern re-hashes passwords transparently as users log in. This provides zero friction for end users.

How it works:

  1. Import users into Auris without passwords (they will be created as passwordless accounts)
  2. When a user attempts to log in on Auris and has no password set, return a specific flow
  3. Your application attempts to verify the credential against Firebase using the Admin SDK
  4. If Firebase verification succeeds, set the user’s password in Auris via the Admin API
  5. All subsequent logins go directly through Auris
// Middleware for lazy password migration // This runs in your backend, not in Auris import admin from 'firebase-admin' async function lazyMigratePassword( email: string, password: string, aurisManagementToken: string ): Promise<boolean> { try { // Step 1: Try to sign in with Firebase // Note: Firebase Admin SDK doesn't have signInWithPassword // Use the Firebase Auth REST API instead const firebaseResponse = await fetch( `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.FIREBASE_API_KEY}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, returnSecureToken: false, }), } ) if (!firebaseResponse.ok) { return false // Invalid credentials in Firebase too } // Step 2: Firebase verified the password — set it in Auris // Find the user in Auris by email const userResponse = await fetch( `https://auth.yourcompany.com/api/users?email=${encodeURIComponent(email)}`, { headers: { Authorization: `Bearer ${aurisManagementToken}`, 'x-tenant': 'your-tenant-id', }, } ) const userData = await userResponse.json() const userId = userData.data?.[0]?.id if (!userId) return false // Step 3: Trigger a password reset for the user in Auris // Note: this triggers a password-reset email rather than directly setting a password. await fetch( `https://auth.yourcompany.com/api/users/${userId}/reset-password`, { method: 'POST', headers: { Authorization: `Bearer ${aurisManagementToken}`, 'x-tenant': 'your-tenant-id', 'Content-Type': 'application/json', }, body: JSON.stringify({ password }), } ) console.log(`Migrated password for user: ${email}`) return true } catch (error) { console.error(`Password migration failed for ${email}:`, error) return false } }

Option B: Force Password Reset (Simpler)

If you prefer a clean break from Firebase:

  1. Import users without passwords
  2. After import, trigger password reset emails for all users
  3. Users set a new password on their first login

This is simpler to implement but requires all users to take action.

Step 3: Import Users into Auris

Transform the Firebase export to the Auris import format:

// transform-firebase-users.ts import fs from 'fs' interface FirebaseUser { uid: string email: string emailVerified: boolean displayName: string phoneNumber?: string disabled: boolean customClaims?: Record<string, unknown> } interface AurisImportUser { email: string firstName: string lastName: string emailVerified: boolean } const firebaseUsers: FirebaseUser[] = JSON.parse( fs.readFileSync('firebase-users-export.json', 'utf-8') ) const aurisUsers: AurisImportUser[] = firebaseUsers .filter((u) => u.email && !u.disabled) // Skip disabled and email-less users .map((user) => { const nameParts = (user.displayName || '').split(' ') return { email: user.email, firstName: nameParts[0] || '', lastName: nameParts.slice(1).join(' ') || '', emailVerified: user.emailVerified, } }) fs.writeFileSync('auris-import.json', JSON.stringify(aurisUsers, null, 2)) console.log(`Transformed ${aurisUsers.length} users for import`)

Upload to Auris:

curl -X POST https://auth.yourcompany.com/api/users/import \ -H "Authorization: Bearer $AURIS_ACCESS_TOKEN" \ -H "x-tenant: your-tenant-id" \ -F "[email protected]" \ -F "format=json"

Step 4: Create an Application in Auris Console

  1. Open the Auris Console and go to Applications then Create Application
  2. Select WEB as the application type
  3. Enter your application name
  4. Add your Callback URLs (the same ones used with Firebase)
  5. Add your Allowed Origins
  6. Save and note the Client ID

Step 5: Replace the Firebase SDK with Auris SDK

# Remove Firebase npm uninstall firebase firebase-admin # Install Auris npm install @auris/js @auris/react # For Next.js: npm install @auris/nextjs

Step 6: Update Application Code

Initialization:

// BEFORE (Firebase) import { initializeApp } from 'firebase/app' import { getAuth, signInWithEmailAndPassword, signOut, onAuthStateChanged } from 'firebase/auth' const app = initializeApp({ apiKey: 'AIza...', authDomain: 'myapp.firebaseapp.com', projectId: 'myapp', }) const auth = getAuth(app) // AFTER (Auris) import { AurisClient } from '@auris/js' const auris = new AurisClient({ domain: 'auth.yourcompany.com', clientId: 'your-client-id', redirectUri: 'http://localhost:3000/callback', autoRefresh: true, })

Authentication (React):

// BEFORE (Firebase) import { getAuth, signInWithEmailAndPassword, signOut } from 'firebase/auth' import { useAuthState } from 'react-firebase-hooks/auth' function Login() { const auth = getAuth() const [user, loading] = useAuthState(auth) const handleLogin = () => { signInWithEmailAndPassword(auth, email, password) } const handleLogout = () => { signOut(auth) } if (loading) return <p>Loading...</p> if (user) return <button onClick={handleLogout}>Sign out</button> return <button onClick={handleLogin}>Sign in</button> } // AFTER (Auris) import { AurisProvider, useAuris } from '@auris/react' function App() { return ( <AurisProvider domain="auth.yourcompany.com" clientId="your-client-id" redirectUri={window.location.origin + '/callback'} > <Login /> </AurisProvider> ) } function Login() { const { loginWithRedirect, logout, user, isAuthenticated, isLoading } = useAuris() if (isLoading) return <p>Loading...</p> if (isAuthenticated) { return ( <button onClick={() => logout({ returnTo: window.location.origin })}> Sign out </button> ) } return <button onClick={loginWithRedirect}>Sign in</button> }

Getting the Current User:

// BEFORE (Firebase) import { getAuth, onAuthStateChanged } from 'firebase/auth' const auth = getAuth() onAuthStateChanged(auth, (user) => { if (user) { console.log('User:', user.uid, user.email) } }) // AFTER (Auris) const user = await auris.getUser() if (user) { console.log('User:', user.id, user.email) } // Or with the auth state change listener: auris.onAuthStateChange((user) => { if (user) { console.log('User:', user.id, user.email) } })

ID Token for API Calls:

// BEFORE (Firebase) import { getAuth } from 'firebase/auth' const auth = getAuth() const token = await auth.currentUser?.getIdToken() const response = await fetch('/api/protected', { headers: { Authorization: `Bearer ${token}` }, }) // AFTER (Auris) const token = await auris.getAccessToken() const response = await fetch('/api/protected', { headers: { Authorization: `Bearer ${token}` }, })

Server-Side Token Verification:

// BEFORE (Firebase Admin SDK) import admin from 'firebase-admin' async function verifyToken(token: string) { const decoded = await admin.auth().verifyIdToken(token) return decoded // { uid, email, ... } } // AFTER (Auris — using JWKS) import { verifyJwt } from '@auris/js/jwt-verify' async function verifyToken(token: string) { const decoded = await verifyJwt(token, { jwksUrl: 'https://auth.yourcompany.com/.well-known/jwks.json', }) return decoded // { sub, email, roles, ... } } // Or using the Next.js helper: import { getSession } from '@auris/nextjs/server' export async function GET(req: Request) { const session = await getSession() if (!session) { return Response.json({ error: 'Unauthorized' }, { status: 401 }) } return Response.json({ userId: session.user.id }) }

Social Login (Google):

// BEFORE (Firebase) import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/auth' const auth = getAuth() const provider = new GoogleAuthProvider() const result = await signInWithPopup(auth, provider) // AFTER (Auris) // Social login is handled by the Hosted Login page — no code change needed. // Configure Google as a social provider in Console → Authentication → Social Login. // Users see the Google button on the hosted login page automatically. // If you want to go directly to Google: await auris.loginWithRedirect({ connection: 'google' })

Step 7: Migrate Custom Claims

Firebase custom claims are typically set via the Admin SDK:

// BEFORE (Firebase) await admin.auth().setCustomUserClaims(uid, { role: 'admin', plan: 'enterprise', orgId: 'org_123', })

In Auris, custom claims are configured per-application in the Console:

  1. Go to Applications then select your app then Custom Claims tab
  2. Add claims:
    • role with type User Attribute mapped to roles[0].name
    • plan with type Static value enterprise (or Expression for dynamic values)
    • orgId with type User Attribute mapped to metadata.orgId

Or via the API:

curl -X POST https://auth.yourcompany.com/api/applications/APP_ID/custom-claims \ -H "Authorization: Bearer $AURIS_ACCESS_TOKEN" \ -H "x-tenant: your-tenant-id" \ -H "Content-Type: application/json" \ -d '{ "claimKey": "role", "valueType": "USER_ATTRIBUTE", "userAttribute": "roles", "isActive": true }'

See the Custom JWT Claims guide for the full configuration reference.

Step 8: Migrate Security Rules to FGA

Firebase Security Rules are declarative access control rules tied to Firestore or Realtime Database paths. Auris uses Fine-Grained Authorization (FGA) based on the Zanzibar model, which is more powerful and decoupled from your data layer.

Firebase Security Rules example:

rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /documents/{docId} { allow read: if request.auth != null && (resource.data.ownerId == request.auth.uid || request.auth.uid in resource.data.viewers); allow write: if request.auth != null && resource.data.ownerId == request.auth.uid; } } }

Auris FGA equivalent model:

model schema 1.1 type user type document relations define owner: [user] define viewer: [user] or owner define editor: [user] or owner define can_read: viewer define can_write: editor

Write relationship tuples to represent the data:

# Grant ownership curl -X POST https://auth.yourcompany.com/api/fga/tuples \ -H "Authorization: Bearer $AURIS_ACCESS_TOKEN" \ -H "x-tenant: your-tenant-id" \ -H "Content-Type: application/json" \ -d '{ "objectType": "document", "objectId": "doc_123", "relation": "owner", "subjectType": "user", "subjectId": "usr_abc" }' # Grant viewer access curl -X POST https://auth.yourcompany.com/api/fga/tuples \ -H "Authorization: Bearer $AURIS_ACCESS_TOKEN" \ -H "x-tenant: your-tenant-id" \ -H "Content-Type: application/json" \ -d '{ "objectType": "document", "objectId": "doc_123", "relation": "viewer", "subjectType": "user", "subjectId": "usr_xyz" }'

Check access in your application:

// Using the Auris SDK import { AurisClient } from '@auris/js' const auris = new AurisClient({ domain: 'auth.yourcompany.com', clientId: 'your-client-id', }) const result = await auris.fga.check({ objectType: 'document', objectId: 'doc_123', relation: 'can_read', subjectType: 'user', subjectId: 'usr_abc', }) if (result.allowed) { // User can read the document }

See the Fine-Grained Authorization guide for the complete FGA reference.

Migration Timeline

A typical Firebase-to-Auris migration follows this timeline:

WeekActivities
1Set up Auris tenant, create applications, configure social providers, export Firebase users
2Import users into Auris, implement lazy password migration endpoint, set up FGA model
3Replace Firebase SDK with Auris SDK in your application, test all auth flows
4Staging deployment and QA, migrate custom claims, set up webhooks/log streaming
5Production deployment, monitor lazy migration progress, begin Firebase decommission
6-8Monitor migration completion rate, send password reset to remaining unmigrated users
8+Decommission Firebase project

Post-Migration Checklist

  • All users can log in (email/password, social, phone)
  • Lazy password migration is working (check Auris audit logs)
  • Custom claims appear in access tokens
  • FGA authorization checks are returning correct results
  • Server-side token verification uses Auris JWKS
  • Social login providers (Google, Facebook, etc.) are configured
  • Webhook events are being delivered
  • Firebase SDK is completely removed from the codebase
  • Firebase Auth emulator references are removed from tests
  • Firebase project billing is downgraded or cancelled

Keep your Firebase project active during the lazy migration period. Monitor the percentage of users who have migrated their passwords by checking Auris audit logs for user.password_changed events. Once migration reaches 95%+ of active users, send a password reset email to the remaining users and schedule Firebase decommission.