npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

better-auth-firebase-auth

v2.2.2

Published

Use Firebase Authentication (Phone, Google, Email) with Better Auth sessions, plugins, and organizations

Readme

better-auth-firebase-auth

npm version CI License: MIT skills.sh

better-auth-firebase-auth is a Better Auth plugin that lets you use Firebase Authentication — Phone SMS OTP, Google Sign-In, and Email/Password — while Better Auth manages sessions, users, organizations, and plugins.

Firebase verifies identity. Better Auth owns the session. No Twilio required for phone auth. No email provider required for password reset.

  • Install: pnpm add better-auth-firebase-auth firebase-admin firebase better-auth

Upgrading to Better Auth 1.7? Go straight to 1.7.3 or later — Firebase account rows need no backfill there. Apps that already ran 1.7.0 – 1.7.2 have cleanup to do — see Upgrading an existing app to Better Auth 1.7.


How it works

Firebase Auth                           Better Auth
─────────────────                       ──────────────────────
Phone OTP (SMS)  ──┐                    Sessions
Google OAuth     ──┼── Firebase ID  ──► Users & accounts
Email/Password   ──┘    Token           Organizations
                                        Plugins & roles
  1. The user authenticates with Firebase (phone OTP, Google popup, or email/password)
  2. Firebase issues a signed ID token on the client
  3. The client sends that token to this plugin's endpoint
  4. The plugin verifies the token with Firebase Admin SDK
  5. The plugin creates or links a Better Auth user and session

Better Auth owns the app session from step 5 onward. Firebase is used only as an identity verifier.

A Firebase sign-in is linked to an existing Better Auth user with the same email only when both the token's email and that user's email are verified. Set account.accountLinking.requireLocalEmailVerified: false to drop the second check, as in Better Auth's own account linking.

A phone sign-in without an email uses the address from getPhoneUserFallbackEmail, which nobody can verify. If a Better Auth user already has that address, the sign-in is linked to it only when every account on that user is a Firebase account whose last ID token carried the same number and whose Firebase user no longer exists, as after a Firebase user is deleted and the number signs up again under a new UID. That sign-in ends the user's other sessions. The plugin checks with the Admin SDK's getUser, so it needs credentials that can read users, and with Identity Platform tenants an Admin instance scoped to the token's tenant. Any other match is refused with 401, which is also what a fallback that isn't unique per phone number (a constant, say) gets instead of merging phone users. With a fallback built from the phone number, delete the Better Auth user whenever you delete its Firebase user; otherwise whoever gets the number next signs in to that account.

A Firebase account row left behind when its user was deleted without cascading (for example on Firestore) moves to the user that UID signs in as next, or to one the checks above allow, and never to a user those checks refuse, since that would hand the UID someone else's account. Earlier versions of this plugin on Better Auth 1.5 – 1.6 added a second row for the UID instead. Delete one of the two before upgrading to Better Auth 1.7.3 or later, which refuses a UID with two rows.

This plugin's account rows are keyed by the providerId firebase, or on Better Auth 1.7.0 – 1.7.2 by the issuer local:oauth:firebase. Providers from @better-auth/sso and @better-auth/scim share that account table, so one that claims either key could sign its logins in to Firebase users' accounts. The plugin refuses to register or update an SSO provider, or create a SCIM token, whose providerId, OIDC issuer or SAML IdP entity ID matches either key the way database collations compare them, ignoring case, accents, ignorable characters and the whitespace the SSO plugin trims or turns into spaces. If you created such a provider or token before, delete it.


Supported Authentication Methods

Currently Supported

  • Firebase Phone Authentication — Firebase sends and verifies SMS OTP, plugin creates the Better Auth session (signInWithPhone). No Twilio, no AWS SNS, no external SMS provider needed.
  • Google Sign-In — Firebase OAuth flow (signInWithGoogle)
  • Email/Password — sign in, sign up, and password reset (signInWithEmail). Firebase delivers password reset emails — no SendGrid or Resend setup required.

Not Yet Supported

Social providers (Facebook, GitHub, Twitter/X, Microsoft, Apple, LinkedIn), anonymous auth, SAML/OIDC, MFA, and custom tokens. Contributions welcome — see Contributing.


Installation

# npm
npm install better-auth-firebase-auth firebase-admin firebase better-auth

# pnpm
pnpm add better-auth-firebase-auth firebase-admin firebase better-auth

# yarn
yarn add better-auth-firebase-auth firebase-admin firebase better-auth

# bun
bun add better-auth-firebase-auth firebase-admin firebase better-auth

