← Guider för utvecklare
Säkerhet14 min läsning

Autentisering i Next.js

Autentiseringsmönster i Next.js: Auth.js (NextAuth v5), Clerk och custom JWT jämförs med trade-offs och en tydlig rekommendation.

Autentisering är ett av de områden där Next.js-projekt tenderar att fastna. Det finns gott om alternativ, dokumentationen pekar åt olika håll beroende på version, och ett felaktigt val tidigt i projektet kan kosta veckor att rätta till senare. Den här guiden tar upp tre konkreta ansatser: Auth.js (NextAuth v5), Clerk och en custom JWT-lösning. Varje avsnitt visar verklig kod och avslutas med en tydlig rekommendation om när respektive verktyg passar.

Vad behöver du egentligen?

Innan du väljer bibliotek finns det tre arkitekturval att ta ställning till. Svaren på dessa avgör vilket verktyg som passar ditt projekt.

Stateless JWT eller sessionsbaserad autentisering?

Med stateless JWT signeras ett token på servern och skickas till klienten. Varje efterföljande förfrågan inkluderar tokenet, och servern verifierar signaturen utan att slå upp något i en databas. Det är snabbt per förfrågan och fungerar bra med flera parallella instanser. Nackdelen är att ett utfärdat token inte kan återkallas förrän det löper ut, om du inte bygger extra infrastruktur (en blockeringslista, kortare livslängd med refresh tokens, eller liknande).

Med sessionsbaserad autentisering lagras ett sessions-ID i en cookie. Vid varje förfrågan slår servern upp sessions-ID:t i databasen för att hämta användardata. Revocation är omedelbart: ta bort sessionen från databasen och användaren är utloggad direkt. Kostnaden är ett databasanrop per förfrågan, vilket adderar latens och kräver att databasen är tillgänglig.

I praktiken väljer de flesta projekt sessioner för vanliga webbapplikationer och JWT för API:er som konsumeras av mobila klienter eller tredjepartstjänster.

Social login eller credentials?

OAuth-providers (Google, GitHub, Microsoft med flera) hanterar lösenordshashning, lösenordsåterställning, e-postverifiering och tvåfaktorsautentisering åt dig. Säkerhetsansvaret delegeras till en part som har dedikerade säkerhetsteam.

Credentials (e-post och lösenord) ger full kontroll men kräver att du implementerar allt det ovanstående själv: bcrypt-hashning, reset-flöden med signerade länkar, e-postverifiering och brute-force-skydd. Det är mer arbete än det brukar verka vid en första uppskattning.

Hanterad tjänst eller self-hosted?

En hanterad tjänst (Clerk, Auth0, Supabase Auth) tar betalt för att sköta infrastruktur, driftsättning, patchning och compliance. Du betalar med pengar och förlorar viss kontroll.

Self-hosted (Auth.js, Lucia, custom) ger full kontroll över var data lagras och hur systemet fungerar. Du äger underhållsansvaret, inklusive säkerhetsuppdateringar, skalning och incidenthantering.

Dessa tre val i kombination pekar mot vilket av följande alternativ som passar bäst.

Auth.js (NextAuth v5)

Auth.js är det etablerade open source-alternativet för Next.js. Version 5 (fortfarande i beta-fas men stabil nog för produktion) skrevs om för App Router och fungerar sömlöst med Server Components och middleware.

Installation och grundkonfiguration

npm install next-auth@beta

Skapa filen auth.ts i projektets rot:

// auth.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'

export const { auth, handlers, signIn, signOut } = NextAuth({
  providers: [GitHub],
})

Lägg till API-routen som hanterar OAuth-callbacks:

// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'

export const { GET, POST } = handlers

Sätt miljövariablerna i .env.local:

AUTH_SECRET=din-hemliga-nyckel-genererad-med-openssl-rand-base64-32
AUTH_GITHUB_ID=ditt-github-app-client-id
AUTH_GITHUB_SECRET=ditt-github-app-client-secret

AUTH_SECRET används för att signera sessions-cookies och tokens. Generera ett starkt värde med openssl rand -base64 32 eller npx auth secret.

Session i en Server Component

auth()-funktionen returnerar den aktiva sessionen och är asynkron:

// app/dashboard/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'

export default async function Dashboard() {
  const session = await auth()

  if (!session) {
    redirect('/logga-in')
  }

  return (
    <main>
      <p>Välkommen, {session.user?.name}</p>
      <p>Inloggad som: {session.user?.email}</p>
    </main>
  )
}

I en Client Component används useSession-hooken istället, men för Server Components är auth() direkt det enklaste alternativet.

Middleware för ruttskydd

Att skydda rutter via middleware innebär att obehöriga förfrågningar aldrig når din sida eller Server Component:

// middleware.ts
import { auth } from '@/auth'

