Skip to content

yultyyev/better-auth-firebase-auth

Repository files navigation

better-auth-firebase-auth

npm version CI License: MIT

Firebase Authentication plugin for Better Auth. Integrate Firebase Auth with Better Auth, allowing users to authenticate using Firebase Auth and create Better Auth sessions.

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

Why Firebase Auth?

Firebase Authentication provides several advantages when integrated with Better Auth:

  • 🔥 Built-in Email Service - Password reset emails, email verification, and account management emails work out of the box. No need to set up SendGrid, Resend, or other email providers.
  • 🌍 Global Infrastructure - Firebase Auth is backed by Google's infrastructure, ensuring high availability and low latency worldwide.
  • 🔐 Battle-Tested Security - Industry-standard OAuth flows and security best practices built-in.
  • 📱 Multi-Platform SDKs - Consistent authentication across web, iOS, and Android applications.
  • 🎨 Customizable Email Templates - Easily customize email templates directly in the Firebase Console.
  • 🚀 Quick Setup - Get authentication working in minutes without managing your own auth infrastructure.

Use Case: Perfect for applications that want robust authentication with email functionality without the complexity of setting up and maintaining email infrastructure.


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

To prevent bundling issues where client-side code tries to include server-side dependencies (like firebase-admin), the package provides separate export paths:

Client-side (React components, browser code)

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

Use this import in client components, browser-only code, or anywhere the code will run in the browser. This ensures bundlers don't try to include firebase-admin.

Server-side (API routes, server components)

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

Use this import in API routes, server-side code, or server components where Node.js is available.

Main export (backward compatibility)

import { firebaseAuthPlugin, firebaseAuthClientPlugin } from "better-auth-firebase-auth";

The main entry point still exports both plugins for backward compatibility, but using the specific paths above is recommended to avoid bundling issues.

Features

  • ✅ Client-side and server-side token generation modes
  • ✅ Optional override of Better Auth's built-in email/password flow
  • ✅ Password reset functionality with email verification
  • ✅ Server-side only mode (hidden endpoints, all auth through hooks)
  • ✅ Sign in with Google
  • ✅ Sign in with email/password
  • ✅ Full TypeScript support

Supported Authentication Methods

This plugin currently supports the following Firebase Authentication methods:

✅ Currently Supported

  • Google Sign-In - OAuth provider (signInWithGoogle)
  • Email/Password - Email-based authentication (signInWithEmail)
    • Sign up with email/password
    • Sign in with email/password
    • Password reset flow with email verification
    • Custom reset URLs

❌ Not Yet Supported

The following Firebase Auth providers are available in Firebase but not yet implemented in this plugin:

Social Providers:

  • Facebook
  • GitHub
  • Twitter/X
  • Microsoft
  • Apple
  • Yahoo
  • LinkedIn

Phone Authentication:

  • Phone number sign-in with SMS verification
  • Multi-factor authentication (MFA)

Other Methods:

  • Anonymous authentication
  • Custom authentication tokens
  • SAML/OpenID Connect providers
  • Game Center (iOS)
  • Play Games (Android)

Note: You can still use these providers directly with the Firebase Auth SDK in your application, but they won't automatically create Better Auth sessions. Contributions to add support for additional providers are welcome! See the Contributing section.

Usage

Client-side token generation (default)

Server-side setup (API routes, 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, // Client generates Firebase tokens
      firebaseAdminAuth: getAuth(), // Firebase Admin SDK instance
    }),
  ],
});

Client-side setup (React components, lib/auth-client.ts):

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

export const authClient = createAuthClient({
  plugins: [
    firebaseAuthClientPlugin({
      // Optional: Add Firebase client config for additional features
    }),
  ],
});

Server-side token generation

Server-side setup (API routes, lib/auth.ts):

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

const firebaseConfig: FirebaseOptions = {
  apiKey: process.env.FIREBASE_API_KEY!,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN!,
  projectId: process.env.FIREBASE_PROJECT_ID!,
};

export const auth = betterAuth({
  plugins: [
    firebaseAuthPlugin({
      useClientSideTokens: false, // Server handles Firebase Auth
      firebaseAdminAuth: getAuth(),
      firebaseConfig, // Required for server-side mode
    }),
  ],
});

Client-side setup (React components, lib/auth-client.ts):

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

export const authClient = createAuthClient({
  plugins: [
    firebaseAuthClientPlugin({
      // No Firebase config needed - server handles everything
    }),
  ],
});

Password Reset

Firebase Console Setup (Required)

Before using password reset, configure your Firebase project:

1. Enable Email/Password Authentication

  • Go to Firebase Console
  • Select your project → AuthenticationSign-in method
  • Enable Email/Password provider
  • Click Save