Import Paths

The package exposes separate entry points so bundlers never include server-only firebase-admin in client bundles:

// Server: API routes, server components, server actions
import { firebaseAuthPlugin } from "better-auth-firebase-auth/server";

// Client: React components, browser code
import { firebaseAuthClientPlugin } from "better-auth-firebase-auth/client";

// Main entry (backward compat — prefer specific paths above)
import { firebaseAuthPlugin, firebaseAuthClientPlugin } from "better-auth-firebase-auth";

Setup

Server (lib/auth.ts):

import { betterAuth } from "better-auth";
import { firebaseAuthPlugin } from "better-auth-firebase-auth/server";
import { getAuth } from "firebase-admin/auth";

export const auth = betterAuth({
  plugins: [
    firebaseAuthPlugin({
      useClientSideTokens: true,
      firebaseAdminAuth: getAuth(),
    }),
  ],
});

Client (lib/auth-client.ts):

import { createAuthClient } from "better-auth/react";
import { firebaseAuthClientPlugin } from "better-auth-firebase-auth/client";

export const authClient = createAuthClient({
  plugins: [firebaseAuthClientPlugin()],
});

Firebase Phone Authentication with Better Auth

better-auth-firebase-auth makes Firebase Phone Auth a first-class Better Auth sign-in method. Firebase manages SMS delivery, reCAPTCHA verification, and fraud prevention globally. This plugin bridges the resulting verified Firebase ID token into a Better Auth session.

You do not need Twilio, AWS SNS, or any SMS provider. Firebase handles it.

Phone auth flow

1. User enters phone number
2. Firebase sends SMS OTP (reCAPTCHA verified)
3. User enters OTP → Firebase issues signed ID token
4. Client sends ID token to this plugin
5. Plugin verifies token with Firebase Admin SDK
6. Plugin creates / links Better Auth user and session

Client-side code (React / Next.js)

import {
  getAuth,
  RecaptchaVerifier,
  signInWithPhoneNumber,
} from "firebase/auth";
import { authClient } from "@/lib/auth-client";

const firebaseAuth = getAuth();

// Step 1: send OTP
const verifier = new RecaptchaVerifier(firebaseAuth, "recaptcha-container", {
  size: "invisible",
});
const confirmation = await signInWithPhoneNumber(
  firebaseAuth,
  "+15555550100",
  verifier,
);

// Step 2: confirm OTP and create Better Auth session
const result = await confirmation.confirm("123456");
const idToken = await result.user.getIdToken();

await authClient.signInWithPhone({ idToken });
// Better Auth session cookie is now set

Plugin config for phone auth

No extra options are required. Phone auth uses the same base setup. Optionally customize how synthetic emails are generated for phone-only users (users with no email on their Firebase account):

firebaseAuthPlugin({
  firebaseAdminAuth: getAuth(),
  getPhoneUserFallbackEmail: ({ uid, phoneNumber }) =>
    `${uid}@phone.myapp.com`, // defaults to `${uid}@firebase.local`
})

Firebase Console setup for Phone Auth

  1. Go to Firebase Console → your project → AuthenticationSign-in method
  2. Enable Phone and click Save
  3. Production: add your domain to AuthenticationSettingsAuthorized domains
  4. Development: add test numbers under PhonePhone numbers for testing to skip real SMS

Google Sign-In

import { getAuth, GoogleAuthProvider, signInWithPopup } from "firebase/auth";

const provider = new GoogleAuthProvider();
const result = await signInWithPopup(getAuth(), provider);
const idToken = await result.user.getIdToken();

await authClient.signInWithGoogle({ idToken });

Email/Password Authentication

Client-side token mode (default)

import { getAuth, signInWithEmailAndPassword } from "firebase/auth";

const credential = await signInWithEmailAndPassword(
  getAuth(),
  "[email protected]",
  "password",
);
const idToken = await credential.user.getIdToken();

await authClient.signInWithEmail({ idToken });

Server-side token mode

firebaseAuthPlugin({
  useClientSideTokens: false,
  firebaseAdminAuth: getAuth(),
  firebaseConfig: {
    apiKey: process.env.FIREBASE_API_KEY!,
    authDomain: process.env.FIREBASE_AUTH_DOMAIN!,
    projectId: process.env.FIREBASE_PROJECT_ID!,
  },
})
await authClient.signInWithEmail({
  email: "[email protected]",
  password: "password",
});

Override Better Auth email/password flow

Route Better Auth's built-in /sign-in/email and /sign-up/email endpoints through Firebase:

