Auth.js v5 hitelesítés Next.js App Routerben: OAuth, Credentials és session-kezelés

Az Auth.js v5 (a régi NextAuth) átírta a hitelesítés API-ját az App Routerhez. Végigmegyünk az OAuth és Credentials beállításon, a Drizzle adapteren, a JWT session stratégián, a middleware-integráción és az RSC/Server Action mintákon — működő kódpéldákkal.

Auth.js v5 + Next.js Útmutató 2026: OAuth

Na jó, vágjunk bele. Az Auth.js v5 (a régi NextAuth.js, csak új névvel) 2025 végén stabilizálódott, és — őszintén szólva — eléggé átszabta az API-t a Next.js App Router köré. A régi [...nextauth]/route.ts fájl helyett ma már egy központi auth.ts-ből exportált függvényekkel dolgozunk, és ami szerintem a legszebb az egészben: a session olvasása RSC-ben, Server Actionben, middleware-ben és Route Handlerben pontosan ugyanaz a hívás. Ebben az útmutatóban végigmegyünk a teljes 2026-os beállításon — OAuth, Credentials, adatbázis-adapter, middleware-védelem, és persze a klasszikus csapdák — működő kódpéldákkal.

Mi újdonság van az Auth.js v5-ben?

A v4-ről v5-re váltás nem kozmetikai, hanem strukturális. Ha tehát régebbi tutorialokat olvasol (és bevallom, én is beleestem ebbe a hibába a múlt nyáron), elavult mintákat fogsz látni. A legfontosabb változások 2026-ra:

  • Univerzális auth() függvény: ugyanaz a hívás működik Server Componentben, Server Actionben, Route Handlerben és middleware-ben. Nincs többé külön getServerSession, getSession és useSession különbség a szerveroldalon — egyszerűen csak auth().
  • Központi konfigurációs fájl: a hitelesítés gyökere egy auth.ts a projekt gyökerében, ami visszaad auth, handlers, signIn és signOut exporokat.
  • Edge-kompatibilis hasítás: a middleware-be külön auth.config.ts kerül, ami nem importál Node-only adaptert vagy bcryptet, így gond nélkül futtatható az Edge Runtime-on.
  • Új csomagnév: next-auth@5 (igen, még mindig ezen a néven publikálva), de hivatalosan az „Auth.js" márka alatt.
  • Beépített signIn és signOut Server Actionökhöz: nem kell többé a next-auth/react kliens hookot ráerőltetni egy egyszerű űrlapra. Hála.

Telepítés és környezeti változók

A v5 még béta-jelölővel publikál (igen, 2026-ban is), így a kanonikus telepítés a @beta tag mentén történik — a stabil release-line ezt használja, lásd a authjs.dev migrációs jegyzeteket:

npm install next-auth@beta
npm install @auth/drizzle-adapter   # ha adatbázist használsz
npm install bcryptjs                # ha Credentials providert használsz
npm install -D @types/bcryptjs

Ezután generálj egy biztonságos AUTH_SECRET-et — a v5 ezt használja a JWT és a CSRF cookie aláírására:

npx auth secret
# vagy: openssl rand -base64 32

A .env.local minimális változatban valahogy így néz ki:

AUTH_SECRET="a-generalt-titkos-kulcs"
AUTH_TRUST_HOST=true   # nem-Vercel produkciós környezetben (Docker, custom)

# OAuth példák
AUTH_GOOGLE_ID="..."
AUTH_GOOGLE_SECRET="..."
AUTH_GITHUB_ID="..."
AUTH_GITHUB_SECRET="..."

# Adatbázis
DATABASE_URL="postgres://..."

Fontos: Vercelen az AUTH_TRUST_HOST automatikusan beáll. Saját Docker-konténerben, vagy Cloudflare/Fly.io mögött viszont manuálisan kell true-ra tenni, különben a v5 elutasítja a host header-t és csodás 500-as hibát kapsz a callback URL-en. (Tudom, mert legutóbb fél órát vadásztam erre.)

Az auth.config.ts és auth.ts különválasztása

