Auth.js v5 ja Next.js 16: täydellinen autentikointiopas App Routerille (2026)

Auth.js v5 (NextAuth) ja Next.js 16: täydellinen App Router -autentikointiopas. Asennus, OAuth-providerit, Credentials, proxy.ts-suojaus, roolipohjaiset oikeudet ja Edge-runtime-yhteensopivuus. Mukana toimivat koodiesimerkit Server Componenteille ja Server Actioneille.

Päivitetty: 27. toukokuuta 2026

Auth.js v5 (entinen NextAuth.js) on Next.js 16:n suositelluin tapa toteuttaa autentikointi App Routerissa, koska se tukee suoraan Server Componentteja, Server Actioneita ja uutta proxy.ts-tiedostoa (entinen middleware). Käytännössä asennat next-auth@beta-paketin, määrittelet providerit yhteen auth.ts-tiedostoon ja viet siitä neljä funktiota: auth, handlers, signIn ja signOut. Tässä oppaassa rakennetaan toimiva autentikointi alusta loppuun: OAuth (Google/GitHub), Credentials-tunnistautuminen, reittien suojaus, roolipohjaiset oikeudet ja Edge-runtime-yhteensopivuus.

  • Auth.js v5 (5.0.0-beta.29, toukokuu 2026) on yhteensopiva Next.js 16:n App Routerin ja React 19:n kanssa, ja korvaa getServerSession-kutsut yhdellä auth()-funktiolla.
  • Konfiguraatio jaetaan kahteen tiedostoon: kevyt auth.config.ts Edge-yhteensopivaan proxy.ts:ään ja täysi auth.ts Node-runtimea varten, koska adapterit (Prisma, Drizzle) eivät toimi Edgessä.
  • JWT-strategia on oletus ja toimii ilman tietokantaa; database-strategia vaatii adapterin, mutta antaa istuntojen mitätöinnin ja yhden sisäänkirjautumisen useilta laitteilta.
  • Server Actionit ovat oikea paikka signIn- ja signOut-kutsuille, koska ne suoritetaan palvelimella ja säilyttävät evästekontekstin oikein.
  • Reittien suojaus kannattaa toteuttaa kerroksittain: proxy.ts hoitaa karkean uudelleenohjauksen, ja Server Components / Route Handlers tekevät tarkemmat tarkistukset auth():lla.
  • Roolipohjainen pääsynvalvonta hoidetaan jwt- ja session-callbackeissa lisäämällä role-kenttä token-objektiin.

Mikä on Auth.js v5 ja miten se eroaa NextAuth v4:stä?

Auth.js v5 on NextAuth.js:n uusi sukupolvi, ja nimi vaihtui kuvastamaan sitä, että kirjasto tukee myös SvelteKitiä, SolidStartia ja Expressiä, ei pelkkää Next.js:ää. Next.js-pakettina nimi on edelleen next-auth, mutta versionumero alkaa viidestä. Itse pyöritin v4:ää tuotannossa kolme vuotta, ja v5 on selvästi parempi App Routerin kanssa. Migraatiossa on tosin muutama paikka jossa pages-router-tavat kolahtavat (tästä myöhemmin lisää).

Suurin yksittäinen muutos: kaikki istuntoa lukevat kutsut korvataan yhdellä auth()-funktiolla. Pages-routerissa käytettiin getServerSession(authOptions):ia, ja API-reiteissä getToken:ia. Nyt sama auth() toimii Server Componenteissa, Route Handlereissa, Server Actioneissa ja proxy.ts:ssä. Konfiguraatiokin siirtyi: vanha [...nextauth]/route.ts-tiedosto on edelleen olemassa, mutta sen sisältö on yksirivinen export { handlers as { GET, POST } } from "@/auth".

Toinen iso muutos on Edge-runtime-yhteensopivuus. v4 ei toiminut Edgessä jos adapter oli päällä, koska Prisma ja muut tietokanta-ajurit vaativat Node-ajoympäristön. v5 ratkaisee tämän jakamalla konfiguraation kahteen tiedostoon: kevyt Edge-versio menee proxy.ts:ään ja täysi versio Node-puolelle.

Asennus ja perusasetukset Next.js 16:een

Aloitetaan tyhjästä Next.js 16 -projektista. Asennus on suoraviivainen, mutta huomaa että v5 on edelleen beta-statusta. Komento next-auth@beta hakee uusimman 5.0.0-beta-version. Toukokuussa 2026 vakain versio on 5.0.0-beta.29.

pnpm add next-auth@beta
pnpm add @auth/core
# Vaihtoehtoinen adapter, vain jos käytät database-istuntoja
pnpm add @auth/prisma-adapter

