Skip to content

yultyyev/better-auth-firestore

Repository files navigation

better-auth-firestore

npm version CI License: MIT

Firestore (Firebase Admin SDK) adapter for Better Auth. A drop-in replacement for the Auth.js Firebase adapter with matching data shape.


Installation

npm

npm install @yultyyev/better-auth-firestore firebase-admin better-auth

pnpm

pnpm add @yultyyev/better-auth-firestore firebase-admin better-auth

yarn

yarn add @yultyyev/better-auth-firestore firebase-admin better-auth

bun

bun add @yultyyev/better-auth-firestore firebase-admin better-auth

Minimal usage

import { firestoreAdapter } from "@yultyyev/better-auth-firestore";
import { createAuth } from "better-auth";
import { getFirestore } from "firebase-admin/firestore";

export const auth = createAuth({
  adapter: firestoreAdapter({ firestore: getFirestore() })
});

Quick start

import { betterAuth } from "better-auth";
import { firestoreAdapter, initFirestore } from "@yultyyev/better-auth-firestore";
import { cert } from "firebase-admin/app";

const firestore = initFirestore({
	credential: cert({
		projectId: process.env.FIREBASE_PROJECT_ID!,
		clientEmail: process.env.FIREBASE_CLIENT_EMAIL!,
		privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n"),
	}),
	projectId: process.env.FIREBASE_PROJECT_ID!,
	name: "better-auth",
});

export const auth = betterAuth({
	// ... your Better Auth options
	database: firestoreAdapter({
		firestore,
		namingStrategy: "default", // or "snake_case"
		collections: {
			// users: "users",
			// sessions: "sessions",
			// accounts: "accounts",
			// verificationTokens: "verificationTokens",
		},
	}),
});

Firebase Setup

1. Create a new Firebase project

  1. Go to Firebase Console
  2. Click "Add project" or "Create a project"
  3. Enter a project name and follow the setup wizard

2. Create Firestore Database

  1. In your Firebase project, go to BuildFirestore Database
  2. Click "Create database"
  3. Choose your preferred security rules mode (you can update rules later)
  4. Select a location for your database

3. Generate Service Account Key

  1. Go to Project Settings (gear icon) → Service Accounts
  2. Under "Firebase Admin SDK", click "Generate new private key"
  3. Download the JSON file (keep it secure - never commit it to version control)

4. Extract Environment Variables

From the downloaded service account JSON file, extract these values:

  • project_idFIREBASE_PROJECT_ID
  • client_emailFIREBASE_CLIENT_EMAIL
  • private_keyFIREBASE_PRIVATE_KEY (requires newline replacement - see Troubleshooting)

Alternative: You can use the JSON file directly by setting GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of your service account JSON file.

5. (Optional) Set up Security Rules

The adapter uses the Firebase Admin SDK (server-side), so Firestore security rules should deny direct client access. See Firestore Security Rules below.

Environment Variables

Required environment variables:

  • FIREBASE_PROJECT_ID - Your Firebase project ID
  • FIREBASE_CLIENT_EMAIL - Service account email from the JSON file
  • FIREBASE_PRIVATE_KEY - Service account private key (with newlines properly escaped)

Note: The FIREBASE_PRIVATE_KEY often contains literal \n characters in environment variables. See Troubleshooting for how to handle this.

Options

firestoreAdapter({
	firestore?: Firestore;
	namingStrategy?: "default" | "snake_case";
	collections?: { users?: string; sessions?: string; accounts?: string; verificationTokens?: string };
	debugLogs?: boolean | DBAdapterDebugLogOption;
});

Default collection names:

  • users: "users"
  • sessions: "sessions"
  • accounts: "accounts"
  • verificationTokens: "verification_tokens" (snake_case) or "verificationTokens" (default)

Debug logging

firestoreAdapter({
  firestore,
  debugLogs: true, // Enable verbose logging
});

Compatibility

Runtime Supported Notes
Node 18+ Recommended
Next.js (App Router) Server routes only
Cloud Functions / Cloud Run Provide FIREBASE_* creds
Vercel Edge / CF Workers Firestore Admin SDK not supported at Edge runtime

