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 Útmutató 2026: OAuth & JWT Session

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 Editorial Team

Our team of expert writers and editors.