In den letzten Jahren hat sich das Hinzufügen von Authentifizierung zu deiner Anwendung von etwas Obskurem und Kompliziertem zu etwas entwickelt, für das du buchstäblich einfach eine API verwenden kannst.

Es gibt eine Vielzahl von Beispielen und Tutorials, die zeigen, wie man bestimmte Authentifizierungsverfahren in Next.js implementiert, aber wenige über das Warum, welche Verfahren, Tools und Kompromisse man wählt.

In diesem Beitrag erfährst du, was du bei der Authentifizierung in Next.js beachten solltest, von der Auswahl eines Anbieters über die Erstellung von Routen für die Anmeldung bis hin zur Entscheidung zwischen Server- und Client-Seite.

Auswahl einer Authentifizierungsmethode / eines Anbieters

Es gibt praktisch 1.000 Möglichkeiten, die Authentifizierung in deine Anwendung einzubauen. Anstatt uns hier auf bestimmte Anbieter zu konzentrieren (ein Thema für einen anderen Blog-Beitrag), wollen wir uns die Arten von Authentifizierungslösungen und ein paar Beispiele dafür ansehen. Was die Implementierung angeht, so wird next-auth schnell zu einer beliebten Option, um deine Next.js-Anwendung mit mehreren Anbietern zu integrieren, SSO hinzuzufügen usw.

Traditionelle Datenbank

Diese Variante ist so einfach wie nur möglich: Du speicherst Benutzernamen und Passwörter in einer relationalen Datenbank. Wenn sich ein Nutzer zum ersten Mal anmeldet, fügst du eine neue Zeile in die Tabelle „Nutzer“ mit den angegebenen Informationen ein. Wenn er sich anmeldet, vergleichst du die Anmeldedaten mit denen, die du in der Tabelle gespeichert hast. Wenn ein Benutzer sein Passwort ändern möchte, aktualisierst du den Wert in der Tabelle.

Die traditionelle Datenbankauthentifizierung ist sicherlich das beliebteste Authentifizierungsverfahren, wenn man sich die Gesamtheit der bestehenden Anwendungen ansieht. Es ist sehr flexibel, billig und bindet dich nicht an einen bestimmten Anbieter. Aber du musst es selbst entwickeln und dich vor allem um die Verschlüsselung kümmern und sicherstellen, dass die Passwörter nicht in die falschen Hände geraten.

Authentifizierungslösungen deines Datenbankanbieters

In den letzten Jahren (und, dank Firebase, schon seit einigen Jahren) ist es für Anbieter von verwalteten Datenbanken relativ normal geworden, eine Art verwaltete Authentifizierungslösung anzubieten. Firebase, Supabase und AWS bieten sowohl verwaltete Datenbanken als auch verwaltete Authentifizierung als Service über eine Reihe von APIs an, die die Benutzererstellung und Sitzungsverwaltung einfach abstrahieren (mehr dazu später).

Die Anmeldung eines Benutzers mit der Supabase-Authentifizierung ist so einfach wie hier dargestellt:

async function signInWithEmail() {
  const { data, error } = await supabase.auth.signInWithPassword({
    email: '[email protected]',
    password: 'example-password',
  })
}

Authentifizierungslösungen, die nicht von deinem Datenbankanbieter stammen

Vielleicht noch verbreiteter als die Authentifizierung als Dienst deines DBaaS ist die Authentifizierung als Dienst eines ganzen Unternehmens oder Produkts. Auth0 gibt es seit 2013 (jetzt im Besitz von Okta), und die jüngsten Ergänzungen wie Stytch haben die Erfahrung der Entwickler in den Vordergrund gestellt und sich dadurch einen Namen gemacht.

Die Benutzeroberfläche, die du erhältst, wenn du Auth0 für die Authentifizierung verwendest
Auth0 für die Authentifizierung

Single Sign On

Mit SSO kannst du deine Identität an einen externen Anbieter „auslagern“. Das kann ein unternehmensorientierter, sicherheitsorientierter Anbieter wie Okta sein, aber auch ein weit verbreiteter Anbieter wie Google oder GitHub. Google SSO ist in der SaaS-Welt allgegenwärtig, und einige auf Entwickler ausgerichtete Tools authentifizieren sich nur über GitHub.

Welchen Anbieter du auch immer wählst, SSO ist in der Regel ein Zusatz zu den anderen oben genannten Authentifizierungsarten und hat seine eigenen Eigenheiten, wenn es um die Integration mit externen Plattformen geht (sei gewarnt: SAML verwendet XML).

Okay, welche ist die richtige für mich?

