Implementierung von Authentifizierung in Next.js

Das Verständnis von Authentifizierung ist entscheidend, um die Daten Ihrer Anwendung zu schützen. Diese Seite führt Sie durch die React- und Next.js-Funktionen, die Sie für die Implementierung von Authentifizierung verwenden können.

Bevor Sie beginnen, ist es hilfreich, den Prozess in drei Konzepte zu unterteilen:

  1. Authentifizierung: Überprüft, ob der Benutzer derjenige ist, für den er sich ausgibt. Der Benutzer muss seine Identität mit etwas nachweisen, das er besitzt, wie z.B. einem Benutzernamen und Passwort.
  2. Sitzungsverwaltung: Verfolgt den Authentifizierungsstatus des Benutzers über Anfragen hinweg.
  3. Autorisierung: Entscheidet, auf welche Routen und Daten der Benutzer zugreifen kann.

Dieses Diagramm zeigt den Authentifizierungsfluss mit React- und Next.js-Funktionen:

Diagramm des Authentifizierungsflusses mit React- und Next.js-Funktionen

Die Beispiele auf dieser Seite zeigen aus didaktischen Gründen eine grundlegende Authentifizierung mit Benutzername und Passwort. Obwohl Sie eine eigene Authentifizierungslösung implementieren können, empfehlen wir aus Sicherheits- und Einfachheitsgründen die Verwendung einer Authentifizierungsbibliothek. Diese bieten integrierte Lösungen für Authentifizierung, Sitzungsverwaltung und Autorisierung sowie zusätzliche Funktionen wie Social Logins, Multi-Faktor-Authentifizierung und rollenbasierte Zugriffskontrolle. Eine Liste finden Sie im Abschnitt Authentifizierungsbibliotheken.

Authentifizierung

Hier sind die Schritte zur Implementierung eines Anmelde- und/oder Registrierungsformulars:

  1. Der Benutzer übermittelt seine Anmeldedaten über ein Formular.
  2. Das Formular sendet eine Anfrage, die von einer API-Route verarbeitet wird.
  3. Bei erfolgreicher Überprüfung wird der Prozess abgeschlossen, was die erfolgreiche Authentifizierung des Benutzers anzeigt.
  4. Wenn die Überprüfung fehlschlägt, wird eine Fehlermeldung angezeigt.

Betrachten Sie ein Anmeldeformular, in dem Benutzer ihre Anmeldedaten eingeben können:

import { FormEvent } from 'react'
import { useRouter } from 'next/router'

export default function LoginPage() {
  const router = useRouter()

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    const formData = new FormData(event.currentTarget)
    const email = formData.get('email')
    const password = formData.get('password')

    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    })

    if (response.ok) {
      router.push('/profile')
    } else {
      // Fehler behandeln
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" placeholder="Email" required />
      <input type="password" name="password" placeholder="Passwort" required />
      <button type="submit">Anmelden</button>
    </form>
  )
}

Das obige Formular hat zwei Eingabefelder für die Erfassung der E-Mail und des Passworts des Benutzers. Bei der Übermittlung wird eine Funktion ausgelöst, die eine POST-Anfrage an eine API-Route (/api/auth/login) sendet.

Sie können dann die API Ihres Authentifizierungsanbieters in der API-Route aufrufen, um die Authentifizierung zu behandeln:

import type { NextApiRequest, NextApiResponse } from 'next'
import { signIn } from '@/auth'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const { email, password } = req.body
    await signIn('credentials', { email, password })

    res.status(200).json({ success: true })
  } catch (error) {
    if (error.type === 'CredentialsSignin') {
      res.status(401).json({ error: 'Ungültige Anmeldedaten.' })
    } else {
      res.status(500).json({ error: 'Etwas ist schiefgelaufen.' })
    }
  }
}

Sitzungsverwaltung

Die Sitzungsverwaltung stellt sicher, dass der authentifizierte Zustand des Benutzers über Anfragen hinweg erhalten bleibt. Sie umfasst das Erstellen, Speichern, Aktualisieren und Löschen von Sitzungen oder Token.

