Web DevelopmentADVANCED 16 min read

Authentication & Session Security: NextAuth (Auth.js) & Firebase Auth

Production authentication architecture covering OAuth 2.0 (Google, GitHub), Credentials login with password hashing, HTTP-only SameSite secure session cookies, JWT vs Database sessions, Next.js Edge Middleware route guards, and Role-Based Access Control (RBAC).

Standard Remediation Protocol

6 verified steps
01

Step 1

Configure NextAuth.js / Auth.js with OAuth providers and Credentials provider using Argon2 or Bcrypt hashing.

02

Step 2

Store session tokens strictly in HTTP-only, Secure, SameSite=Lax cookies to eliminate XSS token theft.

03

Step 3

Implement Next.js Edge Middleware (middleware.ts) to intercept unauthorized traffic before pages or API routes render.

04

Step 4

Enforce Role-Based Access Control (RBAC) across server components, API route handlers, and client navigation.

05

Step 5

Integrate Firebase Authentication client-side sign-in with server-side ID token verification via Firebase Admin SDK.

06

Step 6

Mitigate Cross-Site Request Forgery (CSRF) and automate refresh token rotation for long-lived sessions.

Multi-OS Terminal Diagnostic Commands

PowerShell • Windows CMD • Bash
openssl rand -base64 32; npm list next-auth @auth/core

Production Verification Checklist

Interactive Diagnostic Checklist

Check off steps as you execute troubleshooting workflows. Saved locally in your browser.

0/8 (0%)
Generate high-entropy AUTH_SECRET (32+ bytes) and configure in production environment variables.
Register OAuth applications in Google Cloud Console and GitHub Developer Settings.
Configure NextAuth.js providers with Credentials and OAuth strategies.
Enforce HTTP-only, Secure, SameSite=Lax cookie options for all session identifiers.
Deploy Edge Middleware (middleware.ts) to intercept unauthenticated and unauthorized requests.
Implement Role-Based Access Control (RBAC) callbacks to inject user roles into session tokens.
Verify Firebase ID tokens server-side using admin.auth().verifyIdToken() in API routes.
Audit application dependencies against known CVEs using npm audit or Snyk.

Architectural Details & Remediation Manual

Production Authentication Architecture & Session Security

Authentication establishes who a user is, while authorization determines what resources they can access. Storing authentication tokens in browser localStorage or omitting server-side middleware invites catastrophic XSS and CSRF token theft. This guide details resilient authentication implementation using NextAuth.js (Auth.js) and Firebase Authentication.

1. Session Storage Architecture: Cookie Security vs LocalStorage

The single most critical security decision in modern web authentication is where tokens reside:

  • Vulnerable: LocalStorage / SessionStorage: Any script running in the browser (including third-party analytics, chat widgets, or injected XSS payloads) can read localStorage.getItem('token') and transmit it to an attacker's server. Never store JWTs or session secrets in browser storage.
  • Secure: HTTP-only Cookies: When a cookie has the HttpOnly flag, browser JavaScript cannot access it via document.cookie.
    • HttpOnly: true - Completely prevents client script token extraction.
    • Secure: true - Guarantees cookies are transmitted exclusively over encrypted HTTPS connections.
    • SameSite: 'Lax' or 'Strict' - Prevents Cross-Site Request Forgery (CSRF) attacks by restricting cookie transmission on cross-origin requests.

2. NextAuth.js / Auth.js (v5) Implementation

NextAuth.js provides standardized session management with built-in CSRF defenses and OAuth state verification.

// auth.config.ts
import type { NextAuthConfig } from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcryptjs';
import { db } from '@/lib/db';

export const authConfig: NextAuthConfig = {
  providers: [
    Google({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET }),
    GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }),
    Credentials({
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) return null;
        const user = await db.user.findUnique({ where: { email: String(credentials.email) } });
        if (!user || !user.passwordHash) return null;
        const isValid = await bcrypt.compare(String(credentials.password), user.passwordHash);
        if (!isValid) return null;
        return { id: user.id, email: user.email, name: user.name, role: user.role };
      }
    })
  ],
  session: { strategy: 'jwt', maxAge: 30 * 24 * 60 * 60 }, // 30 days
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
        token.role = (user as any).role || 'user';
      }
      return token;
    },
    async session({ session, token }) {
      if (token && session.user) {
        session.user.id = token.id as string;
        (session.user as any).role = token.role;
      }
      return session;
    }
  }
};

3. Edge Middleware Route Protection

Do not rely solely on client-side redirect hooks. Edge middleware executes before incoming requests reach server components or API route handlers, eliminating flash of unauthorized content.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getToken } from 'next-auth/jwt';

export async function middleware(req: NextRequest) {
  const token = await getToken({ req, secret: process.env.AUTH_SECRET });
  const { pathname } = req.nextUrl;

  // Protect admin routes
  if (pathname.startsWith('/admin')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login?callbackUrl=' + pathname, req.url));
    }
    if (token.role !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', req.url));
    }
  }

  // Protect authenticated user dashboard
  if (pathname.startsWith('/dashboard')) {
    if (!token) {
      return NextResponse.redirect(new URL('/login?callbackUrl=' + pathname, req.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*', '/api/protected/:path*'],
};

4. Firebase Authentication Server Token Verification

When using Firebase Authentication on the client, verify ID tokens on your backend before granting access to sensitive data:

// app/api/v1/user/profile/route.ts
import { NextRequest, NextResponse } from 'next/server';
import * as admin from 'firebase-admin';

export async function GET(req: NextRequest) {
  const authHeader = req.headers.get('Authorization');
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return NextResponse.json({ error: 'Missing authorization header' }, { status: 401 });
  }

  const idToken = authHeader.split('Bearer ')[1];
  try {
    const decodedToken = await admin.auth().verifyIdToken(idToken);
    const userId = decodedToken.uid;
    // Process privileged request for authenticated userId
    return NextResponse.json({ success: true, userId, email: decodedToken.email });
  } catch (error) {
    return NextResponse.json({ error: 'Invalid or expired token' }, { status: 403 });
  }
}
Tags:#Authentication#NextAuth#Auth.js#Firebase Auth#OAuth#JWT#Cookies#Middleware#RBAC

Was this troubleshooting guide helpful?

Your verification helps maintain remediation accuracy across technical guides.

Community Troubleshooting & Insights (0)