Es gibt hier keine „richtige“ Wahl – was für dein Projekt das Richtige ist, hängt von deinen Prioritäten ab. Wenn du schnell und ohne großen Konfigurationsaufwand loslegen willst, macht es Sinn, die Authentifizierung auszulagern (oder sie sogar komplett, einschließlich der Benutzeroberfläche, an jemanden wie Auth0 zu übertragen). Wenn du mit einer komplexeren Einrichtung rechnest, ist es sinnvoll, dein eigenes Autorisierungs-Backend zu bauen. Und wenn du vorhast, größere Kunden zu unterstützen, wirst du irgendwann SSO einführen müssen.

Next.js ist inzwischen so populär, dass die meisten dieser Authentifizierungsanbieter über Next.js-spezifische Dokumente und Integrationsanleitungen verfügen.

Erstellen von Routen für die Anmeldung und den Sign-In und Tipps für die zusätzliche Authentifizierungsmeile

Einige Authentifizierungsanbieter wie Auth0 bieten sogar ganze gehostete Webseiten für die Anmeldung und den Sign-In an. Wenn du diese jedoch von Grund auf neu erstellst, ist es sinnvoll, sie schon früh im Prozess zu erstellen, da du sie als Weiterleitungen brauchst, wenn du deine Authentifizierung tatsächlich implementierst.

Es ist also sinnvoll, die Struktur für diese Seiten zu erstellen und dann die Anfragen im Backend hinzuzufügen. Die einfachste Art, die Autorisierung zu implementieren, ist, zwei dieser Routen zu haben:

  • Eine für die Erstanmeldung
  • Eine weitere für die Anmeldung, wenn der Nutzer bereits ein Konto hat

Neben den Grundlagen musst du auch Sonderfälle abdecken, z. B. wenn ein Nutzer sein Passwort vergisst. Manche Teams bevorzugen es, den Passwort-Rücksetzungsprozess auf einer separaten Route durchzuführen, während andere dynamische UI-Elemente zur regulären Anmeldeseite hinzufügen.

Eine schöne Anmeldeseite entscheidet vielleicht nicht über Erfolg oder Misserfolg, aber kleine Details können einen guten Eindruck hinterlassen und insgesamt für eine bessere UX sorgen. Hier sind ein paar Beispiele von Websites aus dem Internet, die ihre Authentifizierungsprozesse mit ein bisschen mehr Liebe gestaltet haben.

1. Aktualisiere deine Navigationsleiste, wenn es eine aktive Sitzung gibt

Der Aufruf zum Handeln in der Navigationsleiste von Stripe ändert sich, je nachdem, ob du eine authentifizierte Sitzung hast oder nicht. So sieht die Marketingseite aus, wenn du nicht authentifiziert bist. Beachte die Handlungsaufforderung, dich anzumelden:

Stripe Startseite
Auf der Startseite von Stripe ändert sich die CTA, je nachdem, ob du angemeldet bist oder nicht

Und so sieht es aus, wenn du authentifiziert bist. Beachte, dass sich die Handlungsaufforderung so ändert, dass der Nutzer zu seinem Dashboard geleitet wird, anstatt sich anzumelden:

Änderungen auf der Stripe-Startseite
Änderungen auf der Stripe-Startseite

Das ändert mein Stripe-Erlebnis zwar nicht grundlegend, aber es ist durchdacht.Ein interessanter technischer Nebenaspekt: Es gibt einen guten Grund, warum die meisten Unternehmen die Navigationsleiste auf ihrer Marketingseite nicht von der Authentifizierung „abhängig“ machen – es würde eine zusätzliche API-Anfrage bedeuten, um bei jedem einzelnen Seitenaufruf zu prüfen, ob die Besucher authentifiziert sind, und die meisten davon sind es wahrscheinlich nicht.

2. Füge hilfreiche Inhalte neben dem Anmeldeformular hinzu

In den letzten Jahren haben Unternehmen, vor allem im SaaS-Bereich, damit begonnen, der Anmeldeseite Inhalte hinzuzufügen, die den Nutzer dazu „ermutigen“, die Anmeldung tatsächlich abzuschließen. Das kann dazu beitragen, die Konversion auf der Seite zu verbessern, zumindest schrittweise.

Hier ist eine Anmeldeseite von Retool, mit einer Animation und einigen Logos an der Seite:

Retool Anmeldeseite
Wenn du das machst, solltest du darauf achten, dass die Schriftarten auf beiden Seiten gleich sind

Wir machen das auch bei Kinsta für unsere Anmeldeseite:

MyKinsta Anmeldeseite
Kinsta-Anmeldeseite

Der kleine zusätzliche Inhalt kann helfen, den Nutzer daran zu erinnern, wofür er sich anmeldet und warum er es braucht.

3. Wenn du ein Passwort verwendest: Schlage ein starkes Passwort vor oder erzwinge es