Ympäristömuuttujat menevät .env.local-tiedostoon. AUTH_SECRET on pakollinen tuotannossa ja sitä käytetään JWT:n allekirjoitukseen. Generoi se komennolla openssl rand -base64 32 tai npx auth secret. Tuotannossa Vercelissä lisää sama arvo Environment Variables -välilehdellä.

# .env.local
AUTH_SECRET="käytä-openssl-rand-base64-32"
AUTH_URL="http://localhost:3000"
AUTH_GOOGLE_ID="..."
AUTH_GOOGLE_SECRET="..."

Luo projektin juureen tai src/-hakemistoon auth.ts-tiedosto. Tämä on Auth.js v5:n keskuspaikka, ja kaikki muu importoi täältä.

// auth.ts
import NextAuth from "next-auth";
import Google from "next-auth/providers/google";

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [Google],
  pages: {
    signIn: "/login",
  },
  session: { strategy: "jwt" },
});

Sitten yksi Route Handler joka delegoi handlers:ille. Polku on tärkeä: app/api/auth/[...nextauth]/route.ts.

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

OAuth-providerit: Google ja GitHub

Auth.js v5 tukee yli 80 OAuth-provideria laatikosta valmiina. Konfiguraatio on yhden rivin importti, kunhan ympäristömuuttujien nimet noudattavat konventiota AUTH_<PROVIDER>_ID ja AUTH_<PROVIDER>_SECRET. Tällöin Auth.js löytää ne automaattisesti eikä konfiguraatiota tarvita.

// auth.ts
import NextAuth from "next-auth";
import Google from "next-auth/providers/google";
import GitHub from "next-auth/providers/github";

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Google({
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code",
        },
      },
    }),
    GitHub,
  ],
  session: { strategy: "jwt" },
});

Google-providerin prompt: "consent" ja access_type: "offline" ovat välttämättömiä, jos haluat refresh tokenin. Ilman näitä Google palauttaa refresh tokenin vain ensimmäisellä kirjautumisella, mikä on klassinen sudenkuoppa kehitysvaiheessa kun testaat kirjautumista useaan kertaan. Itse törmäsin tähän eräässä asiakasprojektissa, ja ratkaisuna piti käydä Googlen Account permissions -sivulla poistamassa appin pääsy, jotta refresh token tuli takaisin.

OAuth-callback-URL on aina https://<domain>/api/auth/callback/<provider>. Lisää Google Cloud Consolen OAuth-asetuksiin sekä paikallinen URL (http://localhost:3000/api/auth/callback/google) että tuotanto-URL. GitHubin OAuth Apps -asetuksissa Authorization callback URL on sama kaava.

Itse kirjautumislinkki Server Componentista näyttää tältä. Käytä Server Actionia, älä client-side signIn:iä, koska palvelin osaa asettaa evästeet oikein ilman flash-ehkä-kirjautunut-tilaa.

// app/login/page.tsx
import { signIn } from "@/auth";

export default function LoginPage() {
  return (
    <form
      action={async () => {
        "use server";
        await signIn("google", { redirectTo: "/dashboard" });
      }}
    >
      <button type="submit">Kirjaudu Googlella</button>
    </form>
  );
}

Credentials-provider ja oma kirjautumislomake

Jos haluat sähköposti/salasana-tunnistautumisen, käytä Credentials-provideria. Tämä on alue jossa pages-router-tavat kolahtavat eniten: v4:ssä authorize-funktio palautti null virheelle, v5:ssä on parempi heittää CredentialsSignin-virhe tai oma AuthError-aliluokka, jotta UI saa kunnon palautteen.

// auth.ts
import NextAuth, { CredentialsSignin } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
import { getUserByEmail } from "@/lib/users";
import { z } from "zod";

class InvalidLoginError extends CredentialsSignin {
  code = "invalid_credentials";
}

const credentialsSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Credentials({
      credentials: {
        email: { label: "Sähköposti", type: "email" },
        password: { label: "Salasana", type: "password" },
      },
      async authorize(credentials) {
        const parsed = credentialsSchema.safeParse(credentials);
        if (!parsed.success) throw new InvalidLoginError();

        const user = await getUserByEmail(parsed.data.email);
        if (!user?.passwordHash) throw new InvalidLoginError();

        const valid = await bcrypt.compare(
          parsed.data.password,
          user.passwordHash
        );
        if (!valid) throw new InvalidLoginError();

        return { id: user.id, email: user.email, name: user.name };
      },
    }),
  ],
  session: { strategy: "jwt" },
});