export default auth((req) => {
  const arInloggad = !!req.auth
  const gallertill = req.nextUrl.pathname

  if (!arInloggad && gallertill !== '/logga-in') {
    return Response.redirect(new URL('/logga-in', req.url))
  }
})

export const config = {
  matcher: ['/dashboard/:path*', '/installningar/:path*'],
}

matcher-mönstret avgör vilka rutter middleware körs på. Håll listan explicit: undvik att matcha allt och exkludera undantag, det gör konfigurationen svårare att förstå.

Databasadapter

Som standard lagrar Auth.js sessioner i JWT-tokens i cookies, utan databas. Om du vill ha sessioner i databasen (för direkt revocation) lägger du till en adapter:

npm install @auth/prisma-adapter
// auth.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'

export const { auth, handlers, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [GitHub],
})

Med en adapter lagras User, Account, Session och VerificationToken i din databas. Auth.js tillhandahåller det schema som krävs (modellerna för användare, sessioner och konton), men du applicerar det själv med npx prisma migrate dev. Auth.js kör inte migreringar automatiskt.

När passar Auth.js?

Auth.js passar bra om projektet är open source och vendor lock-in är oacceptabelt, om teamet vill ha full kontroll över var data lagras, om du integrerar med en provider som Auth.js redan stöder (listan är lång), eller om projektet har en befintlig databas som sessionerna naturligt kan leva i.

Den enda tydliga nackdelen är uppstartstid. Konfigurationen kräver lite mer arbete än Clerk, och felsökning kan vara frustrerande eftersom felmeddelanden i beta-versionen ibland är kryptiska.

Clerk

Clerk är en hanterad autentiseringstjänst byggd specifikt för Next.js (och React i bredare mening). Det är det snabbaste sättet att få en komplett autentiseringslösning på plats.

Installation och konfiguration

npm install @clerk/nextjs

Hämta dina nycklar från Clerk-dashboarden och lägg till dem i .env.local:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

Wrappa root layout med <ClerkProvider>:

// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider>
      <html lang="sv">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}

Det är hela grundkonfigurationen. Clerk läser automatiskt upp nycklarna från miljövariablerna.

Användardata i Server Components

// app/dashboard/page.tsx
import { currentUser } from '@clerk/nextjs/server'

export default async function Dashboard() {
  const anvandare = await currentUser()

  if (!anvandare) {
    return null
  }

  return (
    <main>
      <p>Välkommen, {anvandare.firstName}</p>
      <p>Inloggad som: {anvandare.emailAddresses[0].emailAddress}</p>
    </main>
  )
}

currentUser() returnerar ett rikt användarobjekt med namn, e-postadresser, profilbild, metadata och mer. Jämfört med Auth.js session-objektet är det mer komplett utan extra konfiguration.

Användardata i Client Components

'use client'

import { useUser } from '@clerk/nextjs'

export function Halsning() {
  const { user, isLoaded } = useUser()

  if (!isLoaded) return <p>Laddar...</p>
  if (!user) return null

  return <p>Hej, {user.firstName}</p>
}

useUser() prenumererar på användarens tillstånd och uppdateras automatiskt när användaren uppdaterar sin profil, utan att du behöver hantera tillstånd manuellt.

Middleware för ruttskydd

// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const arSkyddad = createRouteMatcher([
  '/dashboard/:path*',
  '/installningar/:path*',
  '/api/privat/:path*',
])

export default clerkMiddleware(async (auth, req) => {
  if (arSkyddad(req)) {
    await auth.protect()
  }
})

export const config = {
  matcher: ['/((?!_next|.*\\..*).*)'],
}

auth.protect() redirectar automatiskt oinloggade användare till Clerks inloggningssida. Du kan anpassa redirect-URL:en via Clerk-dashboarden eller via konfigurationsparametrar.

Inbyggda UI-komponenter

Clerk levererar färdiga React-komponenter som matchar din brandings inställningar i dashboarden:

import { SignIn, SignUp, UserButton, SignedIn, SignedOut } from '@clerk/nextjs'

// Komplett inloggningsformulär
export default function InloggningsSida() {
  return <SignIn />
}

// Användarknapp med dropdown för profil och utloggning
export function NavbarAnvandare() {
  return (
    <>
      <SignedIn>
        <UserButton />
      </SignedIn>
      <SignedOut>
        <a href="/logga-in">Logga in</a>
      </SignedOut>
    </>
  )
}

<SignIn /> och <SignUp /> inkluderar social login, e-post/lösenord, magiska länkar och tvåfaktorsautentisering beroende på vilka metoder du aktiverat i dashboarden.

När passar Clerk?

Clerk passar utmärkt om du vill ha autentisering på plats snabbt (under 30 minuter för en fungerande lösning), om projektet ryms inom den kostnadsfria nivån (10 000 månatliga aktiva användare), om du vill ha en hanterad användardashboard utan att bygga en själv, eller om ditt team saknar djup erfarenhet av att bygga och underhålla autentiseringssystem.