Itt jön a lényeg. Az App Router middleware-je az Edge Runtime-on fut, ahol nem érhető el a Node.js crypto modul, és — ami talán fájdalmasabb — a Drizzle/Prisma adapterek sem futnak. Ezért az Auth.js v5 hivatalos mintája egy kettős fájl: egy edge-safe konfigurációs fájl, és egy node-only főfájl, ami kibővíti azt.

auth.config.ts (edge-kompatibilis)

// auth.config.ts
import type { NextAuthConfig } from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";

export default {
  providers: [
    GitHub,   // automatikusan olvassa az AUTH_GITHUB_ID/SECRET értékeket
    Google,
  ],
  pages: {
    signIn: "/login",
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith("/dashboard");
      if (isOnDashboard) return isLoggedIn;
      return true;
    },
  },
} satisfies NextAuthConfig;

auth.ts (node runtime, adapterrel)

// auth.ts
import NextAuth from "next-auth";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import Credentials from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
import { z } from "zod";
import { db } from "@/db";
import { users } from "@/db/schema";
import { eq } from "drizzle-orm";
import authConfig from "./auth.config";

export const { handlers, auth, signIn, signOut } = NextAuth({
  ...authConfig,
  adapter: DrizzleAdapter(db),
  session: { strategy: "jwt" },
  providers: [
    ...authConfig.providers,
    Credentials({
      async authorize(credentials) {
        const parsed = z
          .object({ email: z.string().email(), password: z.string().min(8) })
          .safeParse(credentials);
        if (!parsed.success) return null;

        const { email, password } = parsed.data;
        const user = await db.query.users.findFirst({
          where: eq(users.email, email),
        });
        if (!user?.passwordHash) return null;

        const ok = await bcrypt.compare(password, user.passwordHash);
        if (!ok) return null;

        return { id: user.id, email: user.email, name: user.name };
      },
    }),
  ],
});

A trükk: a middleware csak az auth.config.ts-t importálja, így az Edge bundle nem kapja meg a Drizzle vagy bcrypt függőséget. A teljes auth.ts pedig szépen a Node runtime-ra korlátozódik (Server Components, Actions, API útvonalak). Egyszerű, de zseniális.

Route Handler és middleware bekötése

Az App Router catch-all route handler az egyetlen, ami szinte változatlan maradt v4-ből:

// app/api/auth/[...nextauth]/route.ts
export { GET, POST } from "@/auth";

A middleware viszont teljesen új mintát követ:

// middleware.ts
import NextAuth from "next-auth";
import authConfig from "./auth.config";

export const { auth: middleware } = NextAuth(authConfig);

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

Az authorized callback (lásd auth.config.ts) eldönti, hogy egy adott útvonalon milyen szabály érvényesül. Ez sokkal tisztább, mint a v4-es withAuth HOC volt, mert a logika ugyanabban a fájlban él, mint a providerek — ami szerintem hatalmas előrelépés DX szempontból.

Credentials provider biztonságos jelszókezeléssel

A Credentials provider egy gyakran félreértett pont. Hash-elj rendesen: a regisztrációkor (a perzisztált hash), és soha, de tényleg soha ne küldd el a nyers jelszót semmilyen logba. A bcrypt 12-es saltrounddal 2026-ban még elfogadható, de ha lehet, válts argon2id-re produkcióban. (A bcrypt egyébként szépen öregszik, csak épp nem elég gyorsan ahhoz, hogy a modern GPU-kkal lépést tartson.)

Regisztráció Server Actionnel

// app/(auth)/register/actions.ts
"use server";

import { z } from "zod";
import bcrypt from "bcryptjs";
import { db } from "@/db";
import { users } from "@/db/schema";
import { redirect } from "next/navigation";

const RegisterSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(128),
  name: z.string().min(1).max(100),
});

export async function register(_: unknown, formData: FormData) {
  const parsed = RegisterSchema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) {
    return { error: "Érvénytelen adatok" };
  }

  const passwordHash = await bcrypt.hash(parsed.data.password, 12);

  try {
    await db.insert(users).values({
      email: parsed.data.email,
      name: parsed.data.name,
      passwordHash,
    });
  } catch {
    return { error: "Ez az e-mail már foglalt" };
  }

  redirect("/login");
}

Bejelentkezés signIn-nel Server Actionben