Unter Entwicklern ist es allgemein bekannt, dass Passwörter von Natur aus unsicher sind, aber nicht bei allen Menschen, die sich für dein Produkt anmelden werden. Wenn du deine Nutzer/innen dazu ermutigst, sichere Passwörter zu erstellen, ist das gut für dich und gut für sie.

Coinbase ist bei der Anmeldung ziemlich streng und verlangt von dir ein sicheres Passwort, das komplizierter ist als dein Vorname:

Coinbase Konto Erstellung
Schwaches Passwort bei Coinbase

Nachdem ich stattdessen eines mit meinem Passwortmanager erstellt hatte, konnte ich loslegen:

Coinbase Passwort-Stärke
Starkes Passwort bei Coinbase

Die Benutzeroberfläche sagte mir allerdings nicht, warum das Passwort nicht sicher genug war, und auch nicht, dass es nicht nur eine Zahl sein musste. Wenn du das in deinen Produkttext aufnimmst, wird es für deine Nutzer einfacher und du vermeidest frustrierende Passwortwiederholungen.

4. Kennzeichne deine Eingaben so, dass sie mit einem Passwortmanager gut zusammenspielen

Jeder dritte Amerikaner nutzt einen Passwortmanager wie 1Password, und trotzdem ignorieren viele Formulare im Internet weiterhin das „type=“ in HTML-Eingaben. Sorge dafür, dass deine Formulare mit Passwortmanagern zusammenarbeiten:

  • Schließe deine Eingabeelemente in ein Formularelement ein
  • Gib den Eingaben einen Typ und eine Bezeichnung
  • Füge deinen Eingaben Autovervollständigungsfunktionen hinzu
  • Füge keine Felder dynamisch hinzu (ich schaue dich an, Delta)

Das kann den Unterschied zwischen einer 10-sekündigen, reibungslosen Anmeldung und einer nervigen manuellen Anmeldung ausmachen, vor allem auf dem Handy.

Die Wahl zwischen Sessions und JWT

Sobald sich dein/e Nutzer/in authentifiziert hat, musst du dich für eine Strategie entscheiden, wie du diesen Status bei nachfolgenden Anfragen beibehalten willst. HTTP ist zustandslos und wir wollen unseren Nutzer sicher nicht bei jeder einzelnen Anfrage nach seinem Passwort fragen. Es gibt zwei gängige Methoden, um dies zu bewerkstelligenSessions (oder Cookies) und JWTs (JSON Web Tokens) – und sie unterscheiden sich darin, ob der Server oder der Client die Arbeit übernimmt.

Sitzungen, auch bekannt als Cookies

Bei der sitzungsbasierten Authentifizierung werden die Logik und die Arbeit zur Aufrechterhaltung der Authentifizierung vom Server übernommen. Hier ist der grundlegende Ablauf:

  1. Der Nutzer authentifiziert sich auf der Anmeldeseite.
  2. Der Server erstellt einen Datensatz, der diese spezielle „Sitzung“ repräsentiert Dieser wird in der Regel in eine Datenbank mit einer zufälligen Kennung und Details über die Sitzung eingefügt, z. B. wann sie begonnen hat und wann sie abläuft.
  3. Diese zufällige Kennung – etwa „6982e583b1874abf9078e1d1dd5442f1“ – wird an deinen Browser gesendet und als Cookie gespeichert.
  4. Bei nachfolgenden Anfragen des Clients wird die Kennung in die Sitzungstabelle in der Datenbank aufgenommen und überprüft.

Es ist ziemlich einfach und kann angepasst werden, wenn es darum geht, wie lange Sitzungen dauern, wann sie widerrufen werden sollen usw. Der Nachteil ist die Latenz, die durch die vielen Schreib- und Lesevorgänge in der Datenbank entsteht, aber das dürfte für die meisten Leser keine große Rolle spielen.

JSON Web Token (JWT)

Anstatt die Authentifizierung für nachfolgende Anfragen auf dem Server zu erledigen, kannst du sie mit JWTs (größtenteils) auf der Client-Seite erledigen. So funktioniert es:

  1. Der Nutzer authentifiziert sich über die Anmeldeseite.
  2. Der Server erstellt ein JWT, das die Identität des Nutzers, die ihm gewährten Rechte und ein Ablaufdatum enthält (neben möglichen anderen Dingen).
  3. Der Server signiert dieses Token, verschlüsselt den Inhalt kryptografisch und sendet das Ganze an den Kunden.
  4. Bei jeder Anfrage kann der Client das Token entschlüsseln und überprüfen, ob der Nutzer die Erlaubnis hat, die Anfrage zu stellen (ohne mit dem Server kommunizieren zu müssen).

