Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Implementacja NextAuth.js / Auth.js

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.

Dlaczego warto użyć NextAuth.js?

NextAuth.js oferuje wiele korzyści:

  1. Gotowe rozwiązanie - nie trzeba implementować uwierzytelniania od podstaw
  2. Wsparcie dla różnych dostawców - oferuje integrację z ponad 50 dostawcami uwierzytelniania
  3. Bezpieczeństwo - implementuje najlepsze praktyki bezpieczeństwa
  4. Zarządzanie sesjami - obsługuje sesje po stronie serwera i klienta
  5. Elastyczność - można go dostosować do konkretnych potrzeb aplikacji
  6. Wsparcie dla App Router - działa z nową architekturą Next.js
  7. Rozszerzona typizacja TypeScript - oferuje pełne wsparcie dla typów

Instalacja i podstawowa konfiguracja

Zacznijmy od instalacji NextAuth.js:

1npm install next-auth@latest
2# lub
3yarn add next-auth@latest
4# lub
5pnpm add next-auth@latest

Konfiguracja podstawowa dla App Router

Aby 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.

Dodanie komponentu SessionProvider

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}

Konfiguracja zmiennych środowiskowych

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_secret

W środowisku produkcyjnym:

  • NEXTAUTH_URL
    powinien być pełnym adresem URL Twojej aplikacji
  • NEXTAUTH_SECRET
    powinien być długim, losowym ciągiem znaków. Możesz go wygenerować za pomocą komendy
    openssl rand -base64 32
    .

Konfiguracja różnych dostawców uwierzytelniania

NextAuth.js obsługuje wiele dostawców uwierzytelniania. Przyjrzyjmy się, jak skonfigurować kilka popularnych:

1. Uwierzytelnianie z GitHub

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};

2. Uwierzytelnianie z Google

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};

3. Uwierzytelnianie z danych uwierzytelniających (email/hasło)

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};

4. Uwierzytelnianie z wielu dostawców

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};

Konfiguracja opcji sesji i JWT

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};

Niestandardowe strony uwierzytelniania

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}

Callbacki i zdarzenia

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};

Rozszerzanie typów dla TypeScript

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}

Adaptery bazy danych

Aby używać strategii sesji opartej na bazie danych lub przechowywać konta użytkowników, NextAuth.js oferuje adaptery dla różnych baz danych:

Konfiguracja z Prisma

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}

Inne dostępne adaptery

NextAuth.js oferuje adaptery dla wielu baz danych:

  • Adaptery SQL: PostgreSQL, MySQL, SQLite, SQL Server
  • Adaptery nierelacyjne: MongoDB, DynamoDB
  • ORM/ODM: Prisma, Mongoose, Typeorm, Sequelize, DrizzleORM
  • Inne: Firebase, Supabase, Kysely, Planetscale, Xata

Używanie uwierzytelniania w aplikacji

1. Pobieranie danych sesji po stronie klienta

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}

2. Pobieranie danych sesji po stronie serwera

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}

3. Zarządzanie stanem uwierzytelniania

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}

Dostosowanie UI do NextAuth.js

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}

Uwierzytelnianie z API Routes

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}

Dobre praktyki przy implementacji NextAuth.js

  1. Bezpieczeństwo przede wszystkim

    • Używaj HTTPS w środowisku produkcyjnym
    • Generuj silne NEXTAUTH_SECRET
    • Nigdy nie przechowuj wrażliwych danych w tokenach JWT
  2. Optymalizacja wydajności

    • Wykorzystuj strategie sesji odpowiednie do twoich potrzeb
    • Rozważ używanie strategii JWT dla większej skalowalności
  3. Obsługa błędów

    • Implementuj niestandardowe strony błędów
    • Rejestruj błędy uwierzytelniania do analizy
  4. Doświadczenie użytkownika

    • Projektuj przyjazne formularze logowania
    • Umożliwiaj płynne przełączanie między dostawcami
  5. Testowanie

    • Testuj różne scenariusze uwierzytelniania
    • Sprawdzaj, jak aplikacja reaguje na wygasłe sesje

Zaawansowane scenariusze

1. Uwierzytelnianie wieloetapowe

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})

2. Implementacja własnej logiki rejestracji

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}

Podsumowanie

NextAuth.js (Auth.js) to wszechstronne narzędzie do implementacji uwierzytelniania w aplikacjach Next.js. Oferuje:

  1. Elastyczność - obsługuje wiele strategii uwierzytelniania
  2. Integrację - łatwo integruje się z różnymi dostawcami
  3. Bezpieczeństwo - implementuje najlepsze praktyki zabezpieczeń
  4. Skalowalność - działa z różnymi bazami danych i architekturami
  5. Rozszerzalność - można go dostosować do specyficznych potrzeb

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ń.

Ir a CodeWorlds