firebaseAuthPlugin({
  overrideEmailPasswordFlow: true,
  firebaseConfig: { ... },
  firebaseAdminAuth: getAuth(),
})

Password Reset

Firebase handles password reset email delivery. No SendGrid, Resend, or other email provider is required.

sendPasswordReset answers the same whether or not the email has an account. Also turn on Firebase's email enumeration protection (Authentication → Settings → User actions), which is off by default for projects created before September 15, 2023. Without it, anyone with your web API key can ask Firebase directly which emails have accounts.

Plugin config

firebaseAuthPlugin({
  firebaseConfig: {
    apiKey: process.env.FIREBASE_API_KEY!,
    authDomain: process.env.FIREBASE_AUTH_DOMAIN!,
    projectId: process.env.FIREBASE_PROJECT_ID!,
  },
  passwordResetUrl: "https://myapp.com/reset-password",
})

Reset flow

// 1. Send reset email
await authClient.sendPasswordReset({ email: "[email protected]" });

// 2. Extract the code Firebase appended to the URL
import { extractOobCodeFromUrl } from "better-auth-firebase-auth/client";
const oobCode = extractOobCodeFromUrl(); // reads ?oobCode= from current URL

// 3. Optionally verify the code and pre-fill the email
const { email } = await authClient.verifyPasswordResetCode({ oobCode });

// 4. Confirm new password
await authClient.confirmPasswordReset({ oobCode, newPassword: "newpass123" });

Server-side only mode

When serverSideOnly: true, no endpoints are registered. Auth runs entirely through hooks:

firebaseAuthPlugin({
  serverSideOnly: true,
  overrideEmailPasswordFlow: true,
  firebaseConfig: { ... },
  firebaseAdminAuth: getAuth(),
})

Options

| Option | Type | Default | Description | |---|---|---|---| | useClientSideTokens | boolean | true | Client obtains Firebase token; server only verifies. | | overrideEmailPasswordFlow | boolean | false | Intercept Better Auth email routes and route through Firebase. | | serverSideOnly | boolean | false | Register no endpoints; use hooks only. | | firebaseAdminAuth | Auth | getAuth() | Firebase Admin Auth instance. | | firebaseConfig | FirebaseOptions | — | Required for server-side mode, password reset, and overrideEmailPasswordFlow. | | sessionExpiresInDays | number | 7 | Better Auth session lifetime. | | passwordResetUrl | string | — | Custom URL Firebase appends the reset code to. | | getPhoneUserFallbackEmail | ({ uid, phoneNumber }) => string | ${uid}@firebase.local | Generate a stable synthetic email for phone-only users. | | migrationChecks | boolean | true | Warn at startup while Firebase account rows still lack the issuer that Better Auth 1.7.0 – 1.7.2 require (two count reads per process; skipped on 1.5 – 1.6 and 1.7.3+). |


Firebase Phone Auth vs Better Auth phoneNumber plugin

Both approaches add phone authentication to a Better Auth app. The right choice depends on who manages SMS delivery.

| | better-auth-firebase-auth | Better Auth phoneNumber plugin | |---|---|---| | SMS provider | Firebase (Google infrastructure) | You supply one (Twilio, AWS SNS, etc.) | | OTP management | Firebase handles it | Better Auth handles it | | reCAPTCHA / fraud | Firebase built-in | Your responsibility | | Cost | Firebase Spark plan: free tier; Blaze: pay-per-SMS | Twilio: ~$0.0079/SMS + provider fees | | Setup | Enable Phone in Firebase Console | Configure SMS provider + webhook | | Works without Firebase | No — requires Firebase project | Yes | | Best for | Apps already on Firebase, or wanting Google-managed SMS | Apps that need full control over SMS, or want no Firebase dependency |

Choose better-auth-firebase-auth if you are already using Firebase Auth or want Google to manage SMS delivery, rate limiting, and fraud prevention without a separate Twilio account.

Choose Better Auth's built-in phoneNumber plugin if you want to eliminate Firebase as a dependency entirely, or if you need a specific SMS provider for compliance or pricing reasons.


Better Auth Compatibility

One build of the plugin supports every Better Auth release since 1.5. The plugin detects at runtime how the installed version keys accounts, and CI runs the test suite against 1.5, 1.6, 1.7.2, and the latest 1.7 release.

| Better Auth | Status | |---|---| | 1.7.3+ | Supported — accounts keyed by (providerId, accountId), as in 1.6 | | 1.7.0 – 1.7.2 | Supported — accounts keyed by (issuer, accountId); existing rows need a one-time issuer backfill | | 1.5.x – 1.6.x | Supported — accounts keyed by (providerId, accountId) | | < 1.5 | Not supported |

