Authentication Middleware
authMiddleware
works together with getTokens function to share valid user credentials between Next.js Middleware (opens in a new tab) and Server Components (opens in a new tab).
Features
- 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 changingloginPath
andlogoutPath
middleware options - Automatically refreshes browser authentication cookies when token expires, and let's developer act accordingly
- Signs user cookies with rotating keys to mitigate the risk of cryptanalysis attacks
- Validates user cookies on each request
- Allows to customize behaviour by defining
handleValidToken
,handleInvalidToken
andhandleError
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 (opens in a new tab) 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 (opens in a new tab) 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 (opens in a new tab) the cookie is validated against | |
cookieSerializeOptions | Required | Defines additional cookie options sent along Set-Cookie headers | |
serviceAccount | Optional in authenticated Google Cloud Run (opens in a new tab) 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 (opens in a new tab) |
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 |