Auth.js v5 (dawniej NextAuth) to biblioteka uwierzytelniania dla Next.js 16, którą konfigurujesz przez pojedynczy plik auth.ts eksportujący funkcję auth(). Działa jednolicie w Server Components, Route Handlers, Server Actions oraz nowym pliku proxy.ts (przemianowanym z middleware.ts). W tym przewodniku pokażę, jak skonfigurować Auth.js v5 od zera, dobrać strategię sesji (JWT kontra bazodanową), chronić trasy w App Routerze i uniknąć najczęstszych pułapek związanych z edge runtime. Sporo z nich sam zaliczyłem, migrując jeden nieduży panel z v4, więc będzie z życia.
Auth.js v5 zastępuje getServerSession pojedynczą funkcją auth() eksportowaną z pliku auth.ts, co działa jednolicie we wszystkich kontekstach renderowania.
Next.js 16 przemianował middleware.ts na proxy.ts, a funkcję middleware na proxy. Vercel dostarcza codemod npx @next/codemod@canary middleware-to-proxy.
Podziel konfigurację na auth.config.ts (edge-safe) i auth.ts (adaptery bazodanowe), by uniknąć błędów z Prismą w środowisku edge.
Sesje JWT są bezstanowe i szybkie, ale nieodwoływalne. Sesje bazodanowe kosztują jedno zapytanie na request, ale pozwalają natychmiast wylogować użytkownika.
Obrona w głębi wymaga trzech warstw: proxy.ts, sprawdzania w Server Components oraz weryfikacji sesji w każdej Server Action przed mutacją.
Czym jest Auth.js v5 i czym różni się od NextAuth v4?
Auth.js v5 to praktycznie pełne przepisanie NextAuth.js. Ta sama nazwa NPM (next-auth), ale całkowicie nowe API. Zmiana marki na Auth.js odzwierciedla rozszerzenie zasięgu poza Next.js: ta sama warstwa runtime obsługuje teraz również SvelteKit, SolidStart i inne frameworki. W praktyce jednak większość zespołów korzystających z App Routera w Next.js 16 spotyka Auth.js właśnie w tej nowej, ujednoliconej formie.
W wersji v4 każde odczytanie sesji wymagało wywołania getServerSession(authOptions) z ręcznym przekazaniem konfiguracji. Męczące. W v5 tworzysz pojedynczy plik auth.ts, wywołujesz w nim NextAuth({...}) i destrukturyzujesz z niego funkcje auth, handlers, signIn oraz signOut. Od tego momentu wywołujesz await auth() bez żadnych argumentów, a biblioteka odczytuje kontekst requestu automatycznie zarówno w Server Components, jak i w Route Handlers, Server Actions czy pliku proxy.ts. To jedno z największych ułatwień ergonomicznych, jakie wprowadziła piątka.
Kolejna kluczowa zmiana: konfiguracja przenosi się z pages/api/auth/[...nextauth].ts do korzenia projektu jako auth.ts, a Route Handler staje się jednolinijkową re-eksportacją. Jeśli migrujesz z v4, warto zapoznać się z oficjalnym przewodnikiem migracji Auth.js, który wymienia każdą przełomową zmianę i podaje konkretne skrypty codemod.
Instalacja Auth.js v5 w Next.js 16 krok po kroku
Zakładam świeży projekt utworzony poleceniem npx create-next-app@latest --typescript z App Routerem. Instalacja Auth.js v5 sprowadza się do trzech kroków: dodania pakietu, wygenerowania sekretu i utworzenia trzech plików.
Zainstaluj bibliotekę i providery, których zamierzasz użyć (w przykładzie GitHub oraz warstwa Credentials):
pnpm add next-auth@beta
pnpm add @auth/prisma-adapter # tylko jeśli chcesz sesje bazodanowe
npx auth secret # generuje AUTH_SECRET do .env.local
Polecenie npx auth secret tworzy 32-bajtowy losowy sekret i dopisuje go do .env.local jako zmienną AUTH_SECRET. Ten sekret podpisuje ciasteczka sesyjne, więc jego zgubienie oznacza wylogowanie wszystkich użytkowników przy następnym deployu. Trzymaj go tam, gdzie trzymasz inne sekrety produkcyjne (Vercel Environment Variables, AWS Secrets Manager, Doppler).
Następnie utwórz w projekcie trzy pliki, o których szczegóły w kolejnych sekcjach:
Podział na auth.config.ts i auth.ts nie jest kosmetyczny. To wymóg architektoniczny, ponieważ proxy.ts działa w środowisku edge runtime, gdzie adaptery bazodanowe (Prisma, Drizzle z pg) po prostu nie działają. Do dobrego zrozumienia tego środowiska warto sięgnąć po nasz osobny materiał: Next.js Middleware i Edge Runtime, który tłumaczy dokładnie, jakie API są dostępne w edge.
Konfiguracja pliku auth.ts i podział na edge-safe config
Zacznij od auth.config.ts. Ten plik importuje wyłącznie rzeczy, które działają w edge: nazwane providery bez Node.js API, callbacki i konfigurację tras logowania. Adapter bazodanowy tutaj nie trafia.
Następnie auth.ts, czyli pełna konfiguracja rozszerzająca wersję edge-safe o adapter Prismy oraz provider Credentials (który wymaga bcrypt i tym samym Node runtime):
Route Handler dla ścieżki /api/auth/* jest teraz banalny:
// src/app/api/auth/[...nextauth]/route.ts
export { GET, POST } from "@/auth";
Jak chronić trasy w Next.js 16 za pomocą proxy.ts?
Od Next.js 16 plik middleware.ts nosi nazwę proxy.ts, a eksportowana funkcja to proxy zamiast middleware. Semantyka i matchery pozostały bez zmian, ale każdy istniejący projekt, który aktualizujesz, wymaga zmiany nazwy plików. Vercel dostarcza codemod: npx @next/codemod@canary middleware-to-proxy, który zrobi to automatycznie.
Minimalny proxy.ts chroniący ścieżki pod /dashboard wygląda tak:
W tej wersji cała logika autoryzacji siedzi w callbacku authorized w auth.config.ts. Callback zwraca true, request przechodzi. Zwraca false, Auth.js przekierowuje na stronę logowania zdefiniowaną w pages.signIn. Zwraca instancję Response, używa jej dosłownie (na przykład własne przekierowanie na inną trasę).
Jeśli potrzebujesz bardziej złożonej logiki (na przykład przekierowania zalogowanego użytkownika ze strony /login na /dashboard), opakuj funkcję proxy ręcznie:
W matcherze celowo umieściłem /login. Bez tego przekierowanie zalogowanego użytkownika ze strony logowania nigdy się nie odpali. Warto pamiętać, że proxy.ts chroni tylko trasy stronowe, a endpointy pod /api/* wymagają osobnego sprawdzenia sesji wewnątrz handlera.
Sesje JWT kontra sesje bazodanowe: którą strategię wybrać?
Auth.js v5 daje wybór dwóch strategii sesji, konfigurowanych flagą session.strategy: "jwt" (domyślna) i "database". Wybór wpływa na wydajność, koszt hostingu i możliwość natychmiastowego wylogowania użytkownika. Nie ma tu jednej „poprawnej” odpowiedzi, dlatego porównam obie strategie w tabeli.
Cecha
Sesja JWT
Sesja bazodanowa
Zapytania do bazy na request
0
1
Działa w edge runtime
Tak (weryfikacja podpisu)
Nie (wymaga adaptera)
Natychmiastowe wylogowanie / revoke
Nie (token żyje do wygaśnięcia)
Tak (usuń wiersz sesji)
Rozmiar ciasteczka
~1–2 KB (większy przy dużych claims)
~40 bajtów (identyfikator sesji)
Widoczność aktywnych sesji dla użytkownika
Nie
Tak (lista wierszy w tabeli)
Koszt na Vercel / Cloudflare
Niski (brak wywołań DB w edge)
Wyższy (jedno zapytanie na request)
Rekomendowana dla
Serverless, edge, ruch anonimowo-mieszany
SaaS z panelami, wymogi bezpieczeństwa
W praktyce większość produktów B2C w architekturze serverless korzysta z JWT. Ustaw krótki maxAge (na przykład 15 minut) i włącz mechanizm rotacji przez callback jwt, jeśli potrzebujesz przybliżenia rewokacji bez pełnej sesji bazodanowej. Dla panelów administracyjnych, gdzie zespół musi móc wymusić wylogowanie po zwolnieniu pracownika, wybierz sesje bazodanowe. Jedno zapytanie SELECT to koszt, który znosi każda przyzwoicie skonfigurowana baza.
Uwierzytelnianie w Server Components, Route Handlers i Server Actions
Fajną rzeczą w v5 jest to, że w każdym z tych trzech kontekstów odczytujesz sesję w ten sam sposób: const session = await auth();. Auth.js sam wie, skąd wziąć request: z next/headers, z argumentu handlera lub z argumentu wrappera.
W Server Component (renderowany na serwerze, bez ciasteczek widocznych po stronie klienta):
// src/app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user) redirect("/login");
return <h1>Witaj, {session.user.name}</h1>;
}
W Route Handlerze, gdzie ochronę sesji trzeba zaimplementować jawnie, ponieważ proxy.ts domyślnie nie obejmuje ścieżek /api/*:
// src/app/api/profile/route.ts
import { auth } from "@/auth";
import { NextResponse } from "next/server";
export const GET = auth(async function GET(req) {
if (!req.auth) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json({ user: req.auth.user });
});
W Server Action, czyli najważniejszym miejscu, w którym MUSISZ sprawdzić sesję. Klient może wywołać akcję niezależnie od tego, co pokazuje UI:
// src/app/dashboard/actions.ts
"use server";
import { auth } from "@/auth";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
export async function updateProfile(formData: FormData) {
const session = await auth();
if (!session?.user?.id) {
throw new Error("Unauthorized");
}
await prisma.user.update({
where: { id: session.user.id },
data: { name: formData.get("name") as string },
});
revalidatePath("/dashboard");
}
Powyższy wzorzec potraktuj jako regułę bezpieczeństwa. Sprawdzenie sesji w komponencie renderowanym nie zabezpiecza akcji, bo przeglądarka może wywołać endpoint POST z akcji bezpośrednio, z pominięciem widoku. Więcej wzorców łączących walidację Zod z autoryzacją znajdziesz w naszym przewodniku Server Actions w Next.js: bezpieczne mutacje danych.
Rozszerzanie typów TypeScript o niestandardowe pola sesji
Domyślny typ Session["user"] zawiera tylko name, email oraz image. Jeśli chcesz przechowywać id, role albo plan, musisz zrobić dwie rzeczy: rozszerzyć typy i zaktualizować callbacki, żeby dane rzeczywiście trafiły do sesji.
Rozszerzenie typów odbywa się w pliku deklaracji src/types/next-auth.d.ts:
Bez tych trzech kroków TypeScript pozwoli ci pisać session.user.role, ale runtime zwróci undefined. To najczęstsze źródło błędu „user is admin? no.” w produkcji. Sam wpadłem w to raz i zajęło mi godzinę, żeby dostrzec, że callback po prostu nie kopiuje pola.
Najczęstsze pułapki i migracja z v4
Trzy błędy, które widuję najczęściej przy migracji zespołów z v4 do v5. Po pierwsze, pętle przekierowań. Jeśli w matcherze umieścisz trasy logowania, a w logice zawsze przekierowujesz niezalogowanych na /login, każdy request pod /login odbija się w nieskończoność. Wyklucz stronę logowania z matchera lub wprowadź jawną gałąź, która przepuszcza jej odwiedziny.
Po drugie, importowanie auth.ts w proxy.ts. Adaptery bazodanowe wciągną Node API (crypto, net, bcryptjs) do bundle'u edge, a Next.js zwróci błąd budowania z komunikatem „Module not found: Can't resolve 'net'”. Zawsze rozdzielaj auth.config.ts od pełnej konfiguracji.
Po trzecie, długo żyjące JWT bez rotacji. Domyślny maxAge to 30 dni. Jeśli konto użytkownika zostanie skompromitowane, atakujący ma miesiąc na eksploatację. Ustaw krótszy okres i rotuj tokeny w callbacku jwt, bądź przełącz się na sesje bazodanowe. Więcej o cyklu życia cache i inwalidacji przeczytasz w naszym opracowaniu Partial Prerendering (PPR) w Next.js 16, gdzie omawiam interakcję sesji z warstwą cache.
Migracja z v4 wygląda mniej więcej tak: usuwasz authOptions, zamieniasz getServerSession na await auth(), przenosisz konfigurację z pages/api/auth/[...nextauth].ts do korzenia jako auth.ts, aktualizujesz wszystkie miejsca, w których używałeś useSession. API klienta pozostało, ale sygnały reaktywne działają nieco inaczej. Szczegółowa lista przełomowych zmian znajduje się w dokumentacji Auth.js dotyczącej ochrony tras oraz w oficjalnym przewodniku uwierzytelniania Next.js, który zawiera diagramy przepływów.
Najczęściej zadawane pytania
Czy Auth.js to to samo co NextAuth?
Tak. Auth.js to nowa marka biblioteki dawniej znanej jako NextAuth.js. Pakiet NPM nadal nazywa się next-auth, ale marka i dokumentacja odzwierciedlają rozszerzenie zasięgu na inne frameworki (SvelteKit, SolidStart). W kontekście Next.js 16 różnice praktyczne dotyczą wyłącznie API v5.
Czy Next.js 16 obsługuje NextAuth?
Tak, Next.js 16 w pełni obsługuje Auth.js v5 (czyli next-auth w wersji 5). Trzeba jednak pamiętać o przemianowaniu middleware.ts na proxy.ts i eksportowaniu funkcji jako proxy. Codemod npx @next/codemod@canary middleware-to-proxy wykona tę zmianę automatycznie.
Jak chronić Route Handlery pod /api/*?
proxy.ts domyślnie nie obejmuje ścieżek /api/*. Musisz albo dodać je do matcher, albo (co jest zalecane) opakować każdy handler funkcją auth(async function GET(req) { ... }) i sprawdzać req.auth. To jawna weryfikacja, która nie zależy od routingu.
Kiedy wybrać sesje bazodanowe zamiast JWT?
Wybierz sesje bazodanowe, gdy potrzebujesz natychmiastowego wylogowania użytkownika (na przykład po zwolnieniu pracownika lub przejęciu konta) albo gdy chcesz pokazać użytkownikowi listę jego aktywnych sesji na różnych urządzeniach. Dla większości produktów B2C na Vercelu wystarczy JWT z krótkim maxAge.
Dlaczego dostaję błąd „Module not found: Can't resolve 'net'” w proxy.ts?
Zaimportowałeś do proxy.ts plik auth.ts, który używa adaptera bazodanowego wymagającego Node API. Rozdziel konfigurację: auth.config.ts (edge-safe, bez adaptera) importuj do proxy.ts, a auth.ts (z adapterem) tylko do Server Components, Route Handlers i Server Actions.
Partial Prerendering łączy najlepsze cechy renderowania statycznego i dynamicznego w jednej trasie. Sprawdź, jak włączyć cacheComponents w Next.js 16, projektować granice Suspense i unikać typowych pułapek migracji.
Praktyczny przewodnik po Parallel Routes i Intercepting Routes w Next.js 16. Buduj modale z deep linking, dashboardy z niezależnymi slotami i poznaj najczęstsze pułapki — z gotowymi przykładami kodu.
Przewodnik po Server Actions w Next.js App Router — hooki React 19 (useActionState, useFormStatus), walidacja z Zod, wzorce bezpieczeństwa, rewalidacja cache i next-safe-action z praktycznymi przykładami TypeScript.