// app/(auth)/login/actions.ts
"use server";

import { signIn } from "@/auth";
import { AuthError } from "next-auth";

export async function login(_: unknown, formData: FormData) {
  try {
    await signIn("credentials", {
      email: formData.get("email"),
      password: formData.get("password"),
      redirectTo: "/dashboard",
    });
  } catch (error) {
    if (error instanceof AuthError) {
      return error.type === "CredentialsSignin"
        ? { error: "Hibás e-mail vagy jelszó" }
        : { error: "Hitelesítési hiba történt" };
    }
    throw error; // a Next.js redirect dobott AuthError nem ide tartozik
  }
}

Csapda — és erről egész könyvet lehetne írni: a signIn() sikeres hívás esetén egy speciális NEXT_REDIRECT hibát dob, ami valójában nem hiba, hanem a redirect mechanizmusa. Ezt nem szabad elkapni a try/catch-ben. Ezért fontos a fenti throw error ág: csak a valódi AuthError példányokat kezeljük, a többit hagyjuk továbbpattanni.

Session-stratégia: JWT vs. adatbázis

Az Auth.js v5 két stratégiát kínál, és — őszintén — ez egy kérdés, amit a legtöbben rosszul döntenek el:

SzempontJWTDatabase
Edge middleware-ben olvashatóIgenNem (DB hívás kell)
Session érvénytelenítésCsak lejáratigAzonnali (DELETE row)
OAuth-only támogatásIgenIgen
Credentials támogatásCsak ez működikNem hivatalos
Méret a cookie-ban~1-4 KBCsak session ID

A 2026-os ajánlás röviden: ha bármilyen Credentials providert használsz, válaszd a strategy: "jwt"-et, és a friss adatokat (jogosultságok, profilkép) a jwt és session callbackekben írd át. Ha tisztán OAuth-t használsz és azonnali kijelentkezést akarsz támogatni minden eszközön, válaszd a database stratégiát. Ennyi. A legtöbb projektnél a JWT a megfelelő választás.

JWT callbackek bővítése

// auth.ts (kiegészítés)
callbacks: {
  async jwt({ token, user, trigger, session }) {
    if (user) {
      token.id = user.id;
      token.role = (user as { role?: string }).role ?? "user";
    }
    if (trigger === "update" && session?.name) {
      token.name = session.name; // useSession().update() trigger
    }
    return token;
  },
  async session({ session, token }) {
    session.user.id = token.id as string;
    session.user.role = token.role as string;
    return session;
  },
},

A TypeScript típusok bővítéséhez egy modul-augmentációs fájl kell — enélkül jönnek a vörös hullámvonalak az IDE-ben:

// types/next-auth.d.ts
import "next-auth";
import "next-auth/jwt";

declare module "next-auth" {
  interface Session {
    user: {
      id: string;
      role: string;
      name?: string | null;
      email?: string | null;
      image?: string | null;
    };
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    id: string;
    role: string;
  }
}

Session olvasása Server Componentben és Route Handlerben

És itt jön a v5 legelegánsabb része: mindenhol ugyanaz a hívás. Tényleg mindenhol.

// Server Component
import { auth } from "@/auth";

export default async function DashboardPage() {
  const session = await auth();
  if (!session) return null; // a middleware már átirányított ide jutva

  return <p>Üdv, {session.user.name}!</p>;
}
// Route Handler
import { auth } from "@/auth";
import { NextResponse } from "next/server";

export async function GET() {
  const session = await auth();
  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  return NextResponse.json({ userId: session.user.id });
}
// Server Action
"use server";
import { auth } from "@/auth";

export async function deletePost(id: string) {
  const session = await auth();
  if (!session?.user) throw new Error("Unauthorized");
  // ... törlés logika
}

Kliensoldalon (csak ha tényleg szükséges, pl. egy dropdown menü user-statusához) a useSession hook él tovább, de előtte be kell ágyaznod egy SessionProvider-t a layoutba.

OAuth callback és profilmezők testreszabása

A GitHub és Google providerek alapból az e-mailt és a nevet hozzák, de a gyakorlatban majdnem mindig kell egy saját role mező vagy onboarding-flag. Itt jön be a profile callback:

