W poprzednich modułach omówiliśmy podstawy uwierzytelniania w Next.js oraz implementację logowania z zewnętrznymi dostawcami. Teraz zagłębimy się w kluczowy aspekt każdego systemu uwierzytelniania: zarządzanie sesjami użytkowników i tokenami. Zrozumienie tych mechanizmów jest niezbędne do budowania bezpiecznych i wydajnych aplikacji internetowych.
Sesja użytkownika to mechanizm, który pozwala aplikacji "pamiętać" zalogowanego użytkownika podczas nawigacji między stronami i żądaniami. W nowoczesnych aplikacjach webowych sesje są implementowane na dwa główne sposoby:
W Next.js, a szczególnie w połączeniu z NextAuth.js, mamy dostęp do obu tych podejść.
NextAuth.js oferuje dwie główne strategie zarządzania sesjami:
W tej strategii wszystkie informacje o sesji są przechowywane w tokenie JWT (JSON Web Token), który jest zapisywany w ciasteczku:
1// Konfiguracja strategii JWT w NextAuth.js
2export const authOptions = {
3 providers: [
4 // Konfiguracja dostawców...
5 ],
6 session: {
7 strategy: "jwt",
8 maxAge: 30 * 24 * 60 * 60, // 30 dni w sekundach
9 updateAge: 24 * 60 * 60, // 24 godziny w sekundach
10 },
11 jwt: {
12 secret: process.env.NEXTAUTH_SECRET,
13 // Można również użyć kluczy prywatnych/publicznych zamiast prostego sekretu
14 // encryption: true, // Włącza szyfrowanie JWT
15 // signingKey: process.env.JWT_SIGNING_PRIVATE_KEY,
16 // encryptionKey: process.env.JWT_ENCRYPTION_KEY,
17 }
18};Zalety:
Wady:
W tej strategii tylko identyfikator sesji jest przechowywany w ciasteczku, a pozostałe dane sesji są przechowywane w bazie danych:
1// Konfiguracja strategii bazy danych w NextAuth.js
2import { PrismaAdapter } from "@auth/prisma-adapter";
3import { PrismaClient } from "@prisma/client";
4
5const prisma = new PrismaClient();
6
7export const authOptions = {
8 providers: [
9 // Konfiguracja dostawców...
10 ],
11 adapter: PrismaAdapter(prisma),
12 session: {
13 strategy: "database",
14 maxAge: 30 * 24 * 60 * 60, // 30 dni w sekundach
15 updateAge: 24 * 60 * 60, // 24 godziny w sekundach
16 }
17};Zalety:
Wady:
Kiedy używamy strategii JWT, NextAuth.js tworzy token zawierający podstawowe informacje o użytkowniku:
1{
2 "name": "John Doe",
3 "email": "john@example.com",
4 "sub": "cl5ab1gxk0001t8kgxs4t8j1h", // identyfikator użytkownika
5 "iat": 1658309600, // czas wystawienia tokenu
6 "exp": 1660901600, // czas wygaśnięcia tokenu
7 "jti": "4b32c6a4-d2c5-4f1a-a2b3-5c84c1d94208" // unikalny identyfikator tokenu
8}Możemy rozszerzyć token o dodatkowe dane za pomocą callbacka
jwt:1export const authOptions = {
2 //
3 callbacks: {
4 async jwt({ token, user, account }) {
5 // Przy pierwszym logowaniu mamy dostęp do obiektu użytkownika
6 if (user) {
7 token.role = user.role;
8 token.userId = user.id;
9 token.provider = account?.provider;
10 }
11
12 // Możemy tu dodać logikę odświeżania tokenu
13 // np. sprawdzanie, czy token dostępu OAuth nie wygasł
14
15 return token;
16 },
17
18 async session({ session, token }) {
19 // Dodaj dane z tokenu do obiektu sesji (dostępne w kliencie)
20 if (session.user) {
21 session.user.role = token.role;
22 session.user.id = token.userId;
23 session.user.provider = token.provider;
24 }
25
26 return session;
27 }
28 }
29};NextAuth.js domyślnie konfiguruje ciasteczka sesji zgodnie z najlepszymi praktykami bezpieczeństwa, ale możemy te ustawienia dostosować:
1export const authOptions = {
2 //
3 cookies: {
4 sessionToken: {
5 name: `__Secure-next-auth.session-token`,
6 options: {
7 httpOnly: true,
8 sameSite: "lax",
9 path: "/",
10 secure: true, // Ustaw na false podczas lokalnego testowania bez HTTPS
11 maxAge: 30 * 24 * 60 * 60, // 30 dni w sekundach
12 },
13 },
14 // Możesz też skonfigurować inne ciasteczka używane przez NextAuth.js
15 callbackUrl: {
16 name: `__Secure-next-auth.callback-url`,
17 options: { /* ... */ },
18 },
19 csrfToken: {
20 name: `__Host-next-auth.csrf-token`,
21 options: { /* ... */ },
22 },
23 },
24};W komponentach klienckich możemy używać hooka
useSession do uzyskania dostępu do danych sesji i jej statusu:1'use client';
2
3import { useSession } from 'next-auth/react';
4
5export default function ProfileComponent() {
6 const { data: session, status } = useSession();
7
8 if (status === 'loading') {
9 return <p>Ładowanie...</p>;
10 }
11
12 if (status === 'unauthenticated') {
13 return <p>Dostęp tylko dla zalogowanych użytkowników</p>;
14 }
15
16 return (
17 <div>
18 <h1>Profil</h1>
19 <p>Witaj, {session.user.name}!</p>
20 <p>Email: {session.user.email}</p>
21 {session.user.role === 'admin' && (
22 <p>Masz uprawnienia administratora</p>
23 )}
24 </div>
25 );
26}Możemy również wymagać sesji za pomocą opcji
required:1'use client';
2
3import { useSession } from 'next-auth/react';
4import { redirect } from 'next/navigation';
5
6export default function ProtectedComponent() {
7 const { data: session, status } = useSession({
8 required: true,
9 onUnauthenticated() {
10 redirect('/auth/login?callbackUrl=/profile');
11 },
12 });
13
14 // Gdy ustawione jest required, nie musimy sprawdzać statusu 'unauthenticated'
15 if (status === 'loading') {
16 return <p>Ładowanie...</p>;
17 }
18
19 return (
20 <div>
21 <h1>Chroniona zawartość</h1>
22 <p>Ta strona jest widoczna tylko dla zalogowanych użytkowników.</p>
23 </div>
24 );
25}W komponentach serverowych lub Route Handlers możemy używać
getServerSession do uzyskania dostępu do danych sesji:1// Komponent serverowy (Server Component)
2import { getServerSession } from "next-auth/next";
3import { redirect } from "next/navigation";
4import { authOptions } from "@/app/api/auth/[...nextauth]/route";
5
6export default async function ServerProfilePage() {
7 const session = await getServerSession(authOptions);
8
9 if (!session) {
10 redirect('/auth/login?callbackUrl=/profile');
11 }
12
13 return (
14 <div>
15 <h1>Profil (renderowany na serwerze)</h1>
16 <p>Witaj, {session.user.name}!</p>
17 <p>Email: {session.user.email}</p>
18 {/* Zawartość widoczna tylko dla zalogowanych użytkowników */}
19 </div>
20 );
21}
22
23// Route Handler (API Route)
24import { getServerSession } from "next-auth/next";
25import { NextResponse } from "next/server";
26import { authOptions } from "@/app/api/auth/[...nextauth]/route";
27
28export async function GET() {
29 const session = await getServerSession(authOptions);
30
31 if (!session) {
32 return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
33 }
34
35 // Pobierz dane specyficzne dla zalogowanego użytkownika
36 const userData = await fetchUserData(session.user.id);
37
38 return NextResponse.json(userData);
39}Jeśli korzystamy z dostawców OAuth, możemy chcieć przechowywać i odświeżać tokeny dostępu, aby używać API tych dostawców:
1export const authOptions = {
2 //
3 callbacks: {
4 async jwt({ token, user, account }) {
5 // Przy pierwszym logowaniu zapisz tokeny dostępu/odświeżania
6 if (account && user) {
7 token.accessToken = account.access_token;
8 token.refreshToken = account.refresh_token;
9 token.accessTokenExpires = account.expires_at * 1000; // Konwersja na milisekundy
10 token.userId = user.id;
11 return token;
12 }
13
14 // Jeśli token dostępu nie wygasł, zwróć go
15 if (Date.now() < token.accessTokenExpires) {
16 return token;
17 }
18
19 // Jeśli token dostępu wygasł, odśwież go
20 return refreshAccessToken(token);
21 },
22
23 async session({ session, token }) {
24 // Przekaż token dostępu do klienta (ale nie token odświeżania!)
25 session.accessToken = token.accessToken;
26 session.user.id = token.userId;
27
28 return session;
29 },
30 },
31};
32
33async function refreshAccessToken(token) {
34 try {
35 // Przykład odświeżania tokenu Google OAuth
36 const url = "https://oauth2.googleapis.com/token";
37 const response = await fetch(url, {
38 headers: { "Content-Type": "application/x-www-form-urlencoded" },
39 method: "POST",
40 body: new URLSearchParams({
41 client_id: process.env.GOOGLE_CLIENT_ID,
42 client_secret: process.env.GOOGLE_CLIENT_SECRET,
43 grant_type: "refresh_token",
44 refresh_token: token.refreshToken,
45 }),
46 });
47
48 const refreshedTokens = await response.json();
49
50 if (!response.ok) {
51 throw refreshedTokens;
52 }
53
54 return {
55 ...token,
56 accessToken: refreshedTokens.access_token,
57 accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
58 // Zachowaj refresh token, chyba że otrzymaliśmy nowy
59 refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
60 };
61 } catch (error) {
62 console.error("Error refreshing access token", error);
63 // Token odświeżania mógł wygasnąć, użytkownik będzie musiał się ponownie zalogować
64 return {
65 ...token,
66 error: "RefreshAccessTokenError",
67 };
68 }
69}NextAuth.js udostępnia funkcję
signOut() do wylogowywania:1'use client';
2
3import { signOut } from 'next-auth/react';
4
5export function LogoutButton() {
6 return (
7 <button
8 onClick={() => signOut({ callbackUrl: '/' })}
9 className="bg-red-500 text-white px-4 py-2 rounded"
10 >
11 Wyloguj się
12 </button>
13 );
14}W przypadku strategii opartej na bazie danych, możemy ręcznie unieważnić sesję, usuwając ją z bazy danych:
1// app/api/auth/invalidate-sessions/route.ts
2import { NextResponse } from "next/server";
3import { getServerSession } from "next-auth/next";
4import { authOptions } from "@/app/api/auth/[...nextauth]/route";
5import { prisma } from "@/lib/prisma";
6
7export async function POST(request: Request) {
8 const session = await getServerSession(authOptions);
9
10 if (!session || session.user.role !== 'admin') {
11 return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
12 }
13
14 const { userId } = await request.json();
15
16 // Usuń wszystkie sesje użytkownika o podanym ID
17 await prisma.session.deleteMany({
18 where: {
19 userId,
20 },
21 });
22
23 return NextResponse.json({ success: true });
24}W przypadku strategii JWT jest to trudniejsze, ponieważ tokeny są samodzielne. Możemy jednak zaimplementować "czarną listę" tokenów lub mechanizm rotacji sekretów:
1// Implementacja czarnej listy dla tokena JWT
2const revokedTokens = new Set();
3
4export const authOptions = {
5 //
6 callbacks: {
7 async jwt({ token }) {
8 // Sprawdź, czy token jest na czarnej liście
9 if (revokedTokens.has(token.jti)) {
10 return null; // Token unieważniony
11 }
12 return token;
13 },
14 },
15 events: {
16 async signOut({ token }) {
17 // Dodaj token do czarnej listy przy wylogowaniu
18 if (token?.jti) {
19 revokedTokens.add(token.jti);
20 }
21 },
22 },
23};To proste rozwiązanie działa tylko dla pojedynczego serwera. W przypadku wielu serwerów lub rozwiązań serverless, musielibyśmy użyć współdzielonego magazynu danych (np. Redis) do przechowywania listy unieważnionych tokenów.
Możemy zaimplementować mechanizm wykrywania nieaktywności użytkownika i automatycznego wylogowywania:
1'use client';
2
3import { useSession, signOut } from 'next-auth/react';
4import { useEffect, useState } from 'react';
5
6export function InactivityDetector({ timeoutMinutes = 30 }) {
7 const { data: session } = useSession();
8
9 useEffect(() => {
10 if (!session) return;
11
12 let timeout;
13 const timeoutMs = timeoutMinutes * 60 * 1000;
14
15 const resetTimeout = () => {
16 if (timeout) clearTimeout(timeout);
17 timeout = setTimeout(() => {
18 signOut({ callbackUrl: '/auth/login?timeout=true' });
19 }, timeoutMs);
20 };
21
22 // Zresetuj timer przy każdej aktywności użytkownika
23 const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
24
25 // Ustaw początkowy timer
26 resetTimeout();
27
28 // Dodaj nasłuchiwanie zdarzeń
29 events.forEach(event => {
30 window.addEventListener(event, resetTimeout);
31 });
32
33 return () => {
34 // Wyczyść timer i nasłuchiwanie przy odmontowaniu
35 if (timeout) clearTimeout(timeout);
36 events.forEach(event => {
37 window.removeEventListener(event, resetTimeout);
38 });
39 };
40 }, [session, timeoutMinutes]);
41
42 // Komponent nie renderuje niczego widocznego
43 return null;
44}Następnie można dodać ten komponent do głównego layoutu aplikacji:
1// app/layout.tsx
2import { Providers } from "./providers";
3import { InactivityDetector } from "@/components/InactivityDetector";
4
5export default function RootLayout({
6 children,
7}: {
8 children: React.ReactNode;
9}) {
10 return (
11 <html lang="pl">
12 <body>
13 <Providers>
14 {children}
15 <InactivityDetector timeoutMinutes={30} />
16 </Providers>
17 </body>
18 </html>
19 );
20}Jeśli używamy strategii opartej na bazie danych, możemy zaimplementować zarządzanie wieloma sesjami dla tego samego użytkownika, umożliwiając mu przegląd aktywnych sesji i wylogowanie z konkretnych urządzeń:
1// app/settings/sessions/page.tsx
2import { getServerSession } from "next-auth/next";
3import { redirect } from "next/navigation";
4import { authOptions } from "@/app/api/auth/[...nextauth]/route";
5import { prisma } from "@/lib/prisma";
6import { SessionList } from "./SessionList";
7
8export default async function SessionsPage() {
9 const session = await getServerSession(authOptions);
10
11 if (!session) {
12 redirect('/auth/login?callbackUrl=/settings/sessions');
13 }
14
15 // Pobierz wszystkie aktywne sesje użytkownika
16 const userSessions = await prisma.session.findMany({
17 where: {
18 userId: session.user.id,
19 expires: {
20 gt: new Date(), // Tylko aktywne sesje
21 },
22 },
23 orderBy: {
24 expires: 'desc',
25 },
26 });
27
28 // Oznacz aktualną sesję
29 const currentSessionToken = session.user.sessionToken; // Musisz dostosować, aby mieć dostęp do tokenu
30 const enhancedSessions = userSessions.map(s => ({
31 ...s,
32 isCurrent: s.sessionToken === currentSessionToken,
33 }));
34
35 return (
36 <div className="max-w-4xl mx-auto p-6">
37 <h1 className="text-2xl font-bold mb-6">Aktywne sesje</h1>
38 <SessionList sessions={enhancedSessions} currentSessionId={session.user.id} />
39 </div>
40 );
41}
42
43// app/settings/sessions/SessionList.tsx (komponent kliencki)
44'use client';
45
46import { useState } from 'react';
47import { useRouter } from 'next/navigation';
48
49export function SessionList({ sessions, currentSessionId }) {
50 const [isLoading, setIsLoading] = useState(false);
51 const [error, setError] = useState('');
52 const router = useRouter();
53
54 const handleRevokeSession = async (sessionToken) => {
55 if (confirm('Czy na pewno chcesz wylogować to urządzenie?')) {
56 setIsLoading(true);
57 setError('');
58
59 try {
60 const response = await fetch('/api/auth/revoke-session', {
61 method: 'POST',
62 headers: {
63 'Content-Type': 'application/json',
64 },
65 body: JSON.stringify({ sessionToken }),
66 });
67
68 if (!response.ok) {
69 throw new Error('Nie udało się unieważnić sesji');
70 }
71
72 // Odśwież stronę
73 router.refresh();
74 } catch (e) {
75 setError(e.message);
76 } finally {
77 setIsLoading(false);
78 }
79 }
80 };
81
82 const formatDate = (dateString) => {
83 return new Date(dateString).toLocaleString();
84 };
85
86 return (
87 <div>
88 {error && (
89 <div className="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg">
90 {error}
91 </div>
92 )}
93
94 <div className="overflow-x-auto">
95 <table className="min-w-full divide-y divide-gray-200">
96 <thead className="bg-gray-50">
97 <tr>
98 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Urządzenie</th>
99 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ostatnia aktywność</th>
100 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Wygasa</th>
101 <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Akcje</th>
102 </tr>
103 </thead>
104 <tbody className="bg-white divide-y divide-gray-200">
105 {sessions.map((session) => (
106 <tr key={session.id}>
107 <td className="px-6 py-4 whitespace-nowrap">
108 <div className="flex items-center">
109 <div className="ml-4">
110 <div className="text-sm font-medium text-gray-900">
111 {session.isCurrent ? 'Aktualne urządzenie' : 'Inne urządzenie'}
112 </div>
113 </div>
114 </div>
115 </td>
116 <td className="px-6 py-4 whitespace-nowrap">
117 <div className="text-sm text-gray-900">{formatDate(session.updatedAt)}</div>
118 </td>
119 <td className="px-6 py-4 whitespace-nowrap">
120 <div className="text-sm text-gray-900">{formatDate(session.expires)}</div>
121 </td>
122 <td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
123 <button
124 onClick={() => handleRevokeSession(session.sessionToken)}
125 disabled={isLoading || session.isCurrent}
126 className={`text-red-600 hover:text-red-900 ${(isLoading || session.isCurrent) ? 'opacity-50 cursor-not-allowed' : ''}`}
127 >
128 {session.isCurrent ? 'Aktualna sesja' : 'Wyloguj'}
129 </button>
130 </td>
131 </tr>
132 ))}
133 </tbody>
134 </table>
135 </div>
136 </div>
137 );
138}
139
140// app/api/auth/revoke-session/route.ts
141import { NextResponse } from "next/server";
142import { getServerSession } from "next-auth/next";
143import { authOptions } from "@/app/api/auth/[...nextauth]/route";
144import { prisma } from "@/lib/prisma";
145
146export async function POST(request: Request) {
147 const session = await getServerSession(authOptions);
148
149 if (!session) {
150 return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
151 }
152
153 const { sessionToken } = await request.json();
154
155 // Znajdź sesję, upewniając się, że należy do zalogowanego użytkownika
156 const sessionToRevoke = await prisma.session.findFirst({
157 where: {
158 sessionToken,
159 userId: session.user.id,
160 },
161 });
162
163 if (!sessionToRevoke) {
164 return NextResponse.json(
165 { error: "Session not found or not owned by current user" },
166 { status: 404 }
167 );
168 }
169
170 // Usuń sesję
171 await prisma.session.delete({
172 where: {
173 id: sessionToRevoke.id,
174 },
175 });
176
177 return NextResponse.json({ success: true });
178}Przy implementacji zarządzania sesją i tokenami, należy pamiętać o kilku kluczowych aspektach bezpieczeństwa:
Używaj silnych sekretów - upewnij się, że
NEXTAUTH_SECRET jest długim, losowym ciągiem znakówZabezpiecz ciasteczka - używaj flag:
HttpOnly: zapobiega dostępowi do ciasteczka przez JavaScriptSecure: wymaga HTTPSSameSite: chroni przed atakami CSRFUstaw odpowiedni czas życia - tokeny/sesje nie powinny być ważne przez zbyt długi okres
Ogranicz dane w tokenie - token JWT powinien zawierać minimum niezbędnych informacji
Implementuj odwołanie sesji - umożliw użytkownikom wylogowanie się ze wszystkich urządzeń
Monitoruj podejrzane działania - wykrywaj próby wielokrotnego logowania i nietypowe wzorce dostępu
Rotacja sekretów - okresowo zmieniaj sekrety używane do podpisywania tokenów
Testy są kluczowym elementem zapewnienia niezawodności mechanizmów sesji i tokenów. Oto przykład testu jednostkowego dla funkcjonalności odświeżania tokenu:
1// __tests__/refreshToken.test.ts
2import { refreshAccessToken } from '@/lib/auth';
3import fetchMock from 'jest-fetch-mock';
4
5fetchMock.enableMocks();
6
7beforeEach(() => {
8 fetchMock.resetMocks();
9});
10
11describe('refreshAccessToken', () => {
12 it('should refresh the token successfully', async () => {
13 // Przygotuj mock odpowiedzi z serwera OAuth
14 fetchMock.mockResponseOnce(JSON.stringify({
15 access_token: 'new_access_token',
16 expires_in: 3600
17 }));
18
19 const originalToken = {
20 accessToken: 'old_access_token',
21 refreshToken: 'refresh_token',
22 accessTokenExpires: Date.now() - 1000, // Wygasły token
23 };
24
25 const refreshedToken = await refreshAccessToken(originalToken);
26
27 expect(refreshedToken.accessToken).toBe('new_access_token');
28 expect(refreshedToken.refreshToken).toBe('refresh_token'); // Powinien zachować stary refresh token
29 expect(refreshedToken.accessTokenExpires).toBeGreaterThan(Date.now());
30 expect(fetchMock).toHaveBeenCalledTimes(1);
31 });
32
33 it('should handle refresh token errors', async () => {
34 // Symuluj błąd odświeżania
35 fetchMock.mockRejectOnce(new Error('Failed to refresh token'));
36
37 const originalToken = {
38 accessToken: 'old_access_token',
39 refreshToken: 'refresh_token',
40 accessTokenExpires: Date.now() - 1000,
41 };
42
43 const result = await refreshAccessToken(originalToken);
44
45 expect(result.error).toBe('RefreshAccessTokenError');
46 expect(result.accessToken).toBe('old_access_token'); // Powinien zachować stary token
47 });
48});W architekturze Next.js App Router, ważne jest, aby przestrzegać następujących zasad przy implementacji uwierzytelniania:
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
9 // Sprawdź, czy użytkownik ma dostęp do żądanej strony
10 if (!token) {
11 const url = new URL('/auth/login', request.url);
12 url.searchParams.set('callbackUrl', request.nextUrl.pathname);
13 return NextResponse.redirect(url);
14 }
15
16 // Sprawdzanie uprawnień na podstawie ścieżki i roli
17 if (request.nextUrl.pathname.startsWith('/admin') && token.role !== 'admin') {
18 return NextResponse.redirect(new URL('/dashboard', request.url));
19 }
20
21 return NextResponse.next();
22}
23
24export const config = {
25 matcher: [
26 '/dashboard/:path*',
27 '/settings/:path*',
28 '/admin/:path*',
29 '/api/protected/:path*'
30 ],
31};1// app/admin/dashboard/page.tsx (Server Component)
2import { getServerSession } from "next-auth/next";
3import { redirect } from "next/navigation";
4import { authOptions } from "@/app/api/auth/[...nextauth]/route";
5
6export default async function AdminDashboard() {
7 const session = await getServerSession(authOptions);
8
9 if (!session || session.user.role !== 'admin') {
10 redirect('/');
11 }
12
13 // Pobierz dane administracyjne bezpośrednio na serwerze
14 // bez narażania API keys lub innych wrażliwych informacji klientowi
15 const adminData = await fetchAdminData();
16
17 return (
18 <div>
19 <h1>Panel administratora</h1>
20 {/* Renderuj dane administratora */}
21 </div>
22 );
23}1// app/api/protected/user-data/route.ts
2import { getServerSession } from "next-auth/next";
3import { NextResponse } from "next/server";
4import { authOptions } from "@/app/api/auth/[...nextauth]/route";
5
6export async function GET() {
7 const session = await getServerSession(authOptions);
8
9 if (!session) {
10 return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
11 }
12
13 // Wykonaj chronione operacje
14 const userData = await fetchUserData(session.user.id);
15
16 // Zwróć dane bez wrażliwych informacji
17 const sanitizedData = sanitizeUserData(userData);
18
19 return NextResponse.json(sanitizedData);
20}W tym module omówiliśmy szczegółowo, jak zarządzać sesjami użytkowników i tokenami w aplikacjach Next.js:
Właściwe zarządzanie sesjami i tokenami jest kluczowym aspektem bezpieczeństwa aplikacji internetowych. Dobrze zaprojektowany system uwierzytelniania nie tylko chroni dane użytkowników, ale także zapewnia dobre doświadczenie użytkownika, umożliwiając łatwe logowanie, wylogowywanie i zarządzanie dostępem z różnych urządzeń.
W następnym module zajmiemy się implementacją systemu ról i uprawnień, który pozwoli na bardziej granularne zarządzanie dostępem użytkowników do różnych funkcji aplikacji.