Introduction
Building secure web applications requires implementing proper authentication and authorization mechanisms. In Next.js applications, middleware provides an excellent way to protect routes and handle authentication before requests reach your pages. This blog post will walk you through implementing a robust authentication middleware that seamlessly integrates with internationalization (i18n) routing for library_proj, one of the project I contributed to doing the hacktoberfest.
Why Authentication Middleware?
Authentication middleware sits between the client request and your application logic, allowing you to:
- Protect routes before they're rendered
- Redirect unauthenticated users to login pages
- Preserve intended destinations with redirect parameters
- Prevent authenticated users from accessing auth pages
- Centralize authentication logic for better maintainability
By handling authentication at the middleware level, you ensure security checks happen early in the request lifecycle, before any server-side rendering or data fetching occurs.
Architecture Overview
The authentication middleware works in conjunction with Next.js App Router and includes:
Request → Middleware → Authentication Check → Route Protection → i18n Routing → Response
The middleware performs these checks in order:
- Extract locale from the pathname or browser preferences
- Check authentication status via cookies
- Determine route type (public vs. protected)
- Apply redirects based on authentication state
- Handle locale routing for proper i18n support
Implementation Details
Authentication Check
The middleware checks for authentication tokens stored in cookies. It supports multiple token types for flexibility:
function isAuthenticated(request: NextRequest): boolean {
const authToken = request.cookies.get('auth_token');
const sessionToken = request.cookies.get('session_token');
// Check for either auth_token or session_token
return !!(authToken || sessionToken);
}
Route Classification
The middleware distinguishes between public and protected routes:
Public Routes
Routes accessible without authentication:
/login/signup/register/forgot-password/reset-password
Protected Routes
By default, all routes are protected except those explicitly marked as public. This includes:
- Dashboard routes (
/,/my_books,/books) - Profile and settings pages
- Any other application routes
Protection Logic
function isProtectedRoute(pathname: string): boolean {
const pathWithoutLocale = getPathnameWithoutLocale(pathname);
// If it's a public route, it's not protected
if (isPublicRoute(pathname)) {
return false;
}
// All other routes are protected by default
return true;
}
This "secure by default" approach ensures that new routes are automatically protected unless explicitly added to the public routes list.
Key Features
1. Automatic Route Protection
All routes are protected by default, reducing the risk of accidentally exposing sensitive pages.
2. Smart Redirects
-
Unauthenticated users accessing protected routes are redirected to
/loginwith aredirectparameter - Authenticated users accessing auth pages (login, signup) are redirected to the home page
- Locale is preserved in all redirects
3. i18n Integration
The middleware seamlessly handles internationalization:
- Extracts locale from pathname
- Preserves locale during redirects
- Works with browser language preferences
4. Server Component Support
Authentication status is exposed via headers for server components:
response.headers.set('x-authenticated', authenticated ? 'true' : 'false');
Server components can access this header to conditionally render content:
// In a server component
const headers = await headers();
const isAuthenticated = headers.get('x-authenticated') === 'true';
Best Practices
1. Secure Cookie Settings
When setting authentication cookies, use secure settings:
// In your login handler
cookies().set('auth_token', token, {
httpOnly: true, // Prevent XSS attacks
secure: process.env.NODE_ENV === 'production', // HTTPS only in production
sameSite: 'lax', // CSRF protection
maxAge: 60 * 60 * 24 * 7, // 7 days
path: '/',
});
2. Token Validation
For production, consider validating tokens server-side:
async function isAuthenticated(request: NextRequest): Promise<boolean> {
const token = request.cookies.get('auth_token')?.value;
if (!token) return false;
try {
// Validate token with your backend
const response = await fetch(`${BACKEND_URL}/api/auth/verify`, {
headers: { Authorization: `Bearer ${token}` }
});
return response.ok;
} catch {
return false;
}
}
3. Error Handling
Add proper error handling for edge cases:
try {
// Middleware logic
} catch (error) {
console.error('Middleware error:', error);
// Fallback: allow request but log error
return NextResponse.next();
}
4. Testing
Test your middleware with different scenarios:
- ✅ Unauthenticated user accessing protected route → redirects to login
- ✅ Authenticated user accessing login → redirects to home
- ✅ Authenticated user accessing protected route → allowed
- ✅ Locale preservation during redirects
- ✅ Public routes accessible without authentication
Conclusion
Implementing authentication middleware in Next.js provides a robust, centralized way to protect your application routes. By combining authentication checks with i18n routing, you create a seamless user experience that works across different languages and locales.
Top comments (0)