Skip to content

Latest commit

 

History

History
146 lines (121 loc) · 10.3 KB

middleware.mdx

File metadata and controls

146 lines (121 loc) · 10.3 KB

Authentication Middleware

authMiddleware works together with getTokens function to share valid user credentials between Next.js Middleware and Server Components.

Features

  1. It sets up /api/login and /api/logout endpoints to allow the client to manage browser authentication cookies. Please note that you don't have to setup dedicated api routes youself, as middleware does it for you. You can change the name of those endpoints by changing loginPath and logoutPath middleware options
  2. Automatically refreshes browser authentication cookies when token expires, and let's developer act accordingly
  3. Signs user cookies with rotating keys to mitigate the risk of cryptanalysis attacks
  4. Validates user cookies on each request
  5. Allows to customize behaviour by defining handleValidToken, handleInvalidToken and handleError callbacks

Advanced usage

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

import { NextRequest, NextResponse } 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: ["secret1", "secret2"],
    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",
    },
    debug: true,
    tenantId: "your-tenant-id",
    checkRevoked: true,
    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|.*\\.).*)"],
};

Middleware token verification caching

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

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

See Modifying Request Headers in Middleware for more information on how modified headers work

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

Usage with next-intl and other middlewares

import { 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);
    },
  });
}

Please note that you don't need to pass headers as in middleware token verification caching when using next-intl middleware. request is already updated with modified headers by the time we call intlMiddleware. next-intl will pass modified headers for us.

Options

Name Description
loginPath Required Defines API login endpoint. When called with auth firebase token from the client (see examples below), responds with Set-Cookie headers containing signed id and refresh tokens.
logoutPath Required Defines API logout endpoint. When called from the client (see examples below), returns empty Set-Cookie headers that remove previously set credentials
apiKey Required Firebase project API key used to fetch firebase id and refresh tokens
cookieName Required The name for cookie set by loginPath api route.
cookieSignatureKeys Required Rotating keys the cookie is validated against
cookieSerializeOptions Required Defines additional cookie options sent along Set-Cookie headers
serviceAccount Optional in authenticated Google Cloud Run environment. Otherwise required Firebase project service account.
tenantId Optional string By default undefined Google Cloud Platform tenant identifier. Specify if your project supports multi-tenancy
checkRevoked Optional boolean By default false If true, validates the token against firebase server on each request. Unless you have a good reason, it's better not to use it.
handleValidToken Optional (tokens: { token: string, decodedToken: DecodedIdToken }, headers: Headers) => Promise<NextResponse> By default returns NextResponse.next() Receives id and decoded tokens and should return a promise that resolves with NextResponse. Function is called with modified request headers as a second parameter. By passing this parameters down to NextResponse.next({ request: { headers } }) library won't verify the token in subsequent calls to getTokens or getTokensFromObject, which can improve response times.
handleInvalidToken Optional (reason: InvalidTokenReason) => Promise<NextResponse> By default returns NextResponse.next() If passed, is called and returned if request has not been authenticated (either does not have credentials attached or credentials are malformed). Can be used to redirect unauthenticated users to specific page or pages. Called with reason as first argument, which can be one of MISSING_CREDENTIALS, MISSING_REFRESH_TOKEN, MALFORMED_CREDENTIALS, INVALID_SIGNATURE, INVALID_CREDENTIALS. See handleInvalidToken to read description of each reason.
handleError Optional (error: AuthError) => Promise<NextResponse> By default returns NextResponse.next() Receives an unhandled error that happened during authentication and should resolve with NextResponse. By default, in case of unhandled error during authentication, the library just allows application to render. This allows you to customize error handling. See handleError to read about possible errors
debug Optional boolean By default false Provides helpful logs than can help understand authentication flow and debug issues