Es gibt zwei Arten von Sitzungen:

  1. Zustandslos (Stateless): Sitzungsdaten (oder ein Token) werden in den Cookies des Browsers gespeichert. Der Cookie wird mit jeder Anfrage gesendet, was die Überprüfung der Sitzung auf dem Server ermöglicht. Diese Methode ist einfacher, kann aber weniger sicher sein, wenn sie nicht korrekt implementiert wird.
  2. Datenbank (Database): Sitzungsdaten werden in einer Datenbank gespeichert, wobei der Browser des Benutzers nur die verschlüsselte Sitzungs-ID erhält. Diese Methode ist sicherer, kann aber komplex sein und mehr Serverressourcen verbrauchen.

Gut zu wissen: Während Sie beide Methoden oder beide verwenden können, empfehlen wir die Verwendung einer Sitzungsverwaltungsbibliothek wie iron-session oder Jose.

Zustandslose Sitzungen (Stateless Sessions)

Setzen und Löschen von Cookies

Sie können API-Routen verwenden, um die Sitzung als Cookie auf dem Server zu setzen:

import { serialize } from 'cookie'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encrypt } from '@/app/lib/session'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const sessionData = req.body
  const encryptedSessionData = encrypt(sessionData)

  const cookie = serialize('session', encryptedSessionData, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    maxAge: 60 * 60 * 24 * 7, // Eine Woche
    path: '/',
  })
  res.setHeader('Set-Cookie', cookie)
  res.status(200).json({ message: 'Successfully set cookie!' })
}

Datenbank-Sitzungen

Um Datenbank-Sitzungen zu erstellen und zu verwalten, müssen Sie folgende Schritte durchführen:

  1. Erstellen Sie eine Tabelle in Ihrer Datenbank zur Speicherung von Sitzungen und Daten (oder prüfen Sie, ob Ihre Authentifizierungsbibliothek dies übernimmt).
  2. Implementieren Sie Funktionen zum Einfügen, Aktualisieren und Löschen von Sitzungen
  3. Verschlüsseln Sie die Sitzungs-ID, bevor Sie sie im Browser des Benutzers speichern, und stellen Sie sicher, dass Datenbank und Cookie synchron bleiben (dies ist optional, wird aber für optimistische Authentifizierungsprüfungen in Middleware empfohlen).

Erstellen einer Sitzung auf dem Server:

import db from '../../lib/db'
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const user = req.body
    const sessionId = generateSessionId()
    await db.insertSession({
      sessionId,
      userId: user.id,
      createdAt: new Date(),
    })

    res.status(200).json({ sessionId })
  } catch (error) {
    res.status(500).json({ error: 'Internal Server Error' })
  }
}

Autorisierung

Sobald ein Benutzer authentifiziert ist und eine Sitzung erstellt wurde, können Sie die Autorisierung implementieren, um zu steuern, was der Benutzer in Ihrer Anwendung aufrufen und tun kann.

Es gibt zwei Haupttypen von Autorisierungsprüfungen:

  1. Optimistisch: Prüft, ob der Benutzer berechtigt ist, eine Route aufzurufen oder eine Aktion auszuführen, indem die im Cookie gespeicherten Sitzungsdaten verwendet werden. Diese Prüfungen sind nützlich für schnelle Operationen, wie das Anzeigen/Ausblenden von UI-Elementen oder das Umleiten von Benutzern basierend auf Berechtigungen oder Rollen.
  2. Sicher: Prüft, ob der Benutzer berechtigt ist, eine Route aufzurufen oder eine Aktion auszuführen, indem die in der Datenbank gespeicherten Sitzungsdaten verwendet werden. Diese Prüfungen sind sicherer und werden für Operationen verwendet, die Zugriff auf sensible Daten oder Aktionen erfordern.

Für beide Fälle empfehlen wir:

Optimistische Prüfungen mit Middleware (Optional)

Es gibt Fälle, in denen Sie Middleware verwenden und Benutzer basierend auf Berechtigungen umleiten möchten:

  • Um optimistische Prüfungen durchzuführen. Da Middleware bei jeder Route ausgeführt wird, ist sie eine gute Möglichkeit, Umleitungslogik zu zentralisieren und nicht autorisierte Benutzer vorzufiltern.
  • Um statische Routen zu schützen, die Daten zwischen Benutzern teilen (z.B. Inhalte hinter einer Paywall).

Da Middleware jedoch bei jeder Route ausgeführt wird, einschließlich vorab geladener Routen, ist es wichtig, die Sitzung nur aus dem Cookie zu lesen (optimistische Prüfungen) und Datenbankprüfungen zu vermeiden, um Leistungsprobleme zu verhindern.