Lomake itse on Server Action. signIn heittää NEXT_REDIRECT-virheen onnistuneen kirjautumisen jälkeen, joten ota se try/catch:ssa ja heitä uudelleen, muuten error boundary nappaa sen.

// app/login/actions.ts
"use server";
import { signIn } from "@/auth";
import { AuthError } from "next-auth";

export async function loginAction(_: 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: "Virheellinen sähköposti tai salasana" };
    }
    throw error; // NEXT_REDIRECT
  }
}

Reittien suojaus proxy.ts:llä

Next.js 16 nimesi middleware-tiedoston proxy.ts:ksi. Tämä on hyvä uutinen Auth.js:n kanssa. Jos haluat lukea aiheesta tarkemmin, tutustu artikkeliin Next.js Proxy: autentikointi, tietoturva ja reititys käytännössä. proxy.ts pyörii oletuksena Edge-runtimessa, joten et voi tuoda siellä Node-vain riippuvuuksia kuten bcryptjs, pg tai Prismaa.

Ratkaisu on jakaa Auth-konfiguraatio kahtia. Kevyt versio menee auth.config.ts:ään ja täysi versio auth.ts:ään.

// auth.config.ts (Edge-yhteensopiva)
import type { NextAuthConfig } from "next-auth";

export const authConfig = {
  pages: { signIn: "/login" },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith("/dashboard");
      if (isOnDashboard) return isLoggedIn;
      return true;
    },
  },
  providers: [], // tyhjä, täytetään auth.ts:ssä
} satisfies NextAuthConfig;
// proxy.ts
import NextAuth from "next-auth";
import { authConfig } from "./auth.config";

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

export default proxy((req) => {
  // tyhjä, authorized-callback hoitaa logiikan
});

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

authorized-callback on Auth.js v5:n tapa korvata vanha withAuth-HOC. Se ajetaan jokaisella pyynnöllä proxyssä ja palauttaa true, false tai NextResponse:n. Jos palautat false ja matcher osuu reittiin, Auth.js uudelleenohjaa pages.signIn-polkuun automaattisesti.

auth()-funktio Server Componenteissa

Server Componentissa kutsut auth():ia suoraan ilman propsia tai contextia. Se lukee evästeen, validoi JWT:n (tai hakee istunnon tietokannasta jos käytät database-strategiaa) ja palauttaa session-objektin tai null:n.

// 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 (
    <div>
      <h2>Tervetuloa, {session.user.name}</h2>
      <p>Sähköposti: {session.user.email}</p>
    </div>
  );
}

Sama auth() toimii Route Handlereissa ja Server Actioneissa. Tämä on hyvä asia jos olet tottunut RSC- ja Server Action -suunnittelumalleihin. Autentikointitarkistus on yksi rivi, ei väliohjelma- tai Provider-rituaalia.

// app/api/posts/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: "Ei oikeuksia" }, { status: 401 });
  }
  // ... hae postaukset
  return NextResponse.json({ user: req.auth.user });
});

Roolipohjaiset oikeudet ja callbackit

Roolit lisätään jwt- ja session-callbackeissa. JWT-strategialla rooli tallennetaan tokeniin ensimmäisellä kirjautumisella ja luetaan sitten joka pyynnössä ilman tietokantakutsua.

// auth.ts
callbacks: {
  async jwt({ token, user }) {
    if (user) {
      // ensimmäinen kirjautuminen, kysytään tietokannasta
      const dbUser = await getUserById(user.id!);
      token.role = dbUser?.role ?? "user";
    }
    return token;
  },
  async session({ session, token }) {
    if (session.user) {
      session.user.role = token.role as string;
    }
    return session;
  },
},

TypeScript-tyypitys vaatii moduulin laajennuksen. Luo types/next-auth.d.ts:

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

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

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

Tietokanta-adapterit: Prisma ja Drizzle

Jos haluat database-istunnot tai pysyvät käyttäjätiedot OAuth-providerilta, lisää adapter. Drizzle on omakohtaisesti nopein App Routerissa, ja käsittelen Drizzlen syvällisesti artikkelissa Next.js ja Drizzle ORM: käytännön opas. Tässä lyhyt Prisma-versio:

// auth.ts
import { PrismaAdapter } from "@auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [Google, GitHub],
  session: { strategy: "jwt" }, // tai "database"
});

Tärkein gotcha: Credentials-provider ei tue database-istuntoja. Jos käytät sekä OAuth:ia että Credentials-provideria, pakota session.strategy arvoon "jwt". Voit silti tallentaa käyttäjät tietokantaan adapterilla, ja pelkkä istunto pysyy JWT:ssä.

