AgentStack
SKILL verified MIT Self-run

Firebase Auth Better Auth

skill-yultyyev-better-auth-firebase-auth-better-auth-firebase-auth · by yultyyev

>-

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add skill-yultyyev-better-auth-firebase-auth-better-auth-firebase-auth

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Firebase Auth Better Auth? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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-authGitHub · 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 firebaseAuthPlugin in client code — crashes on firebase-admin. Always use the /server path on the server and /client path 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 containersignInWithPhoneNumber requires a RecaptchaVerifier with a DOM element id. For invisible reCAPTCHA use size: "invisible".
  • Firebase Admin not initialized before getAuth() — call initializeApp() once before passing getAuth() to the plugin.
  • Using overrideEmailPasswordFlow: true without firebaseConfig — throws at startup. This mode requires the Firebase client SDK config.
  • FIREBASEPRIVATEKEY with literal \n — Always call .replace(/\\n/g, "\n") on the key before passing to cert().

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.