GitHub({
  profile(profile) {
    return {
      id: profile.id.toString(),
      name: profile.name ?? profile.login,
      email: profile.email,
      image: profile.avatar_url,
      role: profile.email === process.env.ADMIN_EMAIL ? "admin" : "user",
    };
  },
}),

Egy fontos részlet, amibe sokan beleesnek: ezt csak az első bejelentkezéskor használja az adapter — a következő hívásoknál már a DB-ből olvas. Tehát a role későbbi módosítása nem ide, hanem a DB-be történik, és a jwt callback fogja onnan frissíteni.

Védelem rétegekben: middleware + page guard + action guard

A 2026-os legjobb gyakorlat három rétegű védelem, és ez nem túlzás. A middleware ugyanis megkerülhető (lásd CVE-2025-29927), a kliens pedig — hát, a kliens sosem volt megbízható:

  1. Middleware: gyors átirányítás bejelentkezés előtt. UX-réteg, nem biztonsági.
  2. Layout/Page guard: a védett route layoutjában auth() hívás, és ha nincs session → redirect("/login").
  3. Action/Handler guard: minden mutáló műveletben újraellenőrzés, a felhasználó jogosultságainak vizsgálatával. Ez az igazi biztonsági réteg.
// app/dashboard/layout.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";

export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
  const session = await auth();
  if (!session) redirect("/login");
  return <>{children}</>;
}

Gyakori csapdák 2026-ban

Na, most jön a rész, ami miatt valójában megnyitottad ezt a cikket:

  • „Edge runtime hibát dob bcrypt-tal": nem szabad a bcryptet az auth.config.ts-ben importálni. Tartsd csak az auth.ts-ben — ez a két-fájlos minta egész értelme.
  • „signIn redirect mégis hibát dob": sose vond össze a signIn()-t catch-all catch-csel. Engedd át a NEXT_REDIRECT-et, különben magadba lőttél.
  • „Session frissítése nem érvényesül": JWT stratégia esetén a token addig él, amíg le nem jár. Vagy a kliensoldali session.update() kell, vagy egy alacsonyabb maxAge.
  • „OAuth callback URL mismatch": a v5 alapértelmezett callback URL /api/auth/callback/[provider] — és ez sok dokumentációban (még a régi NextAuth-tutorialokban is) nem stimmel. A pontos URL-t a auth handler logolja indításkor, érdemes onnan másolni.
  • „AUTH_SECRET undefined produkcióban": Vercelen explicit állítsd be Project Settings → Environment Variables alatt. A build folyamat NEM örökli a .env.local-t. Ezt a hibát egyszer mindenki elköveti.

Tesztelés

Az Auth.js v5 tesztelése sokkal egyszerűbb, mint a v4-é volt, mert a session olvasása sima async függvényhívás. Egy egységteszt példa Vitesttel:

import { vi, test, expect } from "vitest";
import { auth } from "@/auth";

vi.mock("@/auth", () => ({
  auth: vi.fn(),
}));

test("védett action elutasítja a vendéget", async () => {
  vi.mocked(auth).mockResolvedValue(null);
  await expect(deletePost("1")).rejects.toThrow("Unauthorized");
});

End-to-end szinten a Playwright storageState mechanizmusa a barátod: egyszer bejelentkezel, és a session cookie-t újrahasználod a tesztek között. Spórol egy csomó futási időt.

