Skip to content
Docs
Usage guide
Middleware

Authentication Middleware

The authMiddleware works with the getTokens function to share valid user credentials between Next.js Middleware (opens in a new tab) and Server Components (opens in a new tab).

Key Features

  1. Sets up /api/login and /api/logout endpoints for managing browser authentication cookies. You don't need to create these API routes yourself—the middleware handles it for you. You can rename these endpoints by adjusting the loginPath and logoutPath options in the middleware settings.
  2. Automatically refreshes browser authentication cookies when the token expires, allowing developers to handle it accordingly.
  3. Signs user cookies with rotating keys to reduce the risk of cryptanalysis attacks.
  4. Validates user cookies on every request.
  5. Provides flexibility by allowing you to define custom behavior using the handleValidToken, handleInvalidToken, and handleError callbacks.

Advanced usage

Advanced usage of authMiddleware in middleware.ts, based on starter example:

middleware.ts
import {NextResponse} from 'next/server';
import type {NextRequest} from 'next/server';
import {
  authMiddleware,
  redirectToHome,
  redirectToLogin
} from 'next-firebase-auth-edge';
 
const PUBLIC_PATHS = ['/register', '/login', '/reset-password'];
 
export async function middleware(request: NextRequest) {
  return authMiddleware(request, {
    loginPath: '/api/login',
    logoutPath: '/api/logout',
    apiKey: 'XXxxXxXXXxXxxxxx_XxxxXxxxxxXxxxXXXxxXxX',
    cookieName: 'AuthToken',
    cookieSignatureKeys: ['Key-Should-Be-at-least-32-bytes-in-length'],
    cookieSerializeOptions: {
      path: '/',
      httpOnly: true,
      secure: false, // Set this to true on HTTPS environments
      sameSite: 'lax' as const,
      maxAge: 12 * 60 * 60 * 24 // twelve days
    },
    serviceAccount: {
      projectId: 'your-firebase-project-id',
      clientEmail:
        'firebase-adminsdk-nnw48@your-firebase-project-id.iam.gserviceaccount.com',
      privateKey:
        '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n'
    },
    enableMultipleCookies: true,
    enableCustomToken: false,
    debug: true,
    tenantId: 'your-tenant-id',
    checkRevoked: true,
    authorizationHeaderName: 'Authorization',
    handleValidToken: async ({token, decodedToken}, headers) => {
      // Authenticated user should not be able to access /login, /register and /reset-password routes
      if (PUBLIC_PATHS.includes(request.nextUrl.pathname)) {
        return redirectToHome(request);
      }
 
      return NextResponse.next({
        request: {
          headers
        }
      });
    },
    handleInvalidToken: async (reason) => {
      console.info('Missing or malformed credentials', {reason});
 
      return redirectToLogin(request, {
        path: '/login',
        publicPaths: PUBLIC_PATHS
      });
    },
    handleError: async (error) => {
      console.error('Unhandled authentication error', {error});
 
      return redirectToLogin(request, {
        path: '/login',
        publicPaths: PUBLIC_PATHS
      });
    }
  });
}
 
export const config = {
  matcher: [
    '/api/login',
    '/api/logout',
    '/',
    '/((?!_next|favicon.ico|api|.*\\.).*)'
  ]
};

Custom Token

Starting from v1.8.0, custom token is no longer enabled by default. If you wish to enable custom token, set enableCustomToken option to true in authMiddleware.

Custom token introduces a significant footprint on the size of authentication cookie and is not required for most use-cases.

It's recommended to use enableCustomToken together with enableMultipleCookies. enableMultipleCookies would split session into multiple cookies, eliminating issues that can come from cookie size, as explained below.

Multiple Cookies

Starting from v1.6.0, the authMiddleware supports the enableMultipleCookies option.

By default, the session data is stored in a single cookie. This works for most cases, but it limits the size of custom claims you can add to a token. Most browsers won't store a cookie larger than 4096 bytes (opens in a new tab).

To prevent cookie size issues, it's recommended to set enableMultipleCookies to true.

When enabled, the session will be split into four cookies:

  • ${cookieName}.id – stores the idToken
  • ${cookieName}.refresh – stores the refreshToken
  • ${cookieName}.custom – stores the customToken
  • ${cookieName}.sig – stores the signature used to validate the tokens

Firebase Hosting and Multiple Cookies

If you're using Firebase Hosting, set enableMultipleCookies to false.

Due to an issue with Firebase Hosting (details here (opens in a new tab)), multiple cookies are not supported.

If you run into cookie size problems on Firebase Hosting, you may want to either reduce the size of custom claims in your tokens or consider switching to a different hosting provider.

