/ astro-integrations / How to Integrate Firebase Auth with Astro: Complete Guide
astro-integrations 5 min read

How to Integrate Firebase Auth with Astro: Complete Guide

Step-by-step guide to integrating Firebase Auth with your Astro website. Setup, configuration, and best practices.

How to Integrate Firebase Auth with Astro: Complete Guide

Firebase Authentication provides backend services and SDKs for authenticating users in your application. It supports email/password, phone numbers, and popular federated identity providers like Google, Facebook, Twitter, and GitHub. When integrated with Astro, Firebase Auth enables secure user sessions that work across server-rendered pages and client-side interactions.

This guide covers a hybrid approach where Firebase handles authentication on the client side, and you verify sessions server-side using Firebase Admin SDK for protected routes and API endpoints.

Prerequisites

You will need:

  • Node.js 18+ installed
  • An existing Astro project with SSR enabled (v3.0+)
  • A Firebase project created at console.firebase.google.com
  • Firebase Authentication enabled in your project (Authentication > Sign-in method)
  • A Firebase service account key (Project Settings > Service accounts > Generate new private key)

Installation

Install both the Firebase client SDK (for browser-side auth) and the Admin SDK (for server-side verification):

npm install firebase firebase-admin

Add an SSR adapter:

npx astro add node

Configuration

Environment Variables

Add Firebase credentials to your .env file:

PUBLIC_FIREBASE_API_KEY=your_api_key
PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
PUBLIC_FIREBASE_PROJECT_ID=your-project-id

FIREBASE_ADMIN_PROJECT_ID=your-project-id
FIREBASE_ADMIN_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
FIREBASE_ADMIN_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_KEY_HERE\n-----END PRIVATE KEY-----\n"

The PUBLIC_ prefixed variables are safe for the client. The admin credentials must stay server-side only.

Client-Side Firebase Setup

Create a client-side Firebase configuration:

// src/lib/firebase-client.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: import.meta.env.PUBLIC_FIREBASE_API_KEY,
  authDomain: import.meta.env.PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.PUBLIC_FIREBASE_PROJECT_ID,
};

const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);

Server-Side Firebase Admin Setup

Initialize the Admin SDK for token verification:

// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';

const activeApps = getApps();

const app = activeApps.length === 0
  ? initializeApp({
      credential: cert({
        projectId: import.meta.env.FIREBASE_ADMIN_PROJECT_ID,
        clientEmail: import.meta.env.FIREBASE_ADMIN_CLIENT_EMAIL,
        privateKey: import.meta.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\n/g, '\n'),
      }),
    })
  : activeApps[0];

export const adminAuth = getAuth(app);

The getApps() check prevents reinitializing the app on hot reload during development.

Common Patterns

Client-Side Login Component

Create a login form that authenticates with Firebase and stores the session token in a cookie:

---
// src/pages/login.astro
---

<html>
  <body>
    <h1>Log In</h1>
    <form id="login-form">
      <label>Email <input type="email" id="email" required /></label>
      <label>Password <input type="password" id="password" required /></label>
      <button type="submit">Log In</button>
    </form>

    <script>
      import { auth } from '../lib/firebase-client';
      import { signInWithEmailAndPassword } from 'firebase/auth';

      const form = document.getElementById('login-form');
      form?.addEventListener('submit', async (e) => {
        e.preventDefault();
        const email = (document.getElementById('email') as HTMLInputElement).value;
        const password = (document.getElementById('password') as HTMLInputElement).value;

        try {
          const userCredential = await signInWithEmailAndPassword(auth, email, password);
          const idToken = await userCredential.user.getIdToken();

          const response = await fetch('/api/auth/session', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ idToken }),
          });

          if (response.ok) {
            window.location.href = '/dashboard';
          }
        } catch (error) {
          console.error('Login failed:', error);
        }
      });
    </script>
  </body>
</html>

Session Creation API Route

Convert the Firebase ID token into a session cookie:

// src/pages/api/auth/session.ts
import type { APIRoute } from 'astro';
import { adminAuth } from '../../../lib/firebase-admin';

export const POST: APIRoute = async ({ request }) => {
  const { idToken } = await request.json();

  try {
    const expiresIn = 60 * 60 * 24 * 7 * 1000; // 7 days
    const sessionCookie = await adminAuth.createSessionCookie(idToken, { expiresIn });

    return new Response(JSON.stringify({ status: 'success' }), {
      headers: {
        'Set-Cookie': `session=${sessionCookie}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=${expiresIn / 1000}`,
        'Content-Type': 'application/json',
      },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: 'Invalid token' }), { status: 401 });
  }
};

Authentication Middleware

Verify the session on every request:

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { adminAuth } from './lib/firebase-admin';

export const onRequest = defineMiddleware(async ({ request, locals, cookies }, next) => {
  const sessionCookie = cookies.get('session')?.value;

  if (sessionCookie) {
    try {
      const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true);
      locals.user = decodedClaims;
    } catch {
      locals.user = null;
    }
  } else {
    locals.user = null;
  }

  return next();
});

Protecting Pages

---
// src/pages/dashboard.astro
const user = Astro.locals.user;

if (!user) {
  return Astro.redirect('/login');
}
---

<html>
  <body>
    <h1>Dashboard</h1>
    <p>Welcome, {user.email}</p>
    <p>Firebase UID: {user.uid}</p>
  </body>
</html>

Troubleshooting

"Firebase app already initialized" error: This happens during hot reload in development. The getApps() check in the admin setup file prevents it. Make sure you are using the pattern shown above rather than calling initializeApp directly.

Private key parsing fails: The FIREBASE_ADMIN_PRIVATE_KEY often has escaped newlines when stored in .env files. The .replace(/\\n/g, '\n') in the config handles this. If deploying to a platform like Vercel, paste the key without escaping and let the platform handle it.

Session cookie not created: Firebase's createSessionCookie requires the ID token to be recent (issued within the last 5 minutes). If the token is older, the user needs to re-authenticate. This is a Firebase security requirement.

CORS errors with Google sign-in: When using social providers, add your domain (including localhost with port) to Firebase Console > Authentication > Settings > Authorized domains.

Session expiration handling: Firebase session cookies can be set for up to 14 days. When a session expires, the middleware will set locals.user to null, and the page redirect will send the user back to login.

Conclusion

Firebase Auth with Astro uses a client-server split where Firebase handles the initial authentication on the client, and the Admin SDK verifies sessions server-side. Session cookies give you secure, HttpOnly tokens that work with server-rendered pages. This approach keeps your authentication flow standard and secure while taking advantage of Firebase's built-in support for social providers, multi-factor authentication, and user management.