Migráció v4-ről v5-re: rövid checklist

  • Telepítsd a next-auth@beta-t.
  • Hozd létre az auth.ts-t és auth.config.ts-t a gyökérben.
  • Cseréld a NEXTAUTH_* env változókat AUTH_* prefixre.
  • Cseréld le a getServerSession() hívásokat auth()-ra.
  • Frissítsd a middleware-t a fenti két-fájlos mintára.
  • Az adapter csomagja most @auth/*-adapter néven publikál (pl. @auth/drizzle-adapter).
  • És persze: készíts adatbázis-mentést. Az adapter séma kompatibilis, de a teszteléshez érdemes vissza tudni állni — bízz bennem.

Mikor érdemes inkább Clerket vagy Lucia-t választani?

Az Auth.js v5 sweet spotja: kis-közepes alkalmazás, saját adatbázissal, OAuth + Credentials kombóval, és testre szabható UI-jal. De nem mindenre ez a jó eszköz. Ha:

  • kész UI-komponenseket akarsz (login form, MFA prompt) → Clerk;
  • teljes kontrollt akarsz a session és cookie szinten, adapter-mentesen → Lucia v3;
  • vállalati SAML/OIDC kell → WorkOS vagy Auth0.

A többi 80%-os esetben viszont az Auth.js v5 a leggyorsabb út a produkcióig App Routerben. És ez nem kis dolog.

Gyakran ismételt kérdések

Az Auth.js v5 ugyanaz, mint a NextAuth.js?

Igen. Az Auth.js az új márkanév, az npm csomag továbbra is next-auth, és minden régi NextAuth fogalom (provider, adapter, callback) megmaradt. A v5 az első major release ezen az új márkán, és kifejezetten az App Router köré tervezett.

Használhatok Credentials providert adatbázis-stratégiával?

Hivatalosan nem. A Credentials provider csak a JWT session-stratégiával kompatibilis. Ha mindkettőt szeretnéd (saját email/jelszó és azonnali session-érvénytelenítés), két út áll nyitva: vagy egyedi „server-side session table"-t építesz a JWT mellé, vagy átállsz Lucia-féle, alacsonyabb szintű könyvtárra. Egyik sem fájdalommentes.

Mit jelent az AUTH_TRUST_HOST=true és mikor kell beállítani?

A v5 alapértelmezésben elutasítja a callback kéréseket, ha a Host fejléc nem felel meg az ismert envelope-nak — így védi magát a Host header injection ellen. Vercelen ezt automatikusan beállítja a platform. Saját Docker-konténerben, Cloudflare Workers mögött vagy bármilyen reverse proxy mögött manuálisan kell true-ra tenni, máskülönben a bejelentkezés szépen elszáll 500-as hibára.

Hogyan érem el a session-t kliens komponensben?

Csomagold be a layout gyökerét a SessionProvider komponenssel a next-auth/react-ből, majd használd a useSession() hookot. A 2026-os ajánlás viszont az, hogy amikor csak lehet, Server Componentben olvass auth()-tal, és a session-adatot propként add le a kliens komponenseknek. Így kerülöd a felesleges JS-bundle-méretet, és a kezdeti render is gyorsabb lesz.

Hogyan implementáljak role-based access controlt (RBAC)?

Tárold a szerepet a users táblában, told be a jwt callbackben a tokenbe, majd ellenőrizd három helyen: middleware-ben (UX), layoutban (page-szintű védelem), és a Server Actionben/Route Handlerben (a tényleges biztonsági réteg). Soha, de tényleg soha ne támaszkodj csak a kliensoldali session.user.role-ra — az két DevTools-kattintással hamisítható.

Összefoglalás

Az Auth.js v5 — ha komolyan veszed a kettős fájl mintát és a háromrétegű védelmet — 2026-ban egyszerűen a leggyorsabb és legtisztább módja a hitelesítésnek Next.js App Routerben. A három kulcsmondat: használj auth.config.ts-t a middleware-hez, tartsd a Node-only függőségeket az auth.ts-ben, és minden mutáló műveletben hívd újra az auth()-t. Ha ezt megteszed, a v4-es örökség legtöbb csapdájától megszabadulsz, és az új univerzális auth() API minden runtime-on egyformán fog viselkedni. Ennyi az egész.

A Szerzőről Priya Raghavan

Priya spent four years on Vercel's developer experience team, where she wrote a chunk of the Turbopack incremental cache layer and rewrote the official App Router migration codemods used by tens of thousands of teams. Before that, she shipped the checkout funnel rewrite at Shopify on Next.js 12, cutting LCP from 3.4s to 1.1s on mid-tier Android. These days she runs an independent consultancy helping Series B companies untangle Pages Router monoliths into RSC-first App Router architectures. She has opinions about Server Actions, mostly negative ones, and is slowly coming around on Partial Prerendering after benchmarking it against six production workloads. She writes about cache semantics, streaming SSR, and the parts of the React docs that quietly contradict the Next.js docs. Eight years in frontend, three of them spent yelling at hydration mismatches.