Better auth firebase auth
Add Firebase Authentication (Phone SMS OTP, Google Sign-In, Email/Password) to a Better Auth app using the better-auth-firebase-auth plugin. Use when adding phone authentication to Better Auth without Twilio, integrating Firebase Auth with Better Auth sessions, working with the better-auth-firebase-auth package, or deciding between Firebase Phone Auth and Better Auth's built-in phoneNumber plugin.From its SKILL.md
npx -y skills add yultyyev/better-auth-firebase-authAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
3 things to look at
- reads credentialsReads from 3 credential sources: `FIREBASE_PROJECT_ID` and 2 more.
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 1 command, including `pnpm add better-auth-firebase-auth firebase-admin firebase better-auth`.
SKILL.md
7.1 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it
Firebase Auth + Better Auth
better-auth-firebase-auth bridges Firebase Authentication identity providers into Better Auth sessions. Firebase verifies the user; Better Auth owns the session, users, and plugins.
Package: better-auth-firebase-auth — GitHub · npm
Decision: Firebase Phone Auth vs Better Auth phoneNumber plugin
Use better-auth-firebase-auth when:
- You want phone auth without setting up Twilio or any SMS provider
- You are already using Firebase in your project
- You want Google to manage SMS delivery, reCAPTCHA, and fraud prevention
Use Better Auth's built-in phoneNumber plugin when:
- You want no Firebase dependency
- You need a specific SMS provider for compliance or cost reasons
Install
pnpm add better-auth-firebase-auth firebase-admin firebase better-auth
Import paths — CRITICAL
Always split server and client imports. Never import firebase-admin into client bundles.
// Server ONLY (API routes, server components, server actions)
import { firebaseAuthPlugin } from "better-auth-firebase-auth/server";
// Client ONLY (React components, browser code)
import { firebaseAuthClientPlugin } from "better-auth-firebase-auth/client";
Server setup (lib/auth.ts)
import { betterAuth } from "better-auth";
import { firebaseAuthPlugin } from "better-auth-firebase-auth/server";
import { cert, getApps, initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";
// Initialize Firebase Admin once
if (getApps().length === 0) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID!,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL!,
privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n"),
}),
});
}
export const auth = betterAuth({
plugins: [
firebaseAuthPlugin({
useClientSideTokens: true, // client gets Firebase token, server only verifies
firebaseAdminAuth: getAuth(),
}),
],
});
Client setup (lib/auth-client.ts)
import { createAuthClient } from "better-auth/react";
import { firebaseAuthClientPlugin } from "better-auth-firebase-auth/client";
export const authClient = createAuthClient({
plugins: [firebaseAuthClientPlugin()],
});
Phone Authentication (SMS OTP)
Firebase sends the SMS and verifies the OTP. No Twilio needed.
Prerequisite: Enable Phone in Firebase Console → Authentication → Sign-in method.
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from "firebase/auth";
import { authClient } from "@/lib/auth-client";
const firebaseAuth = getAuth();
// 1. Send OTP
const verifier = new RecaptchaVerifier(firebaseAuth, "recaptcha-container", {
size: "invisible",
});
const confirmation = await signInWithPhoneNumber(firebaseAuth, "+15555550100", verifier);
// 2. Confirm OTP → get Firebase token → create Better Auth session
const result = await confirmation.confirm(userEnteredCode);
const idToken = await result.user.getIdToken();
await authClient.signInWithPhone({ idToken });
Phone-only users (no email on their Firebase account) get a stable synthetic email: ${uid}@firebase.local by default. Override with getPhoneUserFallbackEmail.
Google Sign-In
import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
const result = await signInWithPopup(getAuth(), new GoogleAuthProvider());
const idToken = await result.user.getIdToken();
await authClient.signInWithGoogle({ idToken });
Email/Password
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
const credential = await signInWithEmailAndPassword(getAuth(), email, password);
const idToken = await credential.user.getIdToken();
await authClient.signInWithEmail({ idToken });
Password reset is handled by Firebase — no email provider (SendGrid, Resend) needed:
await authClient.sendPasswordReset({ email });
Using with the Firestore adapter
To store Better Auth data in Firestore, combine with better-auth-firestore:
import { firestoreAdapter } from "better-auth-firestore";
import { firebaseAuthPlugin } from "better-auth-firebase-auth/server";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
export const auth = betterAuth({
database: firestoreAdapter({ firestore: getFirestore() }),
plugins: [firebaseAuthPlugin({ firebaseAdminAuth: getAuth() })],
});
Remember to create the required Firestore composite index on the verification collection — see better-auth-firestore.
Key options
| Option | Default | Notes |
|---|---|---|
useClientSideTokens | true | false = server handles Firebase client SDK (needs firebaseConfig) |
overrideEmailPasswordFlow | false | Intercept Better Auth's /sign-in/email and /sign-up/email routes |
serverSideOnly | false | No endpoints registered; use hooks only |
sessionExpiresInDays | 7 | Better Auth session lifetime |
passwordResetUrl | — | Custom URL for password reset page |
getPhoneUserFallbackEmail | ${uid}@firebase.local | Stable email for phone-only users |
Runtime support
| Runtime | Supported |
|---|---|
| Node 18+ | ✅ |
| Next.js on Vercel (Node.js runtime) | ✅ Recommended |
| Cloud Functions / Cloud Run | ✅ |
Vercel Edge Runtime (runtime = 'edge') | ❌ Admin SDK requires Node.js |
| Cloudflare Workers | ❌ Admin SDK requires Node.js |
Note: Vercel deploys work fine — the restriction is only when you explicitly opt into the Edge Runtime (export const runtime = 'edge'). The default Node.js serverless runtime on Vercel is fully supported.
Common mistakes
- Importing
firebaseAuthPluginin client code — crashes onfirebase-admin. Always use the/serverpath on the server and/clientpath in the browser. - Forgetting to enable the provider in Firebase Console — Phone, Google, and Email/Password must each be explicitly enabled under Authentication → Sign-in method.
- Missing reCAPTCHA container —
signInWithPhoneNumberrequires aRecaptchaVerifierwith a DOM element id. For invisible reCAPTCHA usesize: "invisible". - Firebase Admin not initialized before
getAuth()— callinitializeApp()once before passinggetAuth()to the plugin. - Using
overrideEmailPasswordFlow: truewithoutfirebaseConfig— throws at startup. This mode requires the Firebase client SDK config. - FIREBASE_PRIVATE_KEY with literal
\n— Always call.replace(/\\n/g, "\n")on the key before passing tocert().
What ships with it: 33 files
105.4 KB alongside SKILL.md, 12 of them executable
examples/
- minimal/AGENTS.md462 B
- minimal/app/api/auth/[...all]/route.tsruns143 B
- minimal/app/globals.css281 B
- minimal/app/layout.tsx390 B
- minimal/app/page.tsx858 B
- minimal/.env.example485 B
- minimal/eslint.config.mjsruns469 B
- minimal/.gitignore374 B
- minimal/lib/auth-client.tsruns300 B
- minimal/lib/auth.tsruns1.6 KB
- minimal/next.config.tsruns372 B
- minimal/package.json662 B
- minimal/README.md1.8 KB
- minimal/tsconfig.json698 B
src/
- firebase-auth-client-plugin.test.tsruns9.1 KB
- firebase-auth-client-plugin.tsruns3.4 KB
- firebase-auth-plugin.test.tsruns26.8 KB
- firebase-auth-plugin.tsruns12.8 KB
- index.tsruns191 B
- types.tsruns1.4 KB
- AGENTS.md8.1 KB
- biome.json1.5 KB
- CONTRIBUTING.md8.1 KB
- .gitignore449 B
- LICENSE1.0 KB
- package.json2.4 KB
- pnpm-workspace.yaml639 B
- README.md16.5 KB
- .releaserc.json745 B
- SECURITY.md2.5 KB
- tsconfig.build.json326 B
- tsconfig.json454 B
- vitest.config.tsruns205 B