Add Session Verification to NextJS Middleware
Implement session verification in the NextJS middleware to ensure only authenticated users access sensitive APIs and pages.
Now that your application is properly managing sessions, let's add session verification checks to the NextJS middleware to ensure that only users with valid sessions can access protected APIs and pages.
NextJS currently supports only a single middleware.ts
file for the entire application. This means your middleware must be designed to handle everything your app requires, including authentication, Cross-Site Request Forgery (CSRF) protection (covered later in this guide), and any additional cross-cutting concerns. When building your middleware, be aware that page requests and API route requests often need to be handled slightly differently, especially when returning Unauthorized responses to the browser.
Implement Session Verification In The Middleware
It’s crucial to ensure that your middleware correctly verifies the existence of an authenticated session for requests to your protected APIs and pages. To do this, you can check for a valid session cookie within your middleware. Below is an example of the middleware function:
//
// Middleware for Both Page Router and App Router
// Location -> src/middleware.ts
//
import { NextRequest, NextResponse } from 'next/server';
import { middlewareGetSession } from '@/session/session';
// NOTE: The protected paths listed below are just for example. You'll need to replace the
// paths to match your applications protected APIs and pages. We keep API paths separate from // page paths as we'll need to distinctly identify them in future sections of the guide
// when implementing CSRF protection.
const PROTECTED_API_PATH_PREFIXES = ['/api/v1'];
const PROTECTED_PAGE_PATH_PREFIXES = ['/settings', '/account', '/profile'];
const ALL_PROTECTED_PATH_PREFIXES = [...PROTECTED_API_PATH_PREFIXES, ...PROTECTED_PAGE_PATH_PREFIXES];
function isProtectedRequest(req: NextRequest) {
return ALL_PROTECTED_PATH_PREFIXES.some(prefix => req.nextUrl.pathname.startsWith(prefix));
}
// If the session verification check fails for a protected API we return a 401 response.
// Otherwise, if the session verification check fails for a protected page we redirect the
// user back to the Login Endpoint.
function unauthorizedResponse(req: NextRequest) {
return req.nextUrl.pathname.startsWith('/api/')
? NextResponse.json({ statusText: 'Unauthorized' }, { status: 401 })
: NextResponse.redirect('<replace-with-your-application-login-endpoint>');
}
/*
* This is the Middleware function where all logic happens.
*/
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
// Skip authentication checks for unprotected pages and API routes.
if (!isProtectedRequest(req)) {
return res;
}
// Ensure an authenticated user session is present for the current request.
const session = await middlewareGetSession(req, res);
const { isAuthenticated } = session;
if (!isAuthenticated) {
return unauthorizedResponse(req);
}
// Save the session in order to "touch" it and extend the session expiration window.
await session.save();
// Continue on to process the page load or API request
return res;
}
/*
* This matcher controls which requests run through middleware at all. It skips NextJS
* internals (i.e. "/_next/*"), static assets (i.e. "/fonts/*", "/favicon.ico", etc.),
* and root-level files. This avoids wasting resources and accidentally running auth, CSRF,
* or other middleware logic on static files that don’t need it. You should adjust these
* exclusions based on the unique structure of your application and the types of middleware
* you plan to use.
*
* NOTE: We don’t explicitly list protected routes here because:
* - This matcher is for broad exclusions only.
* - Fine-grained checks (auth, CSRF, etc.) happen inside middleware based on path.
* - Keeping it general allows your middleware to handle multiple concerns cleanly.
*/
export const config = {
matcher: ['/((?!_next|fonts|examples|[\\w-]+\\.\\w+).*)'],
};
Make sure to include the Session Endpoint as a protected endpoint since a valid session cookie must be present in the request in order for it to function.
Do The Session Verification Checks Need to be Applied to The Auth Endpoints?
The Login, Callback, and Logout Endpoints are meant to be accessed by unauthenticated users, so you don't need to perform session verification checks before they are accessed.
Remove Session Verification Check From Session Endpoint
Since the session cookie is being validated by the middleware now, we can remove the logic from the Session Endpoint that was checking to see if the user was authenticated. The Session Endpoint implementation should now look like the following example:
//
// Location -> src/pages/api/v1/session.ts
//
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from '@/session/session';
// Session Endpoint
export default async function sessionRoute(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession(req, res);
const { userId, tenantId } = session;
//
// This check can be removed since the session is now validated in the middleware.
//
// if (!isAuthenticated) {
// return res.status(401).end();
// }
return res.status(200).json({ userId, tenantId });
}
//
// Location -> src/app/api/v1/session/route.ts
//
import { NextResponse } from 'next/server';
import { getSession } from '@/session/session';
// Session Endpoint
export async function GET() {
const session = await getSession();
const { userId, tenantId } = session;
//
// This check can be removed since the session is now validated in the middleware.
//
// if (!isAuthenticated) {
// return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
// }
return NextResponse.json({ userId, tenantId });
}
Updated 9 days ago
Now that your server endpoints are protected, let's update the frontend code to handle unauthorized error responses.