NextAuth.js (obecnie znany również jako Auth.js) to kompleksowe rozwiązanie do uwierzytelniania w aplikacjach Next.js. Zapewnia gotową infrastrukturę do zarządzania sesjami, tokenami, logowaniem, rejestracją i integracją z zewnętrznymi dostawcami uwierzytelniania. W tym module zajmiemy się szczegółową implementacją tego narzędzia w aplikacjach Next.js.
NextAuth.js oferuje wiele korzyści:
Zacznijmy od instalacji NextAuth.js:
1npm install next-auth@latest
2# lub
3yarn add next-auth@latest
4# lub
5pnpm add next-auth@latestAby skonfigurować NextAuth.js w architekturze App Router, musimy utworzyć plik do obsługi Route API dla uwierzytelniania:
1// app/api/auth/[...nextauth]/route.ts
2import NextAuth from "next-auth";
3import GithubProvider from "next-auth/providers/github";
4
5export const authOptions = {
6 providers: [
7 GithubProvider({
8 clientId: process.env.GITHUB_ID!,
9 clientSecret: process.env.GITHUB_SECRET!
10 })
11 ],
12 // Dodatkowe opcje konfiguracyjne...
13};
14
15const handler = NextAuth(authOptions);
16export { handler as GET, handler as POST };Ten plik tworzy dynamiczną trasę API, która obsługuje wszystkie niezbędne endpointy do uwierzytelniania.
Aby umożliwić korzystanie z hooka
useSession w komponentach po stronie klienta, musimy dodać SessionProvider do naszej aplikacji. Najlepiej zrobić to w pliku Provider lub bezpośrednio w głównym layoucie: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}NextAuth.js wymaga kilku zmiennych środowiskowych. Utwórz lub zaktualizuj plik
.env.local w głównym katalogu projektu:1# Podstawowa konfiguracja dla NextAuth.js
2NEXTAUTH_URL=http://localhost:3000
3NEXTAUTH_SECRET=twój_tajny_klucz_do_szyfrowania_jwt
4
5# Dane uwierzytelniania GitHub (przykład)
6GITHUB_ID=twoje_github_client_id
7GITHUB_SECRET=twoje_github_client_secretW środowisku produkcyjnym:
NEXTAUTH_URL powinien być pełnym adresem URL Twojej aplikacjiNEXTAUTH_SECRET powinien być długim, losowym ciągiem znaków. Możesz go wygenerować za pomocą komendy openssl rand -base64 32.NextAuth.js obsługuje wiele dostawców uwierzytelniania. Przyjrzyjmy się, jak skonfigurować kilka popularnych:
1import GithubProvider from "next-auth/providers/github";
2
3export const authOptions = {
4 providers: [
5 GithubProvider({
6 clientId: process.env.GITHUB_ID!,
7 clientSecret: process.env.GITHUB_SECRET!,
8 // Opcjonalne parametry
9 scope: "read:user user:email", // Określa zakres dostępu
10 profile(profile) {
11 // Dostosowanie profilu użytkownika
12 return {
13 id: profile.id.toString(),
14 name: profile.name || profile.login,
15 email: profile.email,
16 image: profile.avatar_url,
17 // Dodatkowe pola
18 username: profile.login,
19 };
20 },
21 }),
22 ],
23 // Pozostałe opcje konfiguracyjne...
24};1import GoogleProvider from "next-auth/providers/google";
2
3export const authOptions = {
4 providers: [
5 GoogleProvider({
6 clientId: process.env.GOOGLE_CLIENT_ID!,
7 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
8 // Opcjonalne parametry
9 authorization: {
10 params: Promise<{
11 prompt: "consent",
12 access_type: "offline",
13 response_type: "code"
14 }>
15 }
16 }),
17 ],
18 // Pozostałe opcje konfiguracyjne...
19};1import CredentialsProvider from "next-auth/providers/credentials";
2import { compare } from "bcrypt";
3import { prisma } from "@/lib/prisma"; // Zakładamy, że masz skonfigurowaną instancję Prisma
4
5export const authOptions = {
6 providers: [
7 CredentialsProvider({
8 // Nazwa wyświetlana na stronie logowania
9 name: "Credentials",
10 // Konfiguracja pól formularza logowania
11 credentials: {
12 email: { label: "Email", type: "email" },
13 password: { label: "Hasło", type: "password" }
14 },
15 async authorize(credentials) {
16 // Sprawdź, czy credentials istnieją
17 if (!credentials?.email || !credentials?.password) {
18 return null;
19 }
20
21 try {
22 // Znajdź użytkownika w bazie danych
23 const user = await prisma.user.findUnique({
24 where: { email: credentials.email },
25 });
26
27 if (!user || !user.password) {
28 return null;
29 }
30
31 // Sprawdź, czy hasło jest poprawne
32 const isPasswordValid = await compare(
33 credentials.password,
34 user.password
35 );
36
37 if (!isPasswordValid) {
38 return null;
39 }
40
41 // Zwróć obiekt użytkownika (bez hasła)
42 return {
43 id: user.id,
44 name: user.name,
45 email: user.email,
46 role: user.role,
47 // Inne pola, które chcesz uwzględnić
48 };
49 } catch (error) {
50 console.error("Authentication error:", error);
51 return null;
52 }
53 },
54 }),
55 ],
56 // Pozostałe opcje konfiguracyjne...
57};Możesz połączyć wielu dostawców uwierzytelniania:
1export const authOptions = {
2 providers: [
3 GithubProvider({
4 clientId: process.env.GITHUB_ID!,
5 clientSecret: process.env.GITHUB_SECRET!,
6 }),
7 GoogleProvider({
8 clientId: process.env.GOOGLE_CLIENT_ID!,
9 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
10 }),
11 CredentialsProvider({
12 // Konfiguracja...
13 async authorize(credentials) {
14 // Logika uwierzytelniania...
15 },
16 }),
17 // Możesz dodać więcej dostawców...
18 ],
19 // Pozostałe opcje konfiguracyjne...
20};NextAuth.js oferuje różne strategie sesji:
1export const authOptions = {
2 // Konfiguracja dostawców...
3
4 session: {
5 // Wybierz strategię: 'jwt' lub 'database'
6 strategy: 'jwt',
7
8 // Maksymalny czas życia sesji (w sekundach)
9 maxAge: 30 * 24 * 60 * 60, // 30 dni
10
11 // Aktualizacja sesji przy każdym żądaniu, wydłużając jej ważność
12 updateAge: 24 * 60 * 60, // 24 godziny
13 },
14
15 // Konfiguracja JWT (tylko dla strategii 'jwt')
16 jwt: {
17 // Tajny klucz używany do podpisywania tokenów JWT
18 // Domyślnie używa NEXTAUTH_SECRET
19 secret: process.env.JWT_SECRET,
20
21 // Możesz dostosować enkoder/dekoder JWT
22 // encode: async ({ secret, token, maxAge }) => {},
23 // decode: async ({ secret, token }) => {},
24 },
25};Domyślnie NextAuth.js dostarcza proste strony do logowania, ale możesz je zastąpić własnymi:
1export const authOptions = {
2 // Konfiguracja dostawców...
3
4 pages: {
5 signIn: '/auth/login',
6 signOut: '/auth/logout',
7 error: '/auth/error', // Błąd uwierzytelniania (np. niewłaściwe dane)
8 verifyRequest: '/auth/verify-request', // Używane dla Magic Links
9 newUser: '/auth/new-user' // Strona po pierwszej rejestracji
10 },
11};Następnie musisz utworzyć te strony w swojej aplikacji. Oto przykład strony logowania:
1// app/auth/login/page.tsx
2'use client';
3
4import { signIn, useSession } from 'next-auth/react';
5import { useRouter, useSearchParams } from 'next/navigation';
6import { useState, useEffect } from 'react';
7
8export default function LoginPage() {
9 const router = useRouter();
10 const searchParams = useSearchParams();
11 const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
12 const { status } = useSession();
13
14 const [email, setEmail] = useState('');
15 const [password, setPassword] = useState('');
16 const [error, setError] = useState('');
17 const [isLoading, setIsLoading] = useState(false);
18
19 // Przekieruj zalogowanego użytkownika
20 useEffect(() => {
21 if (status === 'authenticated') {
22 router.push(callbackUrl);
23 }
24 }, [status, router, callbackUrl]);
25
26 // Obsługa logowania z GitHub
27 const handleGitHubSignIn = () => {
28 setIsLoading(true);
29 signIn('github', { callbackUrl });
30 };
31
32 // Obsługa logowania z Google
33 const handleGoogleSignIn = () => {
34 setIsLoading(true);
35 signIn('google', { callbackUrl });
36 };
37
38 // Obsługa logowania z danymi uwierzytelniającymi
39 const handleSubmit = async (e: React.FormEvent) => {
40 e.preventDefault();
41 setIsLoading(true);
42 setError('');
43
44 try {
45 const result = await signIn('credentials', {
46 redirect: false,
47 email,
48 password,
49 });
50
51 if (result?.error) {
52 setError('Nieprawidłowy email lub hasło');
53 setIsLoading(false);
54 } else {
55 router.push(callbackUrl);
56 }
57 } catch (error) {
58 setError('Wystąpił błąd podczas logowania');
59 setIsLoading(false);
60 }
61 };
62
63 if (status === 'loading') {
64 return <div>Ładowanie...</div>;
65 }
66
67 return (
68 <div className="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
69 <h1 className="text-2xl font-bold mb-6 text-center">Zaloguj się</h1>
70
71 {error && (
72 <div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
73 {error}
74 </div>
75 )}
76
77 <div className="space-y-4 mb-6">
78 <button
79 type="button"
80 onClick={handleGitHubSignIn}
81 disabled={isLoading}
82 className="w-full flex items-center justify-center gap-2 p-2 border border-gray-300 rounded-md hover:bg-gray-50"
83 >
84 <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
85 {/* Logo GitHub */}
86 <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
87 </svg>
88 Kontynuuj z GitHub
89 </button>
90
91 <button
92 type="button"
93 onClick={handleGoogleSignIn}
94 disabled={isLoading}
95 className="w-full flex items-center justify-center gap-2 p-2 border border-gray-300 rounded-md hover:bg-gray-50"
96 >
97 <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
98 {/* Logo Google */}
99 <path d="M12 24C5.385 24 0 18.615 0 12S5.385 0 12 0s12 5.385 12 12-5.385 12-12 12zm0-22.5C6.21 1.5 1.5 6.21 1.5 12S6.21 22.5 12 22.5 22.5 17.79 22.5 12 17.79 1.5 12 1.5zm-5.25 9v3h3v3h3v-3h3v-3h-3v-3h-3v3h-3z" />
100 </svg>
101 Kontynuuj z Google
102 </button>
103 </div>
104
105 <div className="relative my-6">
106 <div className="absolute inset-0 flex items-center">
107 <div className="w-full border-t border-gray-300"></div>
108 </div>
109 <div className="relative flex justify-center text-sm">
110 <span className="px-2 bg-white text-gray-500">
111 lub zaloguj się za pomocą emaila
112 </span>
113 </div>
114 </div>
115
116 <form onSubmit={handleSubmit} className="space-y-4">
117 <div>
118 <label htmlFor="email" className="block text-sm font-medium mb-1">
119 Email
120 </label>
121 <input
122 id="email"
123 type="email"
124 value={email}
125 onChange={(e) => setEmail(e.target.value)}
126 required
127 className="w-full px-3 py-2 border border-gray-300 rounded-md"
128 />
129 </div>
130
131 <div>
132 <label htmlFor="password" className="block text-sm font-medium mb-1">
133 Hasło
134 </label>
135 <input
136 id="password"
137 type="password"
138 value={password}
139 onChange={(e) => setPassword(e.target.value)}
140 required
141 className="w-full px-3 py-2 border border-gray-300 rounded-md"
142 />
143 </div>
144
145 <button
146 type="submit"
147 disabled={isLoading}
148 className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 disabled:opacity-50"
149 >
150 {isLoading ? 'Logowanie...' : 'Zaloguj się'}
151 </button>
152 </form>
153 </div>
154 );
155}NextAuth.js umożliwia reagowanie na różne zdarzenia w cyklu życia uwierzytelniania poprzez callbacki:
1export const authOptions = {
2 // Konfiguracja dostawców...
3
4 callbacks: {
5 // Wywoływany przy tworzeniu sesji
6 async session({ session, token }) {
7 // Dodaj dane z tokenu do sesji
8 if (token.sub && session.user) {
9 session.user.id = token.sub;
10 }
11
12 // Dodaj niestandardowe pola, np. rolę użytkownika
13 if (token.role && session.user) {
14 session.user.role = token.role as string;
15 }
16
17 return session;
18 },
19
20 // Wywoływany przy tworzeniu JWT
21 async jwt({ token, user, account, profile }) {
22 // Dodaj dane użytkownika przy pierwszym zalogowaniu
23 if (user) {
24 token.role = user.role;
25 // Inne niestandardowe pola...
26 }
27
28 return token;
29 },
30
31 // Wywoływany po zalogowaniu
32 async signIn({ user, account, profile, email, credentials }) {
33 // Sprawdź, czy użytkownik ma dostęp do aplikacji
34 return true; // Zwróć false, aby odmówić dostępu
35 },
36
37 // Wywoływany podczas przekierowania
38 async redirect({ url, baseUrl }) {
39 // Zasady przekierowań
40 if (url.startsWith(baseUrl)) {
41 return url;
42 }
43 // Domyślnie przekieruj na stronę bazową
44 return baseUrl;
45 },
46 },
47
48 // Zdarzenia
49 events: {
50 async signIn(message) {
51 // Wywołane po zalogowaniu
52 console.log('User signed in:', message);
53 },
54 async signOut(message) {
55 // Wywołane po wylogowaniu
56 console.log('User signed out:', message);
57 },
58 async createUser(message) {
59 // Wywołane po utworzeniu użytkownika
60 console.log('User created:', message);
61 },
62 async linkAccount(message) {
63 // Wywołane po powiązaniu konta
64 console.log('Account linked:', message);
65 },
66 async session(message) {
67 // Wywołane po utworzeniu sesji
68 console.log('Session created/updated:', message);
69 },
70 },
71};Jeśli używasz TypeScript, możesz rozszerzyć typy NextAuth.js, aby uwzględnić niestandardowe pola:
1// W pliku z rozszerzeniami typów (np. types/next-auth.d.ts)
2import 'next-auth';
3import 'next-auth/jwt';
4
5declare module 'next-auth' {
6 interface Session {
7 user: {
8 id: string;
9 name?: string | null;
10 email?: string | null;
11 image?: string | null;
12 role?: string;
13 // Dodaj inne niestandardowe pola...
14 };
15 }
16
17 interface User {
18 id: string;
19 name?: string | null;
20 email?: string | null;
21 image?: string | null;
22 role?: string;
23 // Dodaj inne niestandardowe pola...
24 }
25}
26
27declare module 'next-auth/jwt' {
28 interface JWT {
29 role?: string;
30 // Dodaj inne niestandardowe pola...
31 }
32}Aby używać strategii sesji opartej na bazie danych lub przechowywać konta użytkowników, NextAuth.js oferuje adaptery dla różnych baz danych:
1import { PrismaAdapter } from "@auth/prisma-adapter";
2import { PrismaClient } from "@prisma/client";
3
4const prisma = new PrismaClient();
5
6export const authOptions = {
7 adapter: PrismaAdapter(prisma),
8 providers: [
9 // Konfiguracja dostawców...
10 ],
11 // Pozostałe opcje...
12};Schemat Prisma powinien zawierać odpowiednie modele:
1// prisma/schema.prisma
2model Account {
3 id String @id @default(cuid())
4 userId String
5 type String
6 provider String
7 providerAccountId String
8 refresh_token String? @db.Text
9 access_token String? @db.Text
10 expires_at Int?
11 token_type String?
12 scope String?
13 id_token String? @db.Text
14 session_state String?
15
16 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
17
18 @@unique([provider, providerAccountId])
19}
20
21model Session {
22 id String @id @default(cuid())
23 sessionToken String @unique
24 userId String
25 expires DateTime
26 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
27}
28
29model User {
30 id String @id @default(cuid())
31 name String?
32 email String? @unique
33 emailVerified DateTime?
34 image String?
35 password String?
36 role String? @default("user")
37 accounts Account[]
38 sessions Session[]
39}
40
41model VerificationToken {
42 identifier String
43 token String @unique
44 expires DateTime
45
46 @@unique([identifier, token])
47}NextAuth.js oferuje adaptery dla wielu baz danych:
Po skonfigurowaniu NextAuth.js, możesz używać hooka
useSession w komponentach klienckich:1'use client';
2
3import { useSession } from 'next-auth/react';
4import { redirect } from 'next/navigation';
5
6export default function Dashboard() {
7 const { data: session, status } = useSession({
8 required: true,
9 onUnauthenticated() {
10 redirect('/auth/login?callbackUrl=/dashboard');
11 },
12 });
13
14 if (status === 'loading') {
15 return <div>Ładowanie...</div>;
16 }
17
18 return (
19 <div>
20 <h1>Dashboard</h1>
21 <p>Witaj, {session.user.name}!</p>
22 <p>Twój email: {session.user.email}</p>
23 {session.user.role === 'admin' && (
24 <div>
25 <h2>Panel administratora</h2>
26 {/* Zawartość tylko dla administratorów */}
27 </div>
28 )}
29 </div>
30 );
31}W komponentach renderowanych na serwerze, możesz użyć
getServerSession:1// app/profile/page.tsx
2import { getServerSession } from 'next-auth/next';
3import { redirect } from 'next/navigation';
4import { authOptions } from '../api/auth/[...nextauth]/route';
5
6export default async function ProfilePage() {
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</h1>
16 <p>Witaj, {session.user.name}!</p>
17 <p>Twój email: {session.user.email}</p>
18 {/* Reszta zawartości profilu */}
19 </div>
20 );
21}NextAuth.js dostarcza funkcje do zarządzania stanem uwierzytelniania:
1'use client';
2
3import { signIn, signOut, useSession } from 'next-auth/react';
4
5export function AuthButton() {
6 const { data: session } = useSession();
7
8 if (session) {
9 return (
10 <button
11 onClick={() => signOut({ callbackUrl: '/' })}
12 className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
13 >
14 Wyloguj się
15 </button>
16 );
17 }
18
19 return (
20 <button
21 onClick={() => signIn()}
22 className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
23 >
24 Zaloguj się
25 </button>
26 );
27}Możemy stworzyć komponent, który renderuje różne elementy interfejsu w zależności od stanu uwierzytelniania:
1'use client';
2
3import { useSession } from 'next-auth/react';
4import Link from 'next/link';
5import { AuthButton } from './AuthButton';
6
7export function Navbar() {
8 const { data: session, status } = useSession();
9 const isLoading = status === 'loading';
10
11 return (
12 <nav className="bg-white shadow-md p-4">
13 <div className="container mx-auto flex justify-between items-center">
14 <Link href="/" className="text-xl font-bold">
15 MojaAplikacja
16 </Link>
17
18 <div className="flex items-center space-x-4">
19 <Link href="/" className="hover:text-blue-500">
20 Strona główna
21 </Link>
22
23 {session ? (
24 <>
25 <Link href="/dashboard" className="hover:text-blue-500">
26 Dashboard
27 </Link>
28 <Link href="/profile" className="hover:text-blue-500">
29 Profil
30 </Link>
31 {session.user.role === 'admin' && (
32 <Link href="/admin" className="hover:text-blue-500">
33 Admin
34 </Link>
35 )}
36 </>
37 ) : (
38 <Link href="/features" className="hover:text-blue-500">
39 Funkcje
40 </Link>
41 )}
42
43 {!isLoading && (
44 <div className="flex items-center space-x-2">
45 {session && (
46 <div className="flex items-center">
47 {session.user.image && (
48 <img
49 src={session.user.image}
50 alt={session.user.name || 'User'}
51 className="w-8 h-8 rounded-full mr-2"
52 />
53 )}
54 <span className="text-sm font-medium">
55 {session.user.name}
56 </span>
57 </div>
58 )}
59 <AuthButton />
60 </div>
61 )}
62 </div>
63 </div>
64 </nav>
65 );
66}NextAuth.js może być również używany do zabezpieczania endpointów API:
1// app/api/protected-data/route.ts
2import { getServerSession } from 'next-auth/next';
3import { NextResponse } from 'next/server';
4import { authOptions } from '../auth/[...nextauth]/route';
5
6export async function GET() {
7 const session = await getServerSession(authOptions);
8
9 if (!session) {
10 return NextResponse.json(
11 { message: 'Nieautoryzowany dostęp' },
12 { status: 401 }
13 );
14 }
15
16 // Sprawdź uprawnienia na podstawie roli (opcjonalnie)
17 if (session.user.role !== 'admin') {
18 return NextResponse.json(
19 { message: 'Brak uprawnień dostępu' },
20 { status: 403 }
21 );
22 }
23
24 // Dane dostępne tylko dla zalogowanych użytkowników (i administratorów, jeśli sprawdzamy role)
25 return NextResponse.json({
26 message: 'Sukces',
27 data: {
28 secretContent: 'Te dane są chronione',
29 timestamp: new Date().toISOString()
30 }
31 });
32}Bezpieczeństwo przede wszystkim
Optymalizacja wydajności
Obsługa błędów
Doświadczenie użytkownika
Testowanie
W niektórych przypadkach możesz chcieć zaimplementować uwierzytelnianie wieloetapowe (np. hasło + kod SMS):
1// Zaimplementuj niestandardowy CredentialsProvider
2CredentialsProvider({
3 name: 'TwoFactor',
4 credentials: {
5 email: { label: "Email", type: "email" },
6 password: { label: "Hasło", type: "password" },
7 code: { label: "Kod weryfikacyjny", type: "text" }
8 },
9 async authorize(credentials) {
10 if (!credentials?.email || !credentials?.password) {
11 return null;
12 }
13
14 // Pierwszy etap: sprawdź hasło
15 const user = await prisma.user.findUnique({
16 where: { email: credentials.email }
17 });
18
19 if (!user || !await compare(credentials.password, user.password)) {
20 return null;
21 }
22
23 // Drugi etap: sprawdź kod weryfikacyjny
24 if (credentials.code) {
25 const isValidCode = await verifyTwoFactorCode(user.id, credentials.code);
26
27 if (!isValidCode) {
28 return null;
29 }
30 } else {
31 // Jeśli kod nie został podany, a jest wymagany
32 if (user.twoFactorEnabled) {
33 // Wysyłanie kodu na email/telefon
34 await sendTwoFactorCode(user);
35
36 // Zwróć specjalny obiekt, aby App Router wiedział, że potrzebujemy kodu
37 throw new Error('TwoFactorRequired');
38 }
39 }
40
41 // Uwierzytelnienie zakończone sukcesem
42 return {
43 id: user.id,
44 email: user.email,
45 name: user.name,
46 role: user.role
47 };
48 }
49})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 role: 'user' // Domyślna rola
52 }
53 });
54
55 // Nie zwracaj hasła w odpowiedzi
56 const { password: _, ...userWithoutPassword } = user;
57
58 return NextResponse.json(
59 { message: 'Rejestracja zakończona pomyślnie', user: userWithoutPassword },
60 { status: 201 }
61 );
62 } catch (error) {
63 console.error('Registration error:', error);
64 return NextResponse.json(
65 { message: 'Wystąpił błąd serwera' },
66 { status: 500 }
67 );
68 }
69}NextAuth.js (Auth.js) to wszechstronne narzędzie do implementacji uwierzytelniania w aplikacjach Next.js. Oferuje:
Dzięki NextAuth.js możesz szybko zaimplementować kompletny system uwierzytelniania w swojej aplikacji Next.js, oszczędzając czas i minimalizując ryzyko błędów związanych z bezpieczeństwem.
W kolejnych modułach omówimy bardziej zaawansowane aspekty uwierzytelniania, w tym strategie uwierzytelniania, zarządzanie sesjami i tokenami, oraz implementację ról i uprawnień.