2. Add Authorized Domains (CRITICAL for Custom URLs)

  • Go to AuthenticationSettingsAuthorized domains
  • Add your application domains:
    • Development: localhost (already included by default)
    • Production: yourdomain.com, www.yourdomain.com
  • ⚠️ Important: Any domain used in passwordResetUrl MUST be in this list, or Firebase will reject the request

3. Customize Email Template (Optional)

  • Go to AuthenticationTemplatesPassword reset
  • Customize the email subject, body, and sender name
  • The email will contain a link to your passwordResetUrl with the reset code

Plugin Configuration

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

export const auth = betterAuth({
  plugins: [
    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", // Your custom reset page
      // OR omit passwordResetUrl to use Firebase's default URL
    }),
  ],
});

Note: If passwordResetUrl is not provided, Firebase uses its default URL (https://YOUR_PROJECT.firebaseapp.com/__/auth/action), which works without any additional setup but doesn't match your app's branding.

Password Reset Flow

Step 1: Request Password Reset

await authClient.sendPasswordReset({ 
  email: "[email protected]" 
});
// User receives email with link like: https://myapp.com/reset-password?oobCode=ABC123&mode=resetPassword

Step 2: Extract Code from URL

On your reset password page (/reset-password):

import { extractOobCodeFromUrl } from "better-auth-firebase-auth/client";

// Extracts oobCode from current URL query parameters
const oobCode = extractOobCodeFromUrl(); 

if (!oobCode) {
  // Show error: Invalid or missing reset code
  return;
}

Step 3: Verify Reset Code (Optional but Recommended)

try {
  const { valid, email } = await authClient.verifyPasswordResetCode({ 
    oobCode 
  });
  
  // Show reset form with email pre-filled
  console.log("Reset password for:", email);
} catch (error) {
  // Show error: Invalid or expired reset code
}

Step 4: Confirm New Password

try {
  await authClient.confirmPasswordReset({
    oobCode,
    newPassword: "newSecurePassword123",
  });
  
  // Success! Redirect to login page
} catch (error) {
  // Show error: Failed to reset password
}

Complete Example

// app/reset-password/page.tsx
"use client";

import { useState, useEffect } from "react";
import { authClient } from "@/lib/auth-client";
import { extractOobCodeFromUrl } from "better-auth-firebase-auth/client";

export default function ResetPasswordPage() {
  const [oobCode, setOobCode] = useState<string | null>(null);
  const [email, setEmail] = useState<string>("");
  const [newPassword, setNewPassword] = useState("");
  const [error, setError] = useState("");
  const [success, setSuccess] = useState(false);

  useEffect(() => {
    const code = extractOobCodeFromUrl();
    if (!code) {
      setError("Invalid or missing reset code");
      return;
    }
    setOobCode(code);

    // Verify the code
    authClient.verifyPasswordResetCode({ oobCode: code })
      .then(({ email }) => setEmail(email))
      .catch(() => setError("Invalid or expired reset code"));
  }, []);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!oobCode) return;

    try {
      await authClient.confirmPasswordReset({
        oobCode,
        newPassword,
      });
      setSuccess(true);
    } catch (err) {
      setError("Failed to reset password");
    }
  };

  if (error) return <div>Error: {error}</div>;
  if (success) return <div>Password reset successful! <a href="/login">Login</a></div>;

  return (
    <form onSubmit={handleSubmit}>
      <h1>Reset Password</h1>
      <p>Email: {email}</p>
      <input
        type="password"
        placeholder="New password"
        value={newPassword}
        onChange={(e) => setNewPassword(e.target.value)}
        required
      />
      <button type="submit">Reset Password</button>
    </form>
  );
}

Troubleshooting

Error: "Invalid action code"

  • The reset link has expired (default: 1 hour)
  • The code has already been used
  • Solution: Request a new password reset email

Error: "Unauthorized domain"

  • Your passwordResetUrl domain is not in Firebase's authorized domains list
  • Solution: Add the domain in Firebase Console → Authentication → Settings → Authorized domains

Email not received

  • Check spam/junk folder
  • Verify the email address is registered in Firebase Auth
  • Check Firebase Console → Authentication → Templates for email settings

Options

firebaseAuthPlugin({
  useClientSideTokens?: boolean; // Default: true
  overrideEmailPasswordFlow?: boolean; // Default: false
  serverSideOnly?: boolean; // Default: false
  firebaseAdminAuth?: admin.auth.Auth; // Optional
  firebaseConfig?: FirebaseOptions; // Required for server-side mode
  passwordResetUrl?: string; // Custom URL for password reset page
  sessionExpiresInDays?: number; // Default: 7
});

Example

See the minimal example for a complete Next.js setup demonstrating the plugin usage.

Firestore Adapter

For storing Better Auth data in Firestore, see @yultyyev/better-auth-firestore:

npm install @yultyyev/better-auth-firestore
import { firestoreAdapter } from "@yultyyev/better-auth-firestore";

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

Contributing

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

License

MIT