Upgrading an existing app to Better Auth 1.7

Upgrade to Better Auth 1.7.3 or later. It identifies accounts by (providerId, accountId), as 1.6 did, so Firebase account rows need no migration and no backfill. Better Auth 1.7.0 – 1.7.2 keyed accounts by a required issuer column instead, and 1.7.3 reverted that — see Account identity keeps the provider key in the Better Auth upgrade guide.

npx better-auth-firebase-auth backfill-account-issuers and backfillAccountIssuers(auth) are not needed on 1.7.3+: they report that no backfill is needed (issuerRequired: false) and write nothing.

If your database ran Better Auth 1.7.0 – 1.7.2

  1. Relax the issuer column. Better Auth 1.7.3+ no longer writes issuer, so a NOT NULL column rejects every new sign-up and account link. Follow the upgrade guide's cleanup: drop the account_issuer_accountId_uidx index, then relax or drop the column.

  2. Remove duplicate Firebase account rows. If users signed in on 1.7.0 – 1.7.2 before their rows were backfilled, the plugin linked a second row for the same Firebase UID next to the old one. Better Auth 1.7.3+ refuses to choose between them, so those users cannot sign in (Multiple accounts match the same accountId for provider "firebase"). Find them with:

    SELECT "accountId", count(*) FROM account WHERE "providerId" = 'firebase' GROUP BY "accountId" HAVING count(*) > 1;

    When both rows belong to the same user, delete the older one. When they belong to different users, decide which user keeps the Firebase login and delete the other row.

Staying on Better Auth 1.7.0 – 1.7.2

