We use cookies to enhance your experience on the site
CodeWorlds

forbidden() and unauthorized() - New error handling functions

In the security systems of Metropolis Quantum 2150, where access control is critical, you will learn about the new

forbidden()
and
unauthorized()
functions introduced in Next.js 15. These functions simplify handling authorization and authentication errors.

Problem: Manual 401/403 handling

Previously, handling authorization errors required manually creating Response objects:

1// Old, verbose approach
2export async function GET(request: Request) {
3  const session = await getSession();
4
5  if (!session) {
6    return new Response('Unauthorized', { status: 401 });
7  }
8
9  if (!session.user.isAdmin) {
10    return new Response('Forbidden', { status: 403 });
11  }
12
13  return Response.json(await getAdminData());
14}

Solution: forbidden() and unauthorized()

unauthorized() - 401 error

The

unauthorized()
function throws a 401 error and renders the
unauthorized.tsx
file:

1// app/api/profile/route.ts
2import { unauthorized } from 'next/server';
3import { getSession } from '@/lib/auth';
4
5export async function GET(request: Request) {
6  const session = await getSession();
7
8  if (!session) {
9    unauthorized(); // Throws a 401 error
10  }
11
12  return Response.json(session.user);
13}

forbidden() - 403 error

The

forbidden()
function throws a 403 error and renders the
forbidden.tsx
file:

1// app/api/admin/users/route.ts
2import { forbidden, unauthorized } from 'next/server';
3import { getSession } from '@/lib/auth';
4
5export async function GET(request: Request) {
6  const session = await getSession();
7
8  if (!session) {
9    unauthorized(); // 401 - not logged in
10  }
11
12  if (session.user.role !== 'admin') {
13    forbidden(); // 403 - missing permissions
14  }
15
16  return Response.json(await getAllUsers());
17}

Enabling the functions

These functions require an experimental flag:

1// next.config.js
2module.exports = {
3  experimental: {
4    authInterrupts: true,
5  },
6};

Creating error pages

unauthorized.tsx - 401 page

1// app/unauthorized.tsx
2export default function UnauthorizedPage() {
3  return (
4    <div className="min-h-screen flex items-center justify-center bg-gray-900">
5      <div className="text-center">
6        <h1 className="text-6xl font-bold text-red-500 mb-4">401</h1>
7        <h2 className="text-2xl text-white mb-4">Authorization required</h2>
8        <p className="text-gray-400 mb-8">
9          You must log in to access this page.
10        </p>
11        <a
12          href="/login"
13          className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
14        >
15          Log in
16        </a>
17      </div>
18    </div>
19  );
20}

forbidden.tsx - 403 page

1// app/forbidden.tsx
2export default function ForbiddenPage() {
3  return (
4    <div className="min-h-screen flex items-center justify-center bg-gray-900">
5      <div className="text-center">
6        <h1 className="text-6xl font-bold text-yellow-500 mb-4">403</h1>
7        <h2 className="text-2xl text-white mb-4">Access denied</h2>
8        <p className="text-gray-400 mb-8">
9          You do not have permission to view this page.
10        </p>
11        <a
12          href="/"
13          className="px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700"
14        >
15          Back to home page
16        </a>
17      </div>
18    </div>
19  );
20}

Nested error pages

You can create error pages specific to route segments:

1app/
2├── unauthorized.tsx          # Global 401 page
3├── forbidden.tsx             # Global 403 page
4├── admin/
5│   ├── unauthorized.tsx      # 401 for /admin/*
6│   ├── forbidden.tsx         # 403 for /admin/*
7│   └── page.tsx
8└── dashboard/
9    ├── forbidden.tsx         # 403 for /dashboard/*
10    └── page.tsx

Example - Admin forbidden.tsx

1// app/admin/forbidden.tsx
2import { getSession } from '@/lib/auth';
3
4export default async function AdminForbiddenPage() {
5  const session = await getSession();
6
7  return (
8    <div className="min-h-screen flex items-center justify-center bg-slate-900">
9      <div className="max-w-md text-center p-8 bg-slate-800 rounded-xl">
10        <div className="text-6xl mb-4">🔒</div>
11        <h1 className="text-2xl font-bold text-white mb-4">
12          Administrator area
13        </h1>
14        <p className="text-gray-400 mb-6">
15          Hi {session?.user?.name}! Unfortunately, your account does not have
16          administrator permissions. Contact the system administrator if you
17          believe this is a mistake.
18        </p>
19        <div className="flex gap-4 justify-center">
20          <a
21            href="/dashboard"
22            className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
23          >
24            User panel
25          </a>
26          <a
27            href="/support"
28            className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
29          >
30            Contact support
31          </a>
32        </div>
33      </div>
34    </div>
35  );
36}