Den primära kostnaden är vendor lock-in. Migrering bort från Clerk kräver export av användardata, skriven om autentiseringslogik och eventuell upplösning av lösenords-hashar. Det är inte omöjligt men det är arbete.

Custom JWT-lösning

En helt custom JWT-lösning är inkluderad här för fullständighetens skull, inte för att det är ett rekommenderat val för de flesta projekt. Det finns legitima användningsfall, men de är mer sällan förekommande än man tror.

JWT med jose

jose är det rekommenderade biblioteket för JWT i moderna JavaScript-miljöer (inklusive Edge Runtime). Undvik äldre jsonwebtoken-paketet som inte stöder Web Crypto API.

npm install jose
// lib/jwt.ts
import { SignJWT, jwtVerify } from 'jose'

const hemlig = new TextEncoder().encode(process.env.JWT_SECRET)

export async function skapaToken(anvandarId: string) {
  return new SignJWT({ sub: anvandarId })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('7d')
    .sign(hemlig)
}

export async function verifieraToken(token: string) {
  const { payload } = await jwtVerify(token, hemlig)
  return payload
}

Middleware-verifiering

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { verifieraToken } from '@/lib/jwt'

export async function middleware(req: NextRequest) {
  const token = req.cookies.get('auth-token')?.value

  if (!token) {
    return NextResponse.redirect(new URL('/logga-in', req.url))
  }

  try {
    await verifieraToken(token)
    return NextResponse.next()
  } catch {
    return NextResponse.redirect(new URL('/logga-in', req.url))
  }
}

export const config = {
  matcher: ['/dashboard/:path*'],
}

Vad du behöver bygga utöver detta

En fungerande JWT-autentisering för en användarvänd applikation kräver utöver signering och verifiering:

  • Säker cookie-hantering (HttpOnly, Secure, SameSite=Lax eller Strict)
  • Lösenordshashning med bcrypt eller argon2
  • Refresh token-rotation för att hantera kortlivade access tokens
  • En revocation-mekanism (blockeringslista i Redis, eller acceptera att tokens inte kan återkallas)
  • Lösenordsåterställning med signerade, tidsbegränsade URL:er
  • E-postverifiering vid registrering
  • Brute-force-skydd (rate limiting på inloggningsendpointen)
  • CSRF-skydd om du använder cookies

Det är inte omöjligt att bygga, men varje punkt på den listan är ett ställe där ett misstag ger en säkerhetsbrist. Utan dedikerad säkerhetskompetens i teamet är risken hög.

När är custom JWT rätt val?

Custom JWT passar när du integrerar med ett befintligt system som redan utfärdar JWT:er och du behöver validera dem i Next.js, när du bygger intern tooling med väldigt specifika token-krav, eller när teamet har dedikerad säkerhetskompetens och ett tydligt behov som ingen befintlig lösning täcker.

Det är nästan aldrig rätt val för en ny användarvänd applikation som byggs från grunden.

Jämförelse

Auth.jsClerkCustom JWT
Uppstartstid1-2 timmar15-30 minuterDagar
LeverantörsberoendeIngetJaInget
Inbyggda UI-komponenterNejJaNej
KostnadGratisGratis upp till 10k MAUGratis
UnderhållsansvarDuClerkDu
FlexibilitetHögMedelMaximal
Rekommenderas förKontrollbehov, OSSSnabb start, hanteratSpecialfall

Rekommendation

Clerk för de flesta nya projekt. Tidsskillnaden är verklig: en fungerande inloggning med social providers, e-post/lösenord och en användarprofil-sida tar under 30 minuter med Clerk. Det är inte en uppskattning, det är vad det faktiskt tar. Du slipper bygga och underhålla allt det som Clerk hanterar, och den kostnadsfria nivån täcker de flesta projekt fram till kommersiell volym.

Auth.js för team där vendor lock-in är oacceptabelt, projektet självt är open source, eller du behöver full kontroll över var sessionsdata lagras. Det är ett kapabelt bibliotek med bred provider-support, men räkna med att konfigurations- och felsökningsfasen tar längre tid än med Clerk.

Custom JWT nästan aldrig för användarvänd mjukvara. Säkerhetsytan är för stor för att motiveras om du inte har ett specifikt krav som ingen befintlig lösning kan uppfylla. Om du hittar dig själv argumentera för custom JWT i ett vanligt webbprojekt, stanna upp och fråga om anledningen faktiskt håller.

En sista sak att tänka på: välj autentiseringslösning tidigt i projektet och håll fast vid den. Migrering mellan autentiseringssystem är möjlig men kostsam. Det är ett av de tekniska beslut som sitter kvar längst.

Kevin Sommerstein
Kevin SommersteinGrundare, Developly

Medgrundare av Developly Sweden och webbutvecklare med 8 års erfarenhet inom JavaScript, React och Next.js.

LinkedIn →