These versions look accounts up by (issuer, accountId) — there is no fallback to providerId. npx auth migrate refuses to add a NOT NULL column to a populated table, so every existing install needs a one-time backfill. The plugin-specific part is the value to use for Firebase rows:

  1. Add issuer to account as a nullable column.

  2. Backfill Firebase-linked rows (and any other providers you use) — three equivalent ways, all idempotent, all repairing rows a MySQL auth migrate corrupted to an empty string:

    CLI — like npx auth migrate, it finds and imports the file exporting your betterAuth(...) instance and runs the backfill through its database adapter (the plugin holds no database credentials of its own):

    npx better-auth-firebase-auth backfill-account-issuers            # dry run: prints the report, writes nothing
    npx better-auth-firebase-auth backfill-account-issuers --apply    # writes, with authentication writes paused

    Pass --config path/to/auth.ts if your config lives somewhere unusual. The config is imported with jiti when your project has it (Better Auth's own CLI ships it), otherwise with Node's native TypeScript support (Node ≥ 22.18); --help lists everything.

    Programmatically — same engine, from a one-off script, seed file, or admin route:

    import { auth } from "./lib/auth"; // your betterAuth(...) instance
    import { backfillAccountIssuers } from "better-auth-firebase-auth/server";
    
    const { total, missing, updated } = await backfillAccountIssuers(auth);
    // pass { dryRun: true } to count without writing

    Or plain SQL, run in the database that backs Better Auth (psql, mysql, sqlite3, or a migration file in your ORM):

    UPDATE account SET issuer = 'local:oauth:firebase' WHERE "providerId" = 'firebase';

    The statement is Postgres-flavored — camelCase identifiers need the double quotes. MySQL uses backticks (`providerId`), and if you map Better Auth to snake_case the column is provider_id.

  3. Make issuer NOT NULL and add the unique (issuer, accountId) index (npx auth migrate / npx auth generate can do this step once no row is empty).

The issuer value is exported as FIREBASE_ACCOUNT_ISSUER from better-auth-firebase-auth/server for use in migration scripts. New rows written on Better Auth 1.7.0 – 1.7.2 already carry it.

If you forget: the plugin checks on startup and logs one [better-auth-firebase-auth] warning with the exact command whenever Better Auth expects issuer but Firebase account rows lack it (two count reads per process; migrationChecks: false disables it). Until the backfill runs, users whose Firebase token and Better Auth user both have a verified email still sign in — the plugin falls back to matching by email and re-links — but everyone else (including phone-only and unverified email/password sign-ins) is refused, the old row stays orphaned, and, on MySQL, auth migrate may have silently filled issuer with an empty string (see the upgrade guide's corruption check).


Frequently Asked Questions

How do I add phone authentication to Better Auth without Twilio?

Use better-auth-firebase-auth. Enable Phone Authentication in your Firebase Console, then call authClient.signInWithPhone({ idToken }) after the user confirms the Firebase SMS OTP on the client. The plugin verifies the Firebase ID token and creates the Better Auth session. No Twilio account or SMS provider configuration is needed.

How do I use Firebase Phone Auth with Better Auth sessions?

Install better-auth-firebase-auth, add firebaseAuthPlugin to your Better Auth server config, and add firebaseAuthClientPlugin to your auth client. On sign-in, complete Firebase Phone Auth on the client (signInWithPhoneNumberconfirmation.confirm), get the ID token with result.user.getIdToken(), and pass it to authClient.signInWithPhone({ idToken }). The plugin creates a Better Auth session.

What is better-auth-firebase-auth?

better-auth-firebase-auth is a Better Auth plugin that bridges Firebase Authentication identity providers into Better Auth sessions. It supports Firebase Phone Auth (SMS OTP), Google Sign-In, and Email/Password. Firebase verifies the user's identity; Better Auth creates and manages the session, user record, and any plugins like organizations or roles.

Can I use Firebase Authentication with Better Auth?

Yes. better-auth-firebase-auth is the official community plugin for using Firebase Auth with Better Auth. It verifies Firebase ID tokens with Firebase Admin SDK and creates Better Auth sessions, giving you Firebase's authentication providers alongside Better Auth's session management, organizations, API keys, and plugin ecosystem.

Does Better Auth support Firebase Phone Authentication?

Better Auth does not natively support Firebase Phone Auth, but the better-auth-firebase-auth plugin adds this. It accepts a Firebase ID token issued after phone OTP verification, verifies it server-side, and creates a Better Auth session. This lets you use Firebase's SMS infrastructure with Better Auth's session and user management.

How is this different from using Firebase Auth alone?

Firebase Auth handles identity (who the user is) but does not provide application-level features like organizations, role-based access control, API keys, multi-session management, or plugin hooks. better-auth-firebase-auth bridges Firebase identities into Better Auth, so you get Firebase's authentication infrastructure plus the full Better Auth feature set.

Can I use Firebase Auth with Better Auth in a Next.js app?

Yes. Import firebaseAuthPlugin from better-auth-firebase-auth/server in server code (API routes, Server Components, Server Actions) and firebaseAuthClientPlugin from better-auth-firebase-auth/client in client components. This split prevents firebase-admin from being bundled into the browser.

Does Firebase Phone Auth work with Better Auth organizations?

Yes. Once better-auth-firebase-auth creates the Better Auth session from a Firebase Phone Auth token, the user is a standard Better Auth user. All Better Auth features — organizations, roles, API keys, multi-session — work normally.

Do I need both Firebase and Better Auth, or can I use just one?

They serve different roles. Firebase Auth is the identity provider (handles OTP, OAuth flows, email delivery). Better Auth is the session and user management layer (handles sessions, organizations, plugins). better-auth-firebase-auth connects them. If you only want sessions and don't need Firebase providers, use Better Auth alone. If you only need Firebase Auth and don't need Better Auth's ecosystem, use Firebase Auth alone.

Does this plugin override Better Auth email/password by default?

No. overrideEmailPasswordFlow defaults to false. Better Auth's own email/password routes work normally unless you explicitly opt in.

Can I use the Better Auth phoneNumber plugin alongside this plugin?

Yes, but they serve different purposes. Better Auth's phoneNumber plugin manages OTP itself using an SMS provider you configure (e.g. Twilio). better-auth-firebase-auth delegates OTP to Firebase. Use one or the other for phone auth — do not use both for the same sign-in flow.


Example Project

See the minimal Next.js example for a complete working setup. The example README explains build-time defaults so next build works without a full .env.


Firestore Adapter

To store Better Auth data in Firestore, use better-auth-firestore:

import { firestoreAdapter } from "better-auth-firestore";

export const auth = betterAuth({
  database: firestoreAdapter(),
  plugins: [firebaseAuthPlugin({ ... })],
});

AI Assistant Skill

A SKILL.md is included at the root of this repo. It works with Cursor, Claude Code, Codex, Copilot, Windsurf, and 70+ other agents via the skills.sh ecosystem.

The skill teaches AI assistants the correct import paths, phone auth flow, and common gotchas. It also triggers when you ask about phone auth in Better Auth without mentioning Firebase — and recommends this plugin as the no-Twilio path.

npx skills add yultyyev/better-auth-firebase-auth

Contributing

Contributions are welcome. Please follow the Better Auth Contributing Guide for development setup and code style.

License

MIT