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
- 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 theloginPath
andlogoutPath
options in the middleware settings. - Automatically refreshes browser authentication cookies when the token expires, allowing developers to handle it accordingly.
- Signs user cookies with rotating keys to reduce the risk of cryptanalysis attacks.
- Validates user cookies on every request.
- Provides flexibility by allowing you to define custom behavior using the
handleValidToken
,handleInvalidToken
, andhandleError
callbacks.
Advanced usage
Advanced usage of authMiddleware
in middleware.ts
, based on starter example:
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 theidToken
${cookieName}.refresh
– stores therefreshToken
${cookieName}.custom
– stores thecustomToken
${cookieName}.sig
– stores thesignature
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
Name | Required? | Type/Default | Description |
---|---|---|---|
loginPath | Required | Defines 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. | |
logoutPath | Required | Defines the API logout endpoint. When called from the client, it returns empty Set-Cookie headers that clear any previously set credentials. | |
apiKey | Required | The Firebase project API key, used to fetch Firebase ID and refresh tokens. | |
cookieName | Required | The name of the cookie set by the loginPath API route. | |
cookieSignatureKeys | Required | Rotating keys (opens in a new tab) used to validate the cookie. | |
cookieSerializeOptions | Required | Defines additional options for the Set-Cookie headers. | |
serviceAccount | Optional in authenticated Google Cloud Run (opens in a new tab) environments. Otherwise required | The Firebase project service account. | |
enableMultipleCookies | Optional | boolean , defaults to false | Splits 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). |
enableCustomToken | Optional | boolean , defaults to false | If 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 |
tenantId | Optional | string , defaults to undefined | The Google Cloud Platform tenant identifier. Specify if your project supports multi-tenancy (opens in a new tab). |
authorizationHeaderName | Optional | string , defaults to Authorization | The name of the authorization header expected by the login endpoint. |
checkRevoked | Optional | boolean , defaults to false | If 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. |
handleValidToken | Optional | (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. |
handleInvalidToken | Optional | (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. |
handleError | Optional | (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. |
debug | Optional | boolean , defaults to false | Enables helpful logs for better understanding and debugging of the authentication flow. |