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.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}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}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}Te funkcje wymagają flagi eksperymentalnej:
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">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}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}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.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 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}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}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};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}| 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 |
Funkcje
forbidden() i unauthorized() w Next.js 15: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!