Handling Authentication and Redirection in Next.js Middleware
When developing web applications, managing user authentication and access control is critical to providing a secure and seamless experience. With Next.js, you can use middleware to intercept incoming requests and handle various scenarios, such as checking if a user is logged in, redirecting them to appropriate pages, or managing access based on user roles.
In this article, we’ll explore a middleware.ts
file from a Next.js application . We will also provide examples and explanations to illustrate the use cases covered by this middleware.
The Middleware Code
Here’s the full middleware code we’ll be working with:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-url', request.nextUrl.origin);
const currentUser = request.cookies.get('accessToken')?.value;
const sessionData = request.cookies.get('session')?.value;
const copilotFlag = request.nextUrl.searchParams.get('copilot');
console.log("****************************");
console.log("sessionData:", sessionData);
console.log("currentUser----", (currentUser ? "Logged In" : "Guest User"));
console.log("request.url:", request.url);
console.log("request.nextUrl.pathname:", request.nextUrl.pathname);
if (!currentUser && request.nextUrl.pathname !== '/') {
return NextResponse.redirect(new URL('/', request.url));
}
if (copilotFlag && copilotFlag === 'true' && currentUser && request.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL(`/auth=${currentUser}`, request.url));
}
if (currentUser && request.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL('/dashboard-v2', request.url));
}
if (currentUser && request.nextUrl.pathname === '/dashboard') {
return NextResponse.redirect(new URL('/dashboard-v2', request.url));
}
if (
currentUser &&
(request.nextUrl.pathname === '/dashboard/logs' ||
request.nextUrl.pathname === '/dashboard/admin' ||
request.nextUrl.pathname === '/settings')
) {
const sessionObj = sessionData ? JSON.parse(sessionData) : "";
if (!sessionObj || (sessionObj && !sessionObj.roles.includes("admin"))) {
return NextResponse.redirect(new URL('/dashboard-v2', request.url));
}
}
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
export const config = {
matcher: ['/', '/webchat', '/dashboard', '/dashboard/:path*', '/dashboard-v2', '/settings'],
}
Explanation of the Middleware Logic
Let’s go through each section of the middleware to understand the different scenarios it handles:
1. Setting Custom Headers
Before handling any specific logic, the middleware sets a custom header (x-url
) with the origin of the request. This can be useful for tracking purposes or to pass along necessary data for downstream processes.
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-url', request.nextUrl.origin);
2. Extracting Authentication and Session Data
The middleware checks for cookies that store the user’s authentication token (accessToken
) and session information (session
). It also checks for a query parameter (copilot
) to handle specific scenarios .
const currentUser = request.cookies.get('accessToken')?.value;
const sessionData = request.cookies.get('session')?.value;
const copilotFlag = request.nextUrl.searchParams.get('copilot');
3. Scenario 1: Redirect Unauthenticated Users
If a user is not logged in (!currentUser
) and tries to access any route other than the homepage (/
), they are redirected to the homepage.
if (!currentUser && request.nextUrl.pathname !== '/') {
return NextResponse.redirect(new URL('/', request.url));
}
- Example: If a guest user tries to access
/dashboard
, they will be redirected to/
.
4. Scenario 2: Microsoft Teams Integration Check
If the request comes from url with copilotFlag as query parameter being set to true
, and the user is logged in (currentUser
exists), and the request is for the homepage (/
), the user is redirected to a special URL (/auth={currentUser}
).
if (copilotFlag && copilotFlag === 'true' && currentUser && request.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL(`/auth=${currentUser}`, request.url));
}
- Example: A logged-in user accessing the app from url with some query parameter, will be redirected to
/auth={currentUser}
.
5. Scenario 3: Redirect Authenticated Users from Homepage
If a user is logged in (currentUser
) and attempts to access the homepage (/
), they are redirected to the new dashboard (/dashboard-v2
).
if (currentUser && request.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL('/dashboard-v2', request.url));
}
- Example: A logged-in user visiting the homepage will be automatically redirected to
/dashboard-v2
.
6. Scenario 4: Redirect from Old to New Dashboard
If a logged-in user attempts to access the old dashboard (/dashboard
), they are redirected to the new dashboard (/dashboard-v2
).
if (currentUser && request.nextUrl.pathname === '/dashboard') {
return NextResponse.redirect(new URL('/dashboard-v2', request.url));
}
- Example: A logged-in user accessing
/dashboard
will be redirected to/dashboard-v2
.
7. Scenario 5: Role-Based Access Control
For certain paths (/dashboard/logs
, /dashboard/admin
, /settings
), the middleware checks if the user has the admin
role. If not, the user is redirected to /dashboard-v2
.
if (
currentUser &&
(request.nextUrl.pathname === '/dashboard/logs' ||
request.nextUrl.pathname === '/dashboard/admin' ||
request.nextUrl.pathname === '/settings')
) {
const sessionObj = sessionData ? JSON.parse(sessionData) : "";
if (!sessionObj || (sessionObj && !sessionObj.roles.includes("admin"))) {
return NextResponse.redirect(new URL('/dashboard-v2', request.url));
}
}
- Example: A logged-in user without the
admin
role trying to access/settings
will be redirected to/dashboard-v2
.
8. Proceed with the Request
If none of the above conditions are met, the request is allowed to continue with any modified headers.
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
More Examples of Middleware Scenarios
Here are some additional scenarios you might want to consider for your middleware:
- Redirect Based on User Preferences: If you store user preferences (e.g., language or theme) in cookies or session data, you can use the middleware to redirect users to a localized or theme-specific version of your app.
- Redirect Based on User Plan: If your application has different user plans (e.g., free, pro, enterprise), you can use the middleware to manage access to features or routes based on the user’s plan.
- Redirect for Maintenance: If your application or specific routes are undergoing maintenance, you can use the middleware to redirect all traffic to a maintenance page.
Explanation of the matcher
Configuration
The matcher
property in the middleware configuration specifies the routes where the middleware should run. It is an array of strings representing the paths that will trigger the middleware:
export const config = {
matcher: ['/', '/webchat', '/dashboard', '/dashboard/:path*', '/dashboard-v2', '/settings'],
}
'/'
: Matches the homepage (/
).'/
webchat'
: Matches the/
webchat route.'/dashboard'
: Matches the/dashboard
route.'/dashboard/:path*'
: Matches all routes under/dashboard
(e.g.,/dashboard/logs
,/dashboard/admin
).'/dashboard-v2'
: Matches the/dashboard-v2
route.'/settings'
: Matches the/settings
route.
The matcher
helps optimize middleware performance by running it only on specified routes, improving control and security by targeting specific paths.
Conclusion
Middleware in Next.js provides a powerful mechanism to control access and redirection at the edge. By utilizing middleware, you can handle various scenarios, such as authentication checks, role-based access control, and custom redirections based on user context. This example has demonstrated how to handle common scenarios in a Next.js application to provide a secure and user-friendly experience.
Feel free to expand upon this middleware logic to fit the unique needs of your application!