Usage in Server Components

1// app/admin/page.tsx
2import { forbidden, unauthorized } from 'next/server';
3import { getSession } from '@/lib/auth';
4
5export default async function AdminPage() {
6  const session = await getSession();
7
8  if (!session) {
9    unauthorized();
10  }
11
12  if (!session.user.permissions.includes('admin:access')) {
13    forbidden();
14  }
15
16  const adminData = await getAdminDashboard();
17
18  return (
19    <div className="admin-dashboard">
20      <h1>Administrator Panel</h1>
21      <AdminStats data={adminData} />
22    </div>
23  );
24}

Usage in Middleware

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4import { getToken } from 'next-auth/jwt';
5
6export async function middleware(request: NextRequest) {
7  const token = await getToken({ req: request });
8  const path = request.nextUrl.pathname;
9
10  // Paths requiring login
11  if (path.startsWith('/dashboard')) {
12    if (!token) {
13      // Redirect to the unauthorized page
14      return NextResponse.rewrite(new URL('/unauthorized', request.url));
15    }
16  }
17
18  // Paths requiring the admin role
19  if (path.startsWith('/admin')) {
20    if (!token) {
21      return NextResponse.rewrite(new URL('/unauthorized', request.url));
22    }
23
24    if (token.role !== 'admin') {
25      return NextResponse.rewrite(new URL('/forbidden', request.url));
26    }
27  }
28
29  return NextResponse.next();
30}
31
32export const config = {
33  matcher: ['/dashboard/:path*', '/admin/:path*'],
34};

Practical example - Roles system

1// lib/auth-utils.ts
2import { forbidden, unauthorized } from 'next/server';
3import { getSession } from '@/lib/auth';
4
5type Permission = 'read' | 'write' | 'delete' | 'admin';
6
7export async function requireAuth() {
8  const session = await getSession();
9
10  if (!session) {
11    unauthorized();
12  }
13
14  return session;
15}
16
17export async function requirePermission(permission: Permission) {
18  const session = await requireAuth();
19
20  if (!session.user.permissions.includes(permission)) {
21    forbidden();
22  }
23
24  return session;
25}
26
27export async function requireRoles(role: string) {
28  const session = await requireAuth();
29
30  if (session.user.role !== role) {
31    forbidden();
32  }
33
34  return session;
35}
36
37export async function requireAdmin() {
38  return requireRoles('admin');
39}

Usage in components:

1// app/admin/users/page.tsx
2import { requireAdmin } from '@/lib/auth-utils';
3
4export default async function AdminUsersPage() {
5  const session = await requireAdmin(); // Will throw 401 or 403 if needed
6
7  const users = await getAllUsers();
8
9  return <UserManagement users={users} currentAdmin={session.user} />;
10}
1// app/api/posts/[id]/route.ts
2import { requirePermission } from '@/lib/auth-utils';
3
4export async function DELETE(
5  request: Request,
6  { params }: { params: Promise<{ id: string }> }
7) {
8  await requirePermission('delete'); // Will throw 401 or 403 if needed
9
10  await deletePost((await params).id);
11
12  return Response.json({ success: true });
13}

Differences between unauthorized() and forbidden()

| Aspect | unauthorized() | forbidden() | |--------|----------------|-------------| | HTTP code | 401 Unauthorized | 403 Forbidden | | Meaning | Not authenticated | Not authorized | | When to use | User not logged in | User logged in but without permissions | | Error page | unauthorized.tsx | forbidden.tsx | | Typical action | Redirect to login | Inform about missing access |

Summary

The

forbidden()
and
unauthorized()
functions in Next.js 15:

  • Simplify the code - a single line instead of building a Response
  • Standardize handling - consistent behavior across the whole app
  • Support SSR - work in Server Components and Route Handlers
  • Are configurable - custom error pages per segment

In the security systems of Metropolis Quantum 2150, where every access must be verified, these functions let you build solid access-control mechanisms with elegant code!

Go to CodeWorlds