Collections & Data Shape

The adapter maintains the same data shape as Auth.js/NextAuth for seamless migration:

Collection Typical fields
users id, email, name, image, createdAt, updatedAt
accounts provider, providerAccountId, userId, access_token, refresh_token
sessions sessionToken, userId, expires
verificationTokens identifier, token, expires

Defaults: Collections default to users, sessions, accounts, verification_tokens (snake_case) / verificationTokens (default). See Options to customize collection names.

Minimal Firestore Security Rules (server/admin only)

Since this adapter uses the Firebase Admin SDK (server-side), Firestore security rules should deny direct client access:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Why this vs Auth.js Firebase adapter?

Feature Better Auth Firestore Auth.js Firebase Adapter
Status ✅ Active development Now maintained by Better Auth team (announcement)
Firebase Admin SDK ✅ Uses Admin SDK ✅ Uses Admin SDK
Data shape compatibility ✅ Matching shape, migration-free -
Drop-in replacement ✅ Yes -

This adapter is the Better Auth-native solution for Firestore users, recommended for new projects.

Migration from Auth.js/NextAuth

For complete migration steps, see the Better Auth NextAuth Migration Guide, which covers route handlers, client setup, and server-side session handling.

Adapter-Specific Migration

This adapter uses the same default collection names and field names as Auth.js Firebase adapter, making it a drop-in replacement for the database adapter portion of your migration:

  • Collection names: users, sessions, accounts, verificationTokens (same as Auth.js)
  • Field names: sessionToken, userId, providerAccountId, etc. (same as Auth.js)
  • Data shape: Identical, so no data migration scripts needed

Simply replace your Auth.js Firebase adapter with this one:

// Before (Auth.js)
import { FirestoreAdapter } from "@auth/firebase-adapter";

// After (Better Auth)
import { firestoreAdapter } from "@yultyyev/better-auth-firestore";

// Same Firestore instance, same collections, same data shape
export const auth = betterAuth({
  database: firestoreAdapter({ firestore }),
});

If you were using custom collection names with Auth.js, you can override them:

firestoreAdapter({
	firestore,
	collections: {
		accounts: "authjs_accounts", // or whatever custom names you were using
		// ... other overrides
	},
});

Recipes

Use snake_case collections

firestoreAdapter({
  firestore,
  namingStrategy: "snake_case",
});

Keep Auth.js collection names (no data migration)

firestoreAdapter({
  firestore,
  collections: {
    accounts: "accounts", // or your custom collection names
    // ... other overrides
  },
});

Usage with Next.js (App Router)

// app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth";

export const { GET, POST } = toNextJsHandler(auth);

Usage in Node.js script

import { firestoreAdapter } from "@yultyyev/better-auth-firestore";
import { createAuth } from "better-auth";
import { initializeApp, cert } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";

const app = 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 = createAuth({
  adapter: firestoreAdapter({ firestore: getFirestore(app) }),
});

Testing with Firestore Emulator

# start emulator (example)
docker run -d --rm \
	--name auth-firestore \
	-p 8080:8080 \
	google/cloud-sdk:emulators gcloud beta emulators firestore start \
	--host-port=0.0.0.0:8080

export FIRESTORE_EMULATOR_HOST=localhost:8080
pnpm vitest run

Troubleshooting

Error: FIREBASE_PRIVATE_KEY has literal \n

Symptom: Authentication fails or you see errors about invalid private key format.

Fix: Environment variables often store newlines as literal \n strings. Replace them at runtime:

privateKey: process.env.FIREBASE_PRIVATE_KEY!.replace(/\\n/g, "\n")

Error: Requests hang on local dev

Symptom: Firebase Admin SDK requests hang or time out during local development.

Fix: Use the Firestore Emulator and set FIRESTORE_EMULATOR_HOST=localhost:8080 before running your app. See Testing with Firestore Emulator for setup instructions.

Related Links

Build

pnpm build

License

MIT.