Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

forbidden() i unauthorized() - Nowe funkcje obsługi błędów

W systemach bezpieczeństwa Metropolii Quantum 2150, gdzie kontrola dostępu jest kluczowa, poznasz nowe funkcje

forbidden()
i
unauthorized()
wprowadzone w Next.js 15. Te funkcje upraszczają obsługę błędów autoryzacji i autentykacji.

Problem: Ręczna obsługa błędów 401/403

Wcześniej obsługa błędów autoryzacji wymagała ręcznego tworzenia Response:

1// ❌ Stary, rozwlekły sposób
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}

Rozwiązanie: forbidden() i unauthorized()

unauthorized() - Błąd 401

Funkcja

unauthorized()
rzuca błąd 401 i renderuje plik
unauthorized.tsx
:

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(); // Rzuca błąd 401
10  }
11
12  return Response.json(session.user);
13}

forbidden() - Błąd 403

Funkcja

forbidden()
rzuca błąd 403 i renderuje plik
forbidden.tsx
:

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 - niezalogowany
10  }
11
12  if (session.user.role !== 'admin') {
13    forbidden(); // 403 - brak uprawnień
14  }
15
16  return Response.json(await getAllUsers());
17}

Włączenie funkcji

Te funkcje wymagają flagi eksperymentalnej:

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

Tworzenie stron błędów

unauthorized.tsx - Strona 401

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">Brak autoryzacji</h2>
8        <p className="text-gray-400 mb-8">
9          Musisz się zalogować, aby uzyskać dostęp do tej strony.
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          Zaloguj się
16        </a>
17      </div>
18    </div>
19  );
20}

forbidden.tsx - Strona 403

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">Dostęp zabroniony</h2>
8        <p className="text-gray-400 mb-8">
9          Nie masz uprawnień do wyświetlenia tej strony.
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          Wróć na stronę główną
16        </a>
17      </div>
18    </div>
19  );
20}

Zagnieżdżone strony błędów

Możesz tworzyć strony błędów specyficzne dla segmentów:

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

Przykład - 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          Strefa administratora
13        </h1>
14        <p className="text-gray-400 mb-6">
15          Witaj {session?.user?.name}! Niestety, twoje konto nie ma uprawnień
16          administratora. Skontaktuj się z administratorem systemu, jeśli
17          uważasz, że to błąd.
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            Panel użytkownika
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            Kontakt z supportem
31          </a>
32        </div>
33      </div>
34    </div>
35  );
36}

Użycie w 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>Panel Administratora</h1>
21      <AdminStats data={adminData} />
22    </div>
23  );
24}

Użycie w 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  // Ścieżki wymagające logowania
11  if (path.startsWith('/dashboard')) {
12    if (!token) {
13      // Przekierowanie do strony unauthorized
14      return NextResponse.rewrite(new URL('/unauthorized', request.url));
15    }
16  }
17
18  // Ścieżki wymagające roli admin
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};

Praktyczny przykład - System ról

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 requireRole(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 requireRole('admin');
39}

Użycie w komponentach:

1// app/admin/users/page.tsx
2import { requireAdmin } from '@/lib/auth-utils';
3
4export default async function AdminUsersPage() {
5  const session = await requireAdmin(); // Rzuci 401 lub 403 jeśli trzeba
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'); // Rzuci 401 lub 403 jeśli trzeba
9
10  await deletePost((await params).id);
11
12  return Response.json({ success: true });
13}

Różnice między unauthorized() a forbidden()

| Aspekt | unauthorized() | forbidden() | |--------|----------------|-------------| | Kod HTTP | 401 Unauthorized | 403 Forbidden | | Znaczenie | Brak autentykacji | Brak autoryzacji | | Kiedy używać | Użytkownik niezalogowany | Użytkownik zalogowany, ale bez uprawnień | | Strona błędu | unauthorized.tsx | forbidden.tsx | | Typowa akcja | Przekierowanie do logowania | Informacja o braku dostępu |

Podsumowanie

Funkcje

forbidden()
i
unauthorized()
w Next.js 15:

  • Upraszczają kod - jedna linia zamiast tworzenia Response
  • Standaryzują obsługę - spójne zachowanie w całej aplikacji
  • Wspierają SSR - działają w Server Components i Route Handlers
  • Są konfigurowalne - własne strony błędów per segment

W systemach bezpieczeństwa Metropolii Quantum 2150, gdzie każdy dostęp musi być weryfikowany, te funkcje pozwalają budować solidne mechanizmy kontroli dostępu z eleganckim kodem!

Vai a CodeWorlds