Kurs Next.js · Moduł 7: Uwierzytelnianie i autoryzacja

Podstawy uwierzytelniania w aplikacjach Next.js

12 min czytania
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ż:

  1. Chroni dane użytkowników - zapewnia, że osobiste informacje są dostępne tylko dla ich właścicieli
  2. Umożliwia personalizację - pozwala na dostosowanie doświadczenia do konkretnego użytkownika
  3. Kontroluje dostęp - określa, do jakich zasobów i funkcji użytkownik ma dostęp
  4. 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:

  1. Renderowanie po stronie serwera (SSR) - umożliwia weryfikację uwierzytelnienia przed wysłaniem strony do klienta
  2. Statyczna generacja stron (SSG) - wymaga dodatkowej weryfikacji klienta dla chronionych treści
  3. 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ą:

  1. NextAuth.js / Auth.js - kompleksowe rozwiązanie oferujące integrację z wieloma dostawcami uwierzytelniania
  2. Własne rozwiązanie JWT - dla niestandardowych wymagań
  3. 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?

Przydatne artykuły