Beispiel:

import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'

// 1. Geschützte und öffentliche Routen festlegen
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']

export default async function middleware(req: NextRequest) {
  // 2. Prüfen, ob die aktuelle Route geschützt oder öffentlich ist
  const path = req.nextUrl.pathname
  const isProtectedRoute = protectedRoutes.includes(path)
  const isPublicRoute = publicRoutes.includes(path)

  // 3. Sitzung aus dem Cookie entschlüsseln
  const cookie = (await cookies()).get('session')?.value
  const session = await decrypt(cookie)

  // 4. Zu /login umleiten, wenn der Benutzer nicht authentifiziert ist
  if (isProtectedRoute && !session?.userId) {
    return NextResponse.redirect(new URL('/login', req.nextUrl))
  }

  // 5. Zu /dashboard umleiten, wenn der Benutzer authentifiziert ist
  if (
    isPublicRoute &&
    session?.userId &&
    !req.nextUrl.pathname.startsWith('/dashboard')
  ) {
    return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
  }

  return NextResponse.next()
}

// Routen, auf denen Middleware nicht ausgeführt werden soll
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}

Während Middleware für erste Prüfungen nützlich sein kann, sollte sie nicht Ihre einzige Verteidigungslinie zum Schutz Ihrer Daten sein. Die meisten Sicherheitsprüfungen sollten so nah wie möglich an Ihrer Datenquelle durchgeführt werden, siehe Datenzugriffsschicht für weitere Informationen.

Tipps:

  • In Middleware können Sie Cookies auch mit req.cookies.get('session').value lesen.
  • Middleware verwendet die Edge Runtime, prüfen Sie, ob Ihre Authentifizierungsbibliothek und Ihre Sitzungsverwaltungsbibliothek kompatibel sind.
  • Sie können die Eigenschaft matcher in der Middleware verwenden, um festzulegen, auf welchen Routen Middleware ausgeführt werden soll. Für die Authentifizierung wird jedoch empfohlen, Middleware auf allen Routen auszuführen.

Erstellung einer Data Access Layer (DAL)

Schutz von API-Routen

API-Routen in Next.js sind entscheidend für die Handhabung serverseitiger Logik und Datenverwaltung. Es ist wichtig, diese Routen zu sichern, um sicherzustellen, dass nur autorisierte Benutzer auf bestimmte Funktionalitäten zugreifen können. Dies beinhaltet typischerweise die Überprüfung des Authentifizierungsstatus des Benutzers und seiner rollenbasierten Berechtigungen.

Hier ein Beispiel für die Sicherung einer API-Route:

import { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const session = await getSession(req)

  // Überprüfen, ob der Benutzer authentifiziert ist
  if (!session) {
    res.status(401).json({
      error: 'Benutzer ist nicht authentifiziert',
    })
    return
  }

  // Überprüfen, ob der Benutzer die 'admin'-Rolle hat
  if (session.user.role !== 'admin') {
    res.status(401).json({
      error: 'Nicht autorisierter Zugriff: Benutzer hat keine Admin-Berechtigungen.',
    })
    return
  }

  // Für autorisierte Benutzer mit der Route fortfahren
  // ... Implementierung der API-Route
}

Dieses Beispiel zeigt eine API-Route mit einer zweistufigen Sicherheitsprüfung für Authentifizierung und Autorisierung. Zuerst wird auf eine aktive Sitzung geprüft, und dann wird überprüft, ob der angemeldete Benutzer ein 'admin' ist. Dieser Ansatz gewährleistet einen sicheren Zugriff, der auf authentifizierte und autorisierte Benutzer beschränkt ist, und hält eine robuste Sicherheit für die Anforderungsverarbeitung aufrecht.

Ressourcen

Nachdem Sie nun etwas über Authentifizierung in Next.js gelernt haben, finden Sie hier Next.js-kompatible Bibliotheken und Ressourcen, die Ihnen bei der Implementierung einer sicheren Authentifizierung und Sitzungsverwaltung helfen:

Authentifizierungsbibliotheken

Sitzungsverwaltungsbibliotheken

Weiterführende Literatur

Um weiter über Authentifizierung und Sicherheit zu lernen, lesen Sie die folgenden Ressourcen: