Skip to main content
Chapter 5 Authentication and Middleware

Protecting Routes with Middleware

18 min read Lesson 18 / 28

Next.js Middleware for Route Protection

Middleware runs at the edge before a request reaches your page. It is the ideal place to check authentication and redirect unauthenticated users before any component renders.

middleware.ts

// middleware.ts (at the root of your project, same level as app/)
import { auth } from '@/auth';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export default auth((req) => {
    const { nextUrl, auth: session } = req;

    const isAuthenticated = !!session?.user;
    const isAuthPage = nextUrl.pathname.startsWith('/login') ||
                       nextUrl.pathname.startsWith('/register');
    const isProtectedRoute = nextUrl.pathname.startsWith('/dashboard') ||
                              nextUrl.pathname.startsWith('/settings') ||
                              nextUrl.pathname.startsWith('/profile');
    const isAdminRoute = nextUrl.pathname.startsWith('/admin');

    // Redirect authenticated users away from auth pages
    if (isAuthenticated && isAuthPage) {
        return NextResponse.redirect(new URL('/dashboard', nextUrl));
    }

    // Redirect unauthenticated users to login
    if (!isAuthenticated && isProtectedRoute) {
        const loginUrl = new URL('/login', nextUrl);
        loginUrl.searchParams.set('callbackUrl', nextUrl.pathname);
        return NextResponse.redirect(loginUrl);
    }

    // Role-based protection
    if (isAdminRoute && session?.user?.role !== 'admin') {
        return NextResponse.redirect(new URL('/unauthorized', nextUrl));
    }

    return NextResponse.next();
});

// Apply middleware only to these paths (not _next, api/auth, static files)
export const config = {
    matcher: [
        '/((?!_next/static|_next/image|favicon.ico|api/auth).*)',
    ],
};

Reading the Session in Server Components

// app/dashboard/page.tsx
import { auth } from '@/auth';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
    const session = await auth();

    if (!session) {
        redirect('/login');
    }

    return (
        <div>
            <h1>Welcome, {session.user.name}</h1>
            <p>Email: {session.user.email}</p>
        </div>
    );
}

Session in Client Components

// components/user-menu.tsx
'use client';

import { useSession } from 'next-auth/react';

export function UserMenu() {
    const { data: session, status } = useSession();

    if (status === 'loading') return <div className="animate-pulse h-8 w-8 rounded-full bg-gray-200" />;
    if (!session) return null;

    return (
        <div className="flex items-center gap-3">
            {session.user.image && (
                <img src={session.user.image} className="h-8 w-8 rounded-full" alt="" />
            )}
            <span>{session.user.name}</span>
        </div>
    );
}

Middleware-based protection is the most reliable approach — it is enforced at the edge before any server code runs.