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.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}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}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}These functions require an experimental flag:
1// next.config.js
2module.exports = {
3 experimental: {
4 authInterrupts: true,
5 },
6};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}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}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.tsx1// 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}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}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};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}| 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 |
The
forbidden() and unauthorized() functions in Next.js 15: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!