Da die gesamte Arbeit nach der anfänglichen Authentifizierung auf den Client verlagert wird, kann deine Anwendung viel schneller laden und arbeiten. Es gibt jedoch ein Hauptproblem: Es gibt keine Möglichkeit, ein JWT vom Server aus ungültig zu machen. Wenn sich der Nutzer von einem Gerät abmelden möchte oder sich der Umfang seiner Berechtigung ändert, musst du warten, bis das JWT abläuft.

Die Wahl zwischen serverseitiger und clientseitiger Autorisierung

Was Next.js so großartig macht, ist das eingebaute statische Rendering – wenn deine Seite statisch ist, d.h. keine externen API-Aufrufe tätigen muss, wird sie von Next.js automatisch zwischengespeichert und kann über ein CDN extrem schnell ausgeliefert werden. Vor Next.js 13 erkennt Next.js, ob eine Seite statisch ist, wenn du keine `getServerSideProps` oder `getInitialProps` in die Datei einfügst. Alle Versionen nach Next.js 13 verwenden stattdessen React Server Components, um dies zu tun.

Bei der Authentifizierung hast du die Wahl: Entweder du renderst eine statische „Lade“-Seite und erledigst die Abfrage clientseitig oder du machst alles serverseitig. Für Seiten, die eine Authentifizierung erfordern [1], kannst du ein statisches „Skelett“ rendern und dann Authentifizierungsanfragen auf der Client-Seite stellen. Theoretisch bedeutet das, dass die Seite schneller geladen wird, auch wenn der ursprüngliche Inhalt noch nicht vollständig fertig ist.

Hier ist ein vereinfachtes Beispiel aus den Dokumenten, das einen Ladezustand rendert, solange das Benutzerobjekt noch nicht fertig ist:

import useUser from '../lib/useUser'
 
const Profile = () => {
  // Fetch the user client-side
  const { user } = useUser({ redirectTo: '/login' })
 
  // Server-render loading state
  if (!user || user.isLoggedIn === false) {
    // Build some sort of loading page here
    return <div>Loading...</div>
  }
 
  // Once the user request finishes, show the user
  return (
    <div>
      <h1>Your Account</h1>
      <p>Username: {JSON.stringify(user.username,null)}</p>
      <p>Email: {JSON.stringify(user.email,null)}</p>
      <p>Address: {JSON.stringify(user.address,null)}</p>
    </div>
  )
}
 
export default Profile

Beachte, dass du hier eine Art Lade-UI bauen musst, um Platz zu schaffen, während der Client nach dem Laden Anfragen stellt.

Wenn du die Dinge vereinfachen und die Authentifizierung serverseitig ausführen möchtest, kannst du deine Authentifizierungsanfrage in die Funktion „getServerSideProps“ einfügen. Anstelle der bedingten Logik im obigen Snippet könntest du etwas Einfacheres wie diese vereinfachte Version aus den Next-Dokumenten verwenden:

import withSession from '../lib/session'
 
export const getServerSideProps = withSession(async function ({ req, res }) {
  const { user } = req.session
 
  if (!user) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    }
  }
 
  return {
    props: { user },
  }
})
 
const Profile = ({ user }) => {
  // Show the user. No loading state is required
  return (
    <div>
      <h1>Your Account</h1>
      <p>Username: {JSON.stringify(user.username,null)}</p>
      <p>Email: {JSON.stringify(user.email,null)}</p>
      <p>Address: {JSON.stringify(user.address,null)}</p>
    </div>
  )
}
 
export default Profile

Hier gibt es immer noch eine Logik für den Fall, dass die Authentifizierung fehlschlägt, aber es wird zur Anmeldung weitergeleitet, anstatt einen Ladezustand zu rendern.

Zusammenfassung

Welche dieser Möglichkeiten ist also die richtige für dein Projekt? Überprüfe zunächst, wie sicher du dir bist, dass dein Authentifizierungsverfahren schnell funktioniert. Wenn deine Anfragen keine Zeit in Anspruch nehmen, kannst du sie serverseitig ausführen und den Ladestatus vermeiden. Wenn du es vorziehst, etwas sofort zu rendern und dann auf die Anfrage zu warten, überspringe `getServerSideProps` und führe die Authentifizierung anderswo durch.

[1] Wenn du Next verwendest, ist das ein guter Grund, die Authentifizierung nicht pauschal für jede einzelne Seite zu verlangen. Das ist zwar einfacher, führt aber dazu, dass du die Leistungsvorteile des Web-Frameworks von vornherein verpasst.

Justin Gage

Justin is a technical writer and author of the popular Technically newsletter. He did his B.S. in Data Science before a stint in full-stack engineering and now focuses on making complex technical concepts accessible to everyone.