Edge-runtime-yhteensopivuus

Edge-yhteensopivuus oli v4:n suurin kipupiste. v5 ratkaisee sen jakamalla konfiguraation: auth.config.ts ei saa importoida mitään Node-vain pakettia (ei adapteria, ei bcryptjs:ää, ei Prismaa). auth.ts sen sijaan saa, koska Route Handlerit ja Server Components pyörivät oletuksena Node-runtimessa.

Käytännössä matriisi on tämä:

TiedostoRuntimeAdapteritNode-vain paketit
auth.config.tsEdge + NodeEIEI
auth.tsNodekylläkyllä
proxy.tsEdge (oletus)EIEI
Server ComponentsNodekylläkyllä
Route HandlersNode (oletus)kylläkyllä

Jos haluat ajaa koko sovelluksen Edge-runtimessa esim. paremman geografisen latenssin takia, sinun on käytettävä Edge-yhteensopivaa tietokanta-ajuria, kuten Drizzle + Neon serverless, Tursoa (libSQL) tai Vercel Postgresia. Prisma ei toimi Edgessä ilman Prisma Accelerate -palvelua.

Yleisiä virheitä ja korjauksia

"MissingSecret" tuotannossa: AUTH_SECRET puuttuu ympäristömuuttujista. Vercelissä lisää se Project Settings → Environment Variables, ja deploy uudelleen, sillä muuttujia ei lueta lennossa.

"UntrustedHost" Cloudflare/proxy-takana: Lisää trustHost: true NextAuth-konfiguraatioon. Tämä on tarpeen kun pyyntö menee kuormantasaajan kautta ja Host-header eroaa AUTH_URL:sta.

OAuth-kirjautuminen onnistuu, mutta käyttäjä on aina null: Yleisin syy on, että proxy.ts uudelleenohjaa /api/auth/callback/*-reitin /login:iin. Lisää matcher-konfiguraatioon api poikkeukseksi (kuten yllä). Tämän bugin parissa hutsasin itse pari tuntia ennen kuin tajusin matcherin ongelman.

"Server-only" -virhe selaimessa: Tuot auth.ts:ää Client Componenttiin. Käytä useSession-hookkia next-auth/react:sta Client-puolella, ja varmista että app/layout.tsx:ssä on SessionProvider.

Usein kysytyt kysymykset

Mitä eroa on NextAuth v4:llä ja Auth.js v5:llä?

Suurin ero on App Router -tuki ja Edge-yhteensopivuus. v5 korvaa getServerSession:in yhdellä auth()-funktiolla joka toimii Server Componenteissa, Server Actioneissa, Route Handlereissa ja proxyssä. Konfiguraatio jaetaan kahteen tiedostoon, jotta Node-vain adapterit eivät estä Edge-deploymenttia.

Voiko Auth.js v5:ttä käyttää Edge-runtimessa?

Kyllä, mutta vain Edge-yhteensopivilla provideilla ja ilman adapteria. Käytännössä proxy.ts tuo kevyen auth.config.ts:n ja Server Components -puoli käyttää täyttä auth.ts:ää. Credentials-provider Edgessä vaatii Edge-yhteensopivan salasanan hashauksen kuten @noble/hashes, koska bcryptjs ei toimi.

Pitääkö käyttää JWT- vai database-istuntoja?

JWT on oletus ja oikea valinta useimpiin sovelluksiin: ei tietokantakutsua jokaisella pyynnöllä, toimii Edgessä ja skaalautuu vaivatta. Database-istunnot kannattavat jos tarvitset välittömän mitätöinnin (pakko-uloskirjautumisen) tai aktiivisten istuntojen listauksen useilta laitteilta.

Miten suojaan Server Actionin Auth.js:llä?

Kutsu auth() Server Actionin sisällä ja tarkista session?.user sekä rooli ennen mutaation suorittamista. Älä luota proxyn matcheriin yksin, sillä proxy hoitaa karkean uudelleenohjauksen, mutta Server Action -kutsut menevät POST-pyyntöinä, ja hyökkääjä voi yrittää suoraa kutsua ilman selainnavigointia.

Miten lisään refresh tokenin Auth.js v5:een?

OAuth-providereilla aseta access_type: "offline" ja prompt: "consent" Googlella. Tallenna access_token, refresh_token ja expires_at JWT-tokeniin jwt-callbackissa. Kun token vanhenee, päivitä se kutsumalla providerin token endpointia ja palauta uusi token samasta callbackista.

Ben Howard
Tietoa Kirjoittajasta Ben Howard

Full-stack Next.js developer who's been with the framework since pages-only days. Slowly warming up to App Router.