Skip to main content
Technology & EngineeringAuth Services234 lines

Firebase Auth

Build with Firebase Authentication for user sign-in. Use this skill when the

Quick Summary33 lines
You are an auth specialist who integrates Firebase Authentication into projects.
Firebase Auth provides drop-in authentication with email/password, social
providers, phone auth, and anonymous auth — with client SDKs and server-side
token verification via the Admin SDK.

## Key Points

- Use `onAuthStateChanged` for reactive auth state — don't check `currentUser` directly on load
- Verify ID tokens server-side for API routes — never trust client-side auth alone
- Use custom claims for authorization (admin, plan, roles) — they're embedded in the token
- Force token refresh after setting custom claims — tokens are cached for up to 1 hour
- Use `signInWithRedirect` on mobile — popups are blocked on many mobile browsers
- Clean up auth state listeners in useEffect return functions
- Trusting `auth.currentUser` on page load — it's null until auth initializes
- Not verifying ID tokens server-side — client tokens can be spoofed
- Storing sensitive data in custom claims — they're visible in the JWT
- Using custom claims for frequently changing data — token refresh is needed
- Not handling the loading state — auth state takes a moment to initialize
- Creating custom session management when Firebase handles token refresh

## Quick Example

```bash
npm install firebase
npm install firebase-admin  # Server-side
```

```typescript
import { signOut } from 'firebase/auth';
await signOut(auth);
```
skilldb get auth-services-skills/Firebase AuthFull skill: 234 lines
Paste into your CLAUDE.md or agent config

Firebase Authentication Integration

You are an auth specialist who integrates Firebase Authentication into projects. Firebase Auth provides drop-in authentication with email/password, social providers, phone auth, and anonymous auth — with client SDKs and server-side token verification via the Admin SDK.

Core Philosophy

Client-side first

Firebase Auth runs primarily on the client. The SDK handles the OAuth flows, token refresh, and auth state persistence. Your server verifies ID tokens but doesn't manage sessions — Firebase handles that automatically.

Provider-agnostic

Firebase Auth supports email/password, Google, Apple, GitHub, Twitter, Facebook, phone OTP, and anonymous auth through a consistent API. Adding a provider is enabling it in the console and adding a few lines of code.

Custom claims for authorization

Firebase Auth handles authentication (who is this user). For authorization (what can they do), set custom claims on the user token — admin, plan, orgId — and check them client-side or in security rules.

Setup

Install

npm install firebase
npm install firebase-admin  # Server-side

Initialize (client)

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const app = initializeApp({
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
});

export const auth = getAuth(app);

Initialize (server)

import { initializeApp, cert } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';

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 adminAuth = getAuth(app);

Key Techniques

Email/password auth

import { createUserWithEmailAndPassword, signInWithEmailAndPassword, updateProfile } from 'firebase/auth';

// Sign up
const { user } = await createUserWithEmailAndPassword(auth, email, password);
await updateProfile(user, { displayName: name });

// Sign in
const { user } = await signInWithEmailAndPassword(auth, email, password);

// Password reset
import { sendPasswordResetEmail } from 'firebase/auth';
await sendPasswordResetEmail(auth, email);

OAuth providers

import {
  signInWithPopup, signInWithRedirect, getRedirectResult,
  GoogleAuthProvider, GithubAuthProvider, OAuthProvider
} from 'firebase/auth';

// Google
const googleProvider = new GoogleAuthProvider();
const { user } = await signInWithPopup(auth, googleProvider);

// GitHub
const githubProvider = new GithubAuthProvider();
const { user } = await signInWithPopup(auth, githubProvider);

// Apple
const appleProvider = new OAuthProvider('apple.com');
appleProvider.addScope('email');
const { user } = await signInWithPopup(auth, appleProvider);

// Redirect flow (mobile-friendly)
await signInWithRedirect(auth, googleProvider);
// On page load:
const result = await getRedirectResult(auth);
if (result) {
  const user = result.user;
}

Auth state listener

import { onAuthStateChanged, type User } from 'firebase/auth';

// React context pattern
function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setUser(user);
      setLoading(false);
    });
    return unsubscribe;
  }, []);

  return (
    <AuthContext.Provider value={{ user, loading }}>
      {children}
    </AuthContext.Provider>
  );
}

ID token verification (server-side)

// API route — verify Firebase ID token
export async function GET(req: Request) {
  const authHeader = req.headers.get('authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return new Response('Unauthorized', { status: 401 });
  }

  const token = authHeader.split('Bearer ')[1];

  try {
    const decoded = await adminAuth.verifyIdToken(token);
    const userId = decoded.uid;

    const data = await db.query.posts.findMany({
      where: eq(posts.authorId, userId),
    });

    return Response.json(data);
  } catch {
    return new Response('Invalid token', { status: 401 });
  }
}

// Client-side — send token with requests
const token = await auth.currentUser?.getIdToken();
const res = await fetch('/api/posts', {
  headers: { Authorization: `Bearer ${token}` },
});

Custom claims

// Server-side: set custom claims
await adminAuth.setCustomUserClaims(userId, {
  admin: true,
  plan: 'pro',
  orgId: 'org_123',
});

// Client-side: read custom claims
const token = await auth.currentUser?.getIdTokenResult();
const isAdmin = token?.claims.admin === true;
const plan = token?.claims.plan as string;

// Force token refresh to get updated claims
await auth.currentUser?.getIdToken(true);

Phone auth

import { signInWithPhoneNumber, RecaptchaVerifier } from 'firebase/auth';

// Set up reCAPTCHA
const recaptcha = new RecaptchaVerifier(auth, 'recaptcha-container', {
  size: 'invisible',
});

// Send verification code
const confirmationResult = await signInWithPhoneNumber(auth, '+1234567890', recaptcha);

// Verify code
const { user } = await confirmationResult.confirm(userEnteredCode);

Sign out

import { signOut } from 'firebase/auth';
await signOut(auth);

Best Practices

  • Use onAuthStateChanged for reactive auth state — don't check currentUser directly on load
  • Verify ID tokens server-side for API routes — never trust client-side auth alone
  • Use custom claims for authorization (admin, plan, roles) — they're embedded in the token
  • Force token refresh after setting custom claims — tokens are cached for up to 1 hour
  • Use signInWithRedirect on mobile — popups are blocked on many mobile browsers
  • Clean up auth state listeners in useEffect return functions

Anti-Patterns

  • Trusting auth.currentUser on page load — it's null until auth initializes
  • Not verifying ID tokens server-side — client tokens can be spoofed
  • Storing sensitive data in custom claims — they're visible in the JWT
  • Using custom claims for frequently changing data — token refresh is needed
  • Not handling the loading state — auth state takes a moment to initialize
  • Creating custom session management when Firebase handles token refresh

Install this skill directly: skilldb add auth-services-skills

Get CLI access →