Kurs Next.js · Moduł 7: Uwierzytelnianie i autoryzacja
Podstawy uwierzytelniania w aplikacjach Next.js
W tej lekcji8
Uwierzytelnianie (ang. authentication) to fundamentalny aspekt nowoczesnych aplikacji webowych. Pozwala zweryfikować tożsamość użytkowników, co jest niezbędne do personalizacji doświadczeń, ochrony danych i udostępniania funkcji tylko uprawnionym osobom. W tym module poznamy podstawy implementacji uwierzytelniania w aplikacjach Next.js, które łączy zalety renderowania po stronie serwera (SSR) i klienta.
Czym jest uwierzytelnianie i dlaczego jest ważne?
Uwierzytelnianie to proces weryfikacji tożsamości użytkownika. Jest to kluczowy element zabezpieczeń aplikacji internetowych, ponieważ:
- Chroni dane użytkowników - zapewnia, że osobiste informacje są dostępne tylko dla ich właścicieli
- Umożliwia personalizację - pozwala na dostosowanie doświadczenia do konkretnego użytkownika
- Kontroluje dostęp - określa, do jakich zasobów i funkcji użytkownik ma dostęp
- Zapewnia odpowiedzialność - umożliwia śledzenie, kto i kiedy wykonywał określone działania
Uwierzytelnianie vs. Autoryzacja
Ważne jest rozróżnienie dwóch powiązanych, ale oddzielnych koncepcji:
- Uwierzytelnianie (Authentication) - weryfikacja tożsamości użytkownika ("Czy jesteś tym, za kogo się podajesz?")
- Autoryzacja (Authorization) - określanie, co uwierzytelniony użytkownik może robić ("Czy masz prawo to zrobić?")
W wielu przypadkach oba te procesy są ze sobą powiązane, ale pełnią różne funkcje w architekturze bezpieczeństwa.
Specyfika uwierzytelniania w Next.js
Next.js oferuje hybrydowy model renderowania, co ma znaczący wpływ na implementację uwierzytelniania:
- Renderowanie po stronie serwera (SSR) - umożliwia weryfikację uwierzytelnienia przed wysłaniem strony do klienta
- Statyczna generacja stron (SSG) - wymaga dodatkowej weryfikacji klienta dla chronionych treści
- Komponent App Router - zapewnia nowe, dedykowane mechanizmy do obsługi uwierzytelniania
Podstawowe modele uwierzytelniania
W aplikacjach Next.js można zastosować kilka głównych modeli uwierzytelniania:
1. Uwierzytelnianie oparte na sesji
Tradycyjne podejście polegające na utrzymywaniu stanu sesji na serwerze:
1// Przykład API route do logowania z sesją
2import { NextApiRequest, NextApiResponse } from 'next';
3import { getServerSession } from 'next-auth/next';
4import { authOptions } from '../auth/[...nextauth]';
5
6export default async function handler(req: NextApiRequest, res: NextApiResponse) {
7 const session = await getServerSession(req, res, authOptions);
8
9 if (!session) {
10 return res.status(401).json({ message: 'Unauthorized' });
11 }
12
13 // Użytkownik uwierzytelniony, można kontynuować
14 return res.status(200).json({ user: session.user });
15}2. Uwierzytelnianie oparte na tokenach (JWT)
Popularne podejście wykorzystujące tokeny JWT (JSON Web Tokens):
1// Przykład API route do weryfikacji tokenu JWT
2import { NextApiRequest, NextApiResponse } from 'next';
3import jwt from 'jsonwebtoken';
4
5export default function handler(req: NextApiRequest, res: NextApiResponse) {
6 const { authorization } = req.headers;
7
8 if (!authorization || !authorization.startsWith('Bearer ')) {
9 return res.status(401).json({ message: 'Unauthorized' });
10 }
11
12 const token = authorization.replace('Bearer ', '');
13
14 try {
15 const decoded = jwt.verify(token, process.env.JWT_SECRET!);
16 // Token prawidłowy, można kontynuować
17 return res.status(200).json({ user: decoded });
18 } catch (error) {
19 return res.status(401).json({ message: 'Invalid token' });
20 }
21}3. Uwierzytelnianie z zewnętrznymi dostawcami (OAuth)
Wykorzystanie serwisów zewnętrznych jak Google, Facebook czy GitHub:
1// Przykład konfiguracji NextAuth.js z uwierzytelnianiem Google
2import NextAuth from 'next-auth';
3import GoogleProvider from 'next-auth/providers/google';
4
5export default NextAuth({
6 providers: [
7 GoogleProvider({
8 clientId: process.env.GOOGLE_CLIENT_ID!,
9 clientSecret: process.env.GOOGLE_CLIENT_SECRET!
10 })
11 ],
12 // Dodatkowa konfiguracja...
13});Implementacja uwierzytelniania w Next.js App Router
W nowszych wersjach Next.js (13+) z App Router, podejście do uwierzytelniania zostało ulepszone. Poniżej przedstawiamy podstawowe kroki:
1. Konfiguracja dostawcy uwierzytelniania
Najpierw musimy skonfigurować dostawcę uwierzytelniania. W tym przykładzie użyjemy Auth.js (dawniej NextAuth.js):
1// app/api/auth/[...nextauth]/route.ts
2import NextAuth from 'next-auth';
3import CredentialsProvider from 'next-auth/providers/credentials';
4import { compare } from 'bcrypt';
5import { prisma } from '@/lib/prisma';
6
7export const authOptions = {
8 providers: [
9 CredentialsProvider({
10 name: 'Credentials',
11 credentials: {
12 email: { label: "Email", type: "email" },
13 password: { label: "Password", type: "password" }
14 },
15 async authorize(credentials) {
16 if (!credentials?.email || !credentials?.password) {
17 return null;
18 }
19
20 const user = await prisma.user.findUnique({
21 where: {
22 email: credentials.email
23 }
24 });
25
26 if (!user) {
27 return null;
28 }
29
30 const isPasswordValid = await compare(
31 credentials.password,
32 user.password
33 );
34
35 if (!isPasswordValid) {
36 return null;
37 }
38
39 return {
40 id: user.id,
41 email: user.email,
42 name: user.name
43 };
44 }
45 })
46 ],
47 pages: {
48 signIn: '/login',
49 signOut: '/logout',
50 error: '/login',
51 },
52 session: {
53 strategy: 'jwt'
54 }
55};
56
57const handler = NextAuth(authOptions);
58export { handler as GET, handler as POST };2. Tworzenie komponentów do logowania i rejestracji
Następnie potrzebujemy interfejsu użytkownika do logowania:
1// app/login/page.tsx
2'use client';
3
4import { useState } from 'react';
5import { signIn } from 'next-auth/react';
6import { useRouter } from 'next/navigation';
7
8export default function LoginPage() {
9 const router = useRouter();
10 const [email, setEmail] = useState('');
11 const [password, setPassword] = useState('');
12 const [error, setError] = useState('');
13 const [loading, setLoading] = useState(false);
14
15 async function handleSubmit(e: React.FormEvent) {
16 e.preventDefault();
17 setLoading(true);
18 setError('');
19
20 try {
21 const result = await signIn('credentials', {
22 redirect: false,
23 email,
24 password
25 });
26
27 if (result?.error) {
28 setError('Nieprawidłowy email lub hasło');
29 } else {
30 router.push('/dashboard');
31 router.refresh();
32 }
33 } catch (error) {
34 setError('Wystąpił błąd podczas logowania');
35 } finally {
36 setLoading(false);
37 }
38 }
39
40 return (
41 <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
42 <h1 className="text-2xl font-bold mb-6">Logowanie</h1>
43
44 {error && (
45 <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
46 {error}
47 </div>
48 )}
49
50 <form onSubmit={handleSubmit} className="space-y-4">
51 <div>
52 <label htmlFor="email" className="block text-sm font-medium mb-1">
53 Email
54 </label>
55 <input
56 id="email"
57 type="email"
58 value={email}
59 onChange={(e) => setEmail(e.target.value)}
60 required
61 className="w-full px-3 py-2 border border-gray-300 rounded"
62 />
63 </div>
64
65 <div>
66 <label htmlFor="password" className="block text-sm font-medium mb-1">
67 Hasło
68 </label>
69 <input
70 id="password"
71 type="password"
72 value={password}
73 onChange={(e) => setPassword(e.target.value)}
74 required
75 className="w-full px-3 py-2 border border-gray-300 rounded"
76 />
77 </div>
78
79 <button
80 type="submit"
81 disabled={loading}
82 className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 disabled:opacity-50"
83 >
84 {loading ? 'Logowanie...' : 'Zaloguj się'}
85 </button>
86 </form>
87 </div>
88 );
89}Oraz stronę do rejestracji:
1// app/register/page.tsx
2'use client';
3
4import { useState } from 'react';
5import { useRouter } from 'next/navigation';
6
7export default function RegisterPage() {
8 const router = useRouter();
9 const [name, setName] = useState('');
10 const [email, setEmail] = useState('');
11 const [password, setPassword] = useState('');
12 const [confirmPassword, setConfirmPassword] = useState('');
13 const [error, setError] = useState('');
14 const [loading, setLoading] = useState(false);
15
16 async function handleSubmit(e: React.FormEvent) {
17 e.preventDefault();
18 setLoading(true);
19 setError('');
20
21 // Walidacja formularza
22 if (password !== confirmPassword) {
23 setError('Hasła nie są identyczne');
24 setLoading(false);
25 return;
26 }
27
28 try {
29 const response = await fetch('/api/register', {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/json' },
32 body: JSON.stringify({ name, email, password })
33 });
34
35 const data = await response.json();
36
37 if (!response.ok) {
38 throw new Error(data.message || 'Wystąpił błąd podczas rejestracji');
39 }
40
41 // Rejestracja pomyślna, przekieruj do strony logowania
42 router.push('/login?registered=true');
43 } catch (error) {
44 setError(error instanceof Error ? error.message : 'Wystąpił błąd');
45 } finally {
46 setLoading(false);
47 }
48 }
49
50 return (
51 <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
52 <h1 className="text-2xl font-bold mb-6">Rejestracja</h1>
53
54 {error && (
55 <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
56 {error}
57 </div>
58 )}
59
60 <form onSubmit={handleSubmit} className="space-y-4">
61 <div>
62 <label htmlFor="name" className="block text-sm font-medium mb-1">
63 Imię i nazwisko
64 </label>
65 <input
66 id="name"
67 type="text"
68 value={name}
69 onChange={(e) => setName(e.target.value)}
70 required
71 className="w-full px-3 py-2 border border-gray-300 rounded"
72 />
73 </div>
74
75 <div>
76 <label htmlFor="email" className="block text-sm font-medium mb-1">
77 Email
78 </label>
79 <input
80 id="email"
81 type="email"
82 value={email}
83 onChange={(e) => setEmail(e.target.value)}
84 required
85 className="w-full px-3 py-2 border border-gray-300 rounded"
86 />
87 </div>
88
89 <div>
90 <label htmlFor="password" className="block text-sm font-medium mb-1">
91 Hasło
92 </label>
93 <input
94 id="password"
95 type="password"
96 value={password}
97 onChange={(e) => setPassword(e.target.value)}
98 required
99 minLength={8}
100 className="w-full px-3 py-2 border border-gray-300 rounded"
101 />
102 </div>
103
104 <div>
105 <label htmlFor="confirmPassword" className="block text-sm font-medium mb-1">
106 Potwierdź hasło
107 </label>
108 <input
109 id="confirmPassword"
110 type="password"
111 value={confirmPassword}
112 onChange={(e) => setConfirmPassword(e.target.value)}
113 required
114 className="w-full px-3 py-2 border border-gray-300 rounded"
115 />
116 </div>
117
118 <button
119 type="submit"
120 disabled={loading}
121 className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 disabled:opacity-50"
122 >
123 {loading ? 'Rejestracja...' : 'Zarejestruj się'}
124 </button>
125 </form>
126 </div>
127 );
128}3. Implementacja API endpointu do rejestracji
Aby umożliwić rejestrację, potrzebujemy endpointu API:
1// app/api/register/route.ts
2import { NextResponse } from 'next/server';
3import { hash } from 'bcrypt';
4import { prisma } from '@/lib/prisma';
5import { z } from 'zod';
6
7// Schemat walidacji
8const UserSchema = z.object({
9 name: z.string().min(2, 'Imię musi mieć co najmniej 2 znaki'),
10 email: z.string().email('Nieprawidłowy adres email'),
11 password: z.string().min(8, 'Hasło musi mieć co najmniej 8 znaków')
12});
13
14export async function POST(request: Request) {
15 try {
16 const body = await request.json();
17
18 // Walidacja danych
19 const result = UserSchema.safeParse(body);
20
21 if (!result.success) {
22 return NextResponse.json(
23 { message: result.error.errors[0].message },
24 { status: 400 }
25 );
26 }
27
28 const { name, email, password } = result.data;
29
30 // Sprawdź, czy użytkownik już istnieje
31 const existingUser = await prisma.user.findUnique({
32 where: { email }
33 });
34
35 if (existingUser) {
36 return NextResponse.json(
37 { message: 'Użytkownik o podanym adresie email już istnieje' },
38 { status: 409 }
39 );
40 }
41
42 // Haszowanie hasła
43 const hashedPassword = await hash(password, 10);
44
45 // Utworzenie nowego użytkownika
46 const user = await prisma.user.create({
47 data: {
48 name,
49 email,
50 password: hashedPassword
51 }
52 });
53
54 // Zwróć odpowiedź bez hasła
55 const { password: _, ...userWithoutPassword } = user;
56
57 return NextResponse.json(
58 { message: 'Rejestracja zakończona pomyślnie', user: userWithoutPassword },
59 { status: 201 }
60 );
61 } catch (error) {
62 console.error('Registration error:', error);
63 return NextResponse.json(
64 { message: 'Wystąpił błąd serwera' },
65 { status: 500 }
66 );
67 }
68}4. Zabezpieczenie stron chronionych
Aby zabezpieczyć strony, które powinny być dostępne tylko dla zalogowanych użytkowników, możemy użyć 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
9 // Sprawdź, czy użytkownik jest zalogowany
10 if (!token) {
11 const url = new URL('/login', request.url);
12 url.searchParams.set('callbackUrl', request.nextUrl.pathname);
13 return NextResponse.redirect(url);
14 }
15
16 return NextResponse.next();
17}
18
19// Określ ścieżki, które mają być chronione
20export const config = {
21 matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*']
22};5. Wyświetlanie danych użytkownika i zarządzanie sesją
Aby wykorzystać informacje o zalogowanym użytkowniku w komponencie, możemy użyć hooka useSession:
1// components/ProfileSection.tsx
2'use client';
3
4import { useSession } from 'next-auth/react';
5import { signOut } from 'next-auth/react';
6
7export default function ProfileSection() {
8 const { data: session, status } = useSession();
9
10 if (status === 'loading') {
11 return <div>Ładowanie...</div>;
12 }
13
14 if (status === 'unauthenticated' || !session) {
15 return <div>Nie jesteś zalogowany</div>;
16 }
17
18 return (
19 <div className="p-4 bg-white rounded shadow">
20 <h2 className="text-xl font-bold mb-4">Twój profil</h2>
21 <p>Witaj, {session.user?.name || 'użytkowniku'}!</p>
22 <p>Email: {session.user?.email}</p>
23
24 <button
25 onClick={() => signOut({ callbackUrl: '/' })}
26 className="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
27 >
28 Wyloguj się
29 </button>
30 </div>
31 );
32}6. Inicjalizacja SessionProvider dla całej aplikacji
Aby uwierzytelnianie działało poprawnie, musimy dodać SessionProvider na poziomie layoutu:
1// app/providers.tsx
2'use client';
3
4import { SessionProvider } from 'next-auth/react';
5import { ReactNode } from 'react';
6
7export function Providers({ children }: { children: ReactNode }) {
8 return <SessionProvider>{children}</SessionProvider>;
9}
10
11// app/layout.tsx
12import { Providers } from './providers';
13
14export default function RootLayout({
15 children,
16}: {
17 children: React.ReactNode;
18}) {
19 return (
20 <html lang="en">
21 <body>
22 <Providers>{children}</Providers>
23 </body>
24 </html>
25 );
26}Mechanizmy zabezpieczeń
Implementując uwierzytelnianie, należy pamiętać o kilku kluczowych mechanizmach zabezpieczeń:
1. Bezpieczne przechowywanie haseł
Nigdy nie przechowuj haseł w formie czystego tekstu! Zawsze używaj funkcji haszujących, takich jak bcrypt:
1import { hash, compare } from 'bcrypt';
2
3// Haszowanie hasła podczas rejestracji
4const hashedPassword = await hash(password, 10);
5
6// Weryfikacja hasła podczas logowania
7const isValid = await compare(password, hashedPassword);2. Ochrona przed atakami CSRF
Cross-Site Request Forgery (CSRF) to rodzaj ataku, który wykorzystuje uwierzytelnioną sesję użytkownika do wykonania nieautoryzowanych działań. Next.js i NextAuth.js domyślnie oferują pewne zabezpieczenia, ale warto zapewnić dodatkową ochronę:
1// Dodanie tokenu CSRF do formularza
2export default function LoginForm() {
3 const csrfToken = await getCsrfToken();
4
5 return (
6 <form method="post" action="/api/auth/callback/credentials">
7 <input name="csrfToken" type="hidden" defaultValue={csrfToken} />
8 {/* Reszta formularza */}
9 </form>
10 );
11}3. Implementacja HTTPS
Używanie szyfrowanego połączenia HTTPS jest niezbędne do ochrony danych uwierzytelniających i tokenów sesji. W środowisku produkcyjnym upewnij się, że Twoja aplikacja jest dostępna tylko przez HTTPS.
4. Wygasanie tokenów JWT
Jeśli używasz tokenów JWT, ustaw im odpowiedni czas wygaśnięcia, aby ograniczyć ryzyko w przypadku kradzieży tokenu:
1// Konfiguracja wygasania tokenów JWT w NextAuth.js
2export const authOptions = {
3 // ... inne opcje
4 session: {
5 strategy: 'jwt',
6 maxAge: 30 * 24 * 60 * 60, // 30 dni
7 },
8 jwt: {
9 secret: process.env.JWT_SECRET,
10 maxAge: 30 * 24 * 60 * 60, // 30 dni
11 }
12};Typowe wyzwania i rozwiązania
Problem: Wymuszanie uwierzytelnienia na stronach statycznych (SSG)
Wyzwaniem może być zabezpieczenie stron, które są generowane statycznie podczas budowania:
Rozwiązanie: Dodaj dodatkową warstwę weryfikacji po stronie klienta:
1'use client';
2
3import { useSession } from 'next-auth/react';
4import { useRouter } from 'next/navigation';
5import { useEffect } from 'react';
6
7export default function ProtectedPage() {
8 const { status } = useSession();
9 const router = useRouter();
10
11 useEffect(() => {
12 if (status === 'unauthenticated') {
13 router.push('/login');
14 }
15 }, [status, router]);
16
17 if (status === 'loading') {
18 return <div>Ładowanie...</div>;
19 }
20
21 if (status === 'unauthenticated') {
22 return null; // Zamiast renderować zawartość, przekieruj do strony logowania
23 }
24
25 // Właściwa zawartość strony chronionej
26 return (
27 <div>
28 <h1>Strona chroniona</h1>
29 {/* Reszta zawartości */}
30 </div>
31 );
32}Problem: Różne role użytkowników
Czasami potrzebujemy różnych poziomów dostępu dla użytkowników:
Rozwiązanie: Rozszerz token/sesję o informacje o rolach i zaimplementuj sprawdzanie ról:
1// Rozszerzenie typów NextAuth
2declare module 'next-auth' {
3 interface Session {
4 user: {
5 id: string;
6 name?: string | null;
7 email?: string | null;
8 image?: string | null;
9 role: string;
10 };
11 }
12
13 interface User {
14 id: string;
15 name?: string | null;
16 email?: string | null;
17 image?: string | null;
18 role: string;
19 }
20}
21
22// Używanie informacji o rolach
23function canAccessAdmin(session: Session | null) {
24 return session?.user?.role === 'admin';
25}
26
27// W komponencie
28const { data: session } = useSession();
29const isAdmin = session?.user?.role === 'admin';
30
31if (!isAdmin) {
32 return <div>Brak dostępu. Ta sekcja jest tylko dla administratorów.</div>;
33}Podsumowanie
Uwierzytelnianie w Next.js może być implementowane na wiele sposobów, ale najczęściej używane podejścia obejmują:
- NextAuth.js / Auth.js - kompleksowe rozwiązanie oferujące integrację z wieloma dostawcami uwierzytelniania
- Własne rozwiązanie JWT - dla niestandardowych wymagań
- Zewnętrzne serwisy - np. Firebase Auth, Auth0, Clerk
Wybór odpowiedniego rozwiązania zależy od specyficznych wymagań Twojej aplikacji, takich jak:
- Potrzeba integracji z zewnętrznymi dostawcami (Google, Facebook, itp.)
- Wymagania dotyczące własności danych
- Złożoność modelu uprawnień i ról
- Skalowalność
W kolejnych modułach przyjrzymy się bardziej zaawansowanym aspektom uwierzytelniania, w tym szczegółowej implementacji NextAuth.js, zarządzaniu sesjami i tokenami, ochronie ścieżek za pomocą middleware, oraz zagadnieniom bezpieczeństwa.
Kod do tej lekcji: App.tsx
1// Demo: Podstawy uwierzytelniania w aplikacjach Next.js
2// Przegląd strategii auth: JWT, Session, OAuth
3import React, { useState } from 'react';
4
5// Uwierzytelnianie w Next.js:
6// 1. JWT (JSON Web Token) - token w Authorization header
7// 2. Session-based - cookie z session ID, dane na serwerze
8// 3. OAuth - delegacja do zewnętrznego providera (Google, GitHub)
9// 4. Auth.js (NextAuth) - najpopularniejsza biblioteka
10
11interface AuthStrategy {
12 name: string;
13 description: string;
14 flow: string[];
15 pros: string[];
16 cons: string[];
17 color: string;
18 nextjsCode: string;
19}
20
21const strategies: AuthStrategy[] = [
22 {
23 name: 'JWT (Stateless)',
24 description: 'Token zawierający zakodowane dane użytkownika. Serwer nie przechowuje stanu sesji.',
25 flow: ['User loguje się (email + hasło)', 'Serwer generuje JWT token', 'Token wysyłany w każdym request (Header)', 'Serwer weryfikuje podpis tokena'],
26 pros: ['Skalowalny - brak stanu na serwerze', 'Działa z mikroserwisami', 'Łatwy do implementacji'],
27 cons: ['Nie można łatwo unieważnić', 'Token może być duży', 'Ryzyko kradzieży tokena'],
28 color: '#ff9800',
29 nextjsCode: "// middleware.ts\nimport { jwtVerify } from 'jose';\n\nexport async function middleware(req) {\n const token = req.headers.get('authorization');\n const { payload } = await jwtVerify(token, secret);\n // payload.userId, payload.role...\n}",
30 },
31 {
32 name: 'Session (Stateful)',
33 description: 'Serwer przechowuje dane sesji. Klient otrzymuje cookie z ID sesji.',
34 flow: ['User loguje się', 'Serwer tworzy sesję w bazie/Redis', 'Cookie z session ID wysyłane do klienta', 'Przy każdym request serwer sprawdza sesję'],
35 pros: ['Łatwe unieważnianie (usuń sesję)', 'Mniej danych w cookie', 'Bezpieczniejsze (HttpOnly cookies)'],
36 cons: ['Wymaga storage (Redis/DB)', 'Trudniejsze skalowanie', 'Sticky sessions'],
37 color: '#64ffda',
38 nextjsCode: "// auth.ts (Auth.js v5)\nimport NextAuth from 'next-auth';\nimport Credentials from 'next-auth/providers/credentials';\n\nexport const { auth, signIn, signOut } = NextAuth({\n providers: [Credentials({...})],\n session: { strategy: 'database' },\n});",
39 },
40 {
41 name: 'OAuth 2.0',
42 description: 'Delegacja uwierzytelniania do zewnętrznego providera (Google, GitHub, etc.).',
43 flow: ['User klika "Sign in with Google"', 'Redirect do Google login', 'Google potwierdza tożsamość', 'Callback z kodem autoryzacji', 'Serwer wymienia kod na token'],
44 pros: ['Brak zarządzania hasłami', 'Zaufany provider', 'Szybkie logowanie dla usera'],
45 cons: ['Zależność od external serwisu', 'Złożony flow (redirects)', 'Wymaga rejestracji aplikacji'],
46 color: '#7c4dff',
47 nextjsCode: "// auth.ts\nimport NextAuth from 'next-auth';\nimport Google from 'next-auth/providers/google';\nimport GitHub from 'next-auth/providers/github';\n\nexport const { handlers, auth } = NextAuth({\n providers: [Google, GitHub],\n});\n\n// app/api/auth/[...nextauth]/route.ts\nexport const { GET, POST } = handlers;",
48 },
49];
50
51function FlowDiagram({ steps, color }: { steps: string[]; color: string }) {
52 return (
53 <div style={{ display: 'grid', gap: '8px', marginTop: '12px' }}>
54 {steps.map((step, i) => (
55 <div key={i} style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
56 <div style={{
57 width: '28px', height: '28px', borderRadius: '50%',
58 background: `${color}20`, color, flexShrink: 0,
59 display: 'flex', alignItems: 'center', justifyContent: 'center',
60 fontSize: '0.8rem', fontWeight: 'bold',
61 }}>{i + 1}</div>
62 <span style={{ color: '#b0bec5', fontSize: '0.85rem' }}>{step}</span>
63 </div>
64 ))}
65 </div>
66 );
67}
68
69export default function AuthIntroduction() {
70 const [activeStrategy, setActiveStrategy] = useState(0);
71 const s = strategies[activeStrategy];
72
73 return (
74 <div style={{
75 background: '#0f0f23', minHeight: '100vh', padding: '24px',
76 color: '#fff', fontFamily: 'system-ui, sans-serif',
77 }}>
78 <h1 style={{ color: '#64ffda', marginBottom: '4px' }}>Authentication in Next.js</h1>
79 <p style={{ color: '#b0bec5', marginBottom: '20px' }}>
80 JWT vs Session vs OAuth - porównanie strategii
81 </p>
82
83 <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '10px', marginBottom: '24px' }}>
84 {strategies.map((st, i) => (
85 <button key={st.name} onClick={() => setActiveStrategy(i)} style={{
86 background: i === activeStrategy ? `${st.color}15` : 'rgba(255,255,255,0.02)',
87 border: `2px solid ${i === activeStrategy ? st.color : 'rgba(255,255,255,0.08)'}`,
88 color: i === activeStrategy ? st.color : '#78909c',
89 padding: '14px', borderRadius: '10px', cursor: 'pointer',
90 textAlign: 'center', fontWeight: 500,
91 }}>
92 {st.name}
93 </button>
94 ))}
95 </div>
96
97 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
98 <div>
99 <div style={{
100 background: `${s.color}08`, border: `1px solid ${s.color}30`,
101 borderRadius: '12px', padding: '20px',
102 }}>
103 <h3 style={{ color: s.color, marginBottom: '8px' }}>{s.name}</h3>
104 <p style={{ color: '#b0bec5', fontSize: '0.9rem', lineHeight: 1.5, marginBottom: '0' }}>
105 {s.description}
106 </p>
107 <FlowDiagram steps={s.flow} color={s.color} />
108 </div>
109
110 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px', marginTop: '16px' }}>
111 <div style={{ background: 'rgba(76,175,80,0.08)', borderRadius: '10px', padding: '14px' }}>
112 <h4 style={{ color: '#4caf50', marginBottom: '8px', fontSize: '0.85rem' }}>Zalety</h4>
113 {s.pros.map(p => (
114 <p key={p} style={{ color: '#b0bec5', fontSize: '0.8rem', margin: '4px 0' }}>+ {p}</p>
115 ))}
116 </div>
117 <div style={{ background: 'rgba(244,67,54,0.08)', borderRadius: '10px', padding: '14px' }}>
118 <h4 style={{ color: '#f44336', marginBottom: '8px', fontSize: '0.85rem' }}>Wady</h4>
119 {s.cons.map(c => (
120 <p key={c} style={{ color: '#b0bec5', fontSize: '0.8rem', margin: '4px 0' }}>- {c}</p>
121 ))}
122 </div>
123 </div>
124 </div>
125
126 <div style={{
127 background: 'rgba(0,0,0,0.3)', borderRadius: '12px', padding: '20px',
128 }}>
129 <h4 style={{ color: s.color, marginBottom: '12px' }}>Implementacja w Next.js</h4>
130 <pre style={{
131 margin: 0, color: '#b0bec5', fontSize: '0.8rem',
132 lineHeight: 1.6, whiteSpace: 'pre-wrap',
133 }}>
134 {s.nextjsCode}
135 </pre>
136 </div>
137 </div>
138 </div>
139 );
140}Widzisz błąd w tej lekcji?