Middleware Token Verification Caching

Since v0.9.0, the handleValidToken function is called with modified request headers as a second parameter.

You can pass this headers object to NextResponse.next({ request: { headers } }) to enable token verification caching.

For more details on modifying request headers in middleware, check out Modifying Request Headers in Middleware (opens in a new tab).

The example below shows a simplified version of how you can combine other middleware with next-firebase-auth-edge.

handleValidToken: async ({token, decodedToken, customToken}, headers) => {
  return NextResponse.next({
    request: {
      headers // Pass modified request headers to skip token verification in subsequent getTokens and getApiRequestTokens calls
    }
  });
};

Usage with next-intl and other middlewares

import type {NextRequest} from 'next/server';
import createIntlMiddleware from 'next-intl/middleware';
import {authMiddleware} from 'next-firebase-auth-edge';
 
const intlMiddleware = createIntlMiddleware({
  locales: ['en', 'pl'],
  defaultLocale: 'en'
});
 
export async function middleware(request: NextRequest) {
  return authMiddleware(request, {
    // ...
    handleValidToken: async (tokens) => {
      return intlMiddleware(request);
    },
    handleInvalidToken: async (reason) => {
      return intlMiddleware(request);
    },
    handleError: async (error) => {
      return intlMiddleware(request);
    }
  });
}

Note: When using next-intl middleware, you don't need to pass headers like you would in middleware token verification caching (opens in a new tab). By the time we call intlMiddleware, the request already has the updated headers. next-intl will handle passing the modified headers for you.

If you're experiencing issues with redirects while using next-intl middleware, check out this comment on GitHub (opens in a new tab) for code examples.

Options

NameRequired?Type/DefaultDescription
loginPathRequiredDefines the API login endpoint. When called with a Firebase auth token from the client, it responds with Set-Cookie headers containing signed ID and refresh tokens.
logoutPathRequiredDefines the API logout endpoint. When called from the client, it returns empty Set-Cookie headers that clear any previously set credentials.
apiKeyRequiredThe Firebase project API key, used to fetch Firebase ID and refresh tokens.
cookieNameRequiredThe name of the cookie set by the loginPath API route.
cookieSignatureKeysRequiredRotating keys (opens in a new tab) used to validate the cookie.
cookieSerializeOptionsRequiredDefines additional options for the Set-Cookie headers.
serviceAccountOptional in authenticated Google Cloud Run (opens in a new tab) environments. Otherwise requiredThe Firebase project service account.
enableMultipleCookiesOptionalboolean, defaults to falseSplits session tokens into multiple cookies to increase token claims capacity. Recommended, but defaults to false for backwards compatibility. Set to false on Firebase Hosting due to limitations like this (opens in a new tab).
enableCustomTokenOptionalboolean, defaults to falseIf enabled, authentication cookie would contain custom token. This is helpful if you want to use signInWithCustomToken (opens in a new tab) Firebase Client SDK method
tenantIdOptionalstring, defaults to undefinedThe Google Cloud Platform tenant identifier. Specify if your project supports multi-tenancy (opens in a new tab).
authorizationHeaderNameOptionalstring, defaults to AuthorizationThe name of the authorization header expected by the login endpoint.
checkRevokedOptionalboolean, defaults to falseIf true, validates the token against the Firebase server on every request. Unless there's a specific need, it's usually better not to use this.
handleValidTokenOptional(tokens: { token: string, decodedToken: DecodedIdToken, customToken?: string }, headers: Headers) => Promise<NextResponse>, defaults to NextResponse.next()Called when a valid token is received. Should return a promise resolving with NextResponse. It's passed modified request headers as a second parameter, which, if forwarded with NextResponse.next({ request: { headers } }), prevents re-verification of the token in subsequent calls, improving response times.
handleInvalidTokenOptional(reason: InvalidTokenReason) => Promise<NextResponse>, defaults to NextResponse.next()Called when a request is unauthenticated (either missing credentials or has invalid credentials). Can be used to redirect users to a specific page. The reason argument can be one of MISSING_CREDENTIALS, MISSING_REFRESH_TOKEN, MALFORMED_CREDENTIALS, INVALID_SIGNATURE, or INVALID_CREDENTIALS. See the handleInvalidToken section for details on each reason.
handleErrorOptional(error: AuthError) => Promise<NextResponse>, defaults to NextResponse.next()Called when an unhandled error occurs during authentication. By default, the app will render, but you can customize error handling here. See the handleError section for more information on possible errors.
debugOptionalboolean, defaults to falseEnables helpful logs for better understanding and debugging of the authentication flow.