Startseite / Artikel

Google-Anmelden mit Auth.js und Prisma: Die Next.js-Anleitungen zusammenführen

Verbinden Sie die Prisma- und Auth.js-Next.js-Anleitungen zu einer funktionierenden Anwendung mit Google-Anmeldung, persistenten Benutzern sowie Beiträgen, die zur Sitzung gehören, einschließlich der in den Dokumenten übersprungenen Korrekturen.

Verlag: REACTAPP.TOP10 Min. Lesezeit2404 Wörter

Quellen: Prisma + NextAuth setup for NextJS

Um Konten und gespeicherte Daten in eine Next.js-Anwendung hinzuzufügen, ist kein separates Backend erforderlich. Mit Auth.js (dem Nachfolger von NextAuth) für die Authentifizierung, Prisma als ORM sowie einer gehosteten Postgres-Datenbank wie Neon kann dasselbe Next.js-Projekt Anmelden, Sessions und Datenspeicherung verwalten. Jedes dieser Tools bietet eigene, gute Next.js-Anleitungen, doch diese weichen in Bezug auf die Ordnerstruktur voneinander ab, beziehen sich auf veraltete Konventionen und zeigen nie, wie die einzelnen Komponenten miteinander verbunden sind. Diese Schritt-für-Schritt-Anleitung folgt diesen Anleitungen in der richtigen Reihenfolge und weist auf alle notwendigen Anpassungen hin, sodass am Ende eine Anmeldung über Google möglich ist, Benutzer in Postgres gespeichert werden und Beiträge dem angemeldeten Benutzer zugeordnet sind.

Warum Authentifizierung und Daten innerhalb der Next.js-Anwendung belassen?

Eine gängige Konfiguration für ein Produkt, das ursprünglich hauptsächlich als Client-Seitentool konzipiert ist, besteht aus einem separaten Backend – beispielsweise Django mit Postgres sowie einem gehosteten Identitätsanbieter wie Auth0. Das funktioniert zwar, erfordert aber den Einsatz eines weiteren Services, der bereitgestellt, überwacht und bezahlt werden muss. Wenn ein kleines Produkt Funktionen wie gespeicherte Workflows hinzufügen muss, senkt die Wiederverwendung des Next.js-Servers für Authentifizierung und Datenbankzugriff die Hostingkosten sowie die Anzahl der beteiligten Komponenten. Der Nachteil dabei ist, dass man nun für die Integration der drei Bibliotheken verantwortlich ist – genau darum geht es im Rest dieses Artikels.

Diese Pakete ändern sich schnell. Auth.js für Next.js wird noch unter einem Beta-Tag veröffentlicht, und sowohl Prisma als auch Next.js haben in den letzten Major-Versionen ihre Standardeinstellungen geändert. Betrachten Sie die untenstehenden Schritte als Orientierungshilfe und überprüfen Sie die Details immer gegen die aktuelle Dokumentation jedes Projekts.

Prisma ohne zusätzliche gehostete Dienste installieren

Der Prisma-Leitfaden für Next.js ist um Prisma Postgres, die eigene gehostete Datenbank des Unternehmens, herum geschrieben. Wenn Sie eine andere Postgres-Version verwenden möchten, können Sie @prisma/extension-accelerate überspringen – dieses Modul ist nur für diese gehostete Lösung relevant.

Für eine einfache Postgres-Einrichtung benötigen Sie zwei Installationen: die CLI sowie tsx als Entwicklungskomponenten und den Client zusammen mit dotenv als Laufzeitkomponenten. Der Codeausschnitt zeigt beide Befehle in einer Zeile; führen Sie sie nacheinander aus:

npm install prisma tsx --save-dev npm install @prisma/client dotenv

Danach initialisieren Sie Prisma im Projekt:

npx prisma init

Der Leitfaden erwähnt außerdem ein --db-Flag für init. Dieses Flag stellt eine Prisma Postgres-Instanz bereit – lassen Sie es daher weg, wenn Sie Ihre eigene Datenbank verwenden.

Um eine Datenbank lokal ohne Installation von Postgres zum Laufen zu bringen, kann Prisma einen Entwicklungsserver für Sie starten:

npx prisma dev

Der init-Schritt hat bereits eine DATABASE_URL in Ihre .env-Datei geschrieben, die auf diesen lokalen Server verweist. Wenn Sie bereits Postgres lokal ausführen oder auf eine Neon-Branch verweisen möchten, ersetzen Sie diesen Wert durch Ihre eigene Verbindungszeichenkette. Um genauer zu sehen, wie Prisma 7 in einem Next.js 16-Projekt funktioniert – einschließlich der neuen Konfigurationsdatei – sehen Sie unsere Anleitung zur Einrichtung von Prisma 7.

Erstellung der Beispiel-Posts-App

Der Rest des Prisma-Leitfadens erstellt ein kleines Blog mit Benutzern und Beiträgen. Wenn Sie Prisma zu einer bestehenden Anwendung hinzufügen, können Sie direkt zum Abschnitt zur Authentifizierung springen. Es lohnt sich jedoch, den weiteren Schritten zu folgen, da die späteren Schritte davon ausgehen, dass diese Modelle bereits vorhanden sind.

Fangen Sie damit an, die Modelle wie im Schritt zur Skriptstruktur beschrieben in schema.prisma hinzuzufügen, und laden Sie anschließend die Umgebungsvariablen in der Prisma-Konfiguration wie im Schritt zu dotenv ein.

Korrektur des generierten Importpfads für den Client

Beim Initialisieren der Datenbank importiert das vorhandene Seed-Skript direkt aus dem generierten prisma-Verzeichnis, was fehlschlägt. Der generierte Code stellt den Client unter einem Eintrag namens client zur Verfügung, weshalb die erste Importzeile des Seed-Skripts dorthin verweisen muss:

import { PrismaClient, Prisma } from "../app/generated/prisma/client";

Der Alias @/app funktioniert genauso gut wie der relative Pfad ../app.

Dieselbe Korrektur gilt, wenn Sie den gemeinsam genutzten Prisma-Client in lib/prisma.ts einrichten:

import { PrismaClient } from '../app/generated/prisma/client'

Der Leitfaden wechselt außerdem von Schritt zu Schritt zwischen den Konventionen src/app und einfachem app/. Wählen Sie die, die Ihr Projekt tatsächlich verwendet: Bei einem src/-Root ist der Pfad ../src/app/generated; ohne diesen ../app/generated. Die meisten „Module nicht gefunden“-Fehler in dieser Phase entstehen durch das Mischen beider Konventionen.

Sobald der Client vorhanden ist, fügen Sie die von dem Leitfaden beschriebenen Seiten hinzu: die Anfrage zur Startseite, die Liste der Beiträge, die Detailseite eines Beitrags sowie das Formular zur Erstellung neuer Beiträge.

Aufnahme von Auth.js

Es gibt zwei Dokumentationsseiten für diese Bibliothek: die ältere NextAuth-Seite und die neuere Auth.js-Referenz für Next.js. Die Auth.js-Dokumentation beschreibt die aktuelle Version und ist die zu verfolgende Quelle.

Installieren Sie das Paket über seinen Beta-Kanal:

npm install next-auth@beta

Dann führen Sie npx auth secret aus. Dadurch wird eine zufällige AUTH_SECRET erzeugt, die von Auth.js zur Signierung und Verschlüsselung von Tokens sowie Cookies verwendet wird, und diese Werte werden in .env.local geschrieben. Da dieses Projekt seine Variablen bereits in .env speichert, übertragen Sie den Wert dorthin und löschen Sie .env.local, damit es nur eine einzige Konfigurationsquelle gibt. Halten Sie diese Datei außerhalb des Versionskontrollsystems.

Folgen Sie nun den Installationsschritten. Der zentrale Bestandteil ist eine auth.ts-Datei im Projektverzeichnis. Sie ruft einmal NextAuth auf und exportiert vier Hilfsfunktionen, die der Rest der Anwendung verwendet: die HTTP-handlers, signIn, signOut sowie auth, die die aktuelle Sitzung liest:

import NextAuth from "next-auth"

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

Der Route-Handler unter ./app/api/auth/[...nextauth]/route.ts exportiert einfach die GET- und POST-Handler erneut, damit Auth.js seine Anmeldungs-, Callback- und Sitzungsendpunkte bereitstellen kann. Im Beispiel befinden sich Kommentar und Export auf derselben Zeile; in Ihrer Datei sollten Sie den Export in einer eigenen Zeile nach dem Import platzieren:

import { handlers } from "@/auth" // Referring to the auth.ts we just created export const { GET, POST } = handlers

Die Installationsanleitung fügt außerdem eine Datei namens middleware.ts hinzu. Next.js hat seitdem den Begriff „middleware“ in „proxy“ geändert (siehe die Next.js-Anmerkung zur Änderung), sodass die in der Anleitung beschriebene Datei nicht mehr mit dem aktuellen Next.js übereinstimmt. Für diese Einrichtung benötigen Sie sie nicht, da jede geschützte Seite sowie Aktion selbst die Session überprüft. Falls Sie später Schutz auf Anfragebenebene wünschen, müssen Sie die Implementierung an die Proxy-Konvention anpassen.

Anbindung von Google als Anmeldedienstleister

In der Google Cloud Console erstellen Sie einen OAuth-Klienten vom Typ Webanwendung. Legen Sie die autorisierte JavaScript-Quelle auf localhost:3000 und die Redirect-URI auf localhost:3000/api/auth/callback/google fest – dies ist die Callback-Route, die Auth.js für den Google-Anbieter bereitstellt. Bei der Bereitstellung fügen Sie bitte auch die Produktions-Quelle sowie den Callback hinzu. Speichern Sie die von Google bereitgestellten Client-ID und -Secret in Ihrer Umgebungsdatei unter den von den Auth.js-Dokumentationen angegebenen Variablennamen, niemals im Quellcode.

Anschließend fügt die OAuth-Anleitung ein Anmeldungskomponente hinzu. Es handelt sich dabei um ein Formular, dessen Action eine eingebettete Serveraction ist, die signIn aufruft:

import { signIn } from "@/auth"

export function SignIn() {
  return (
    <form
      action={async () => {
        "use server"
        await signIn()
      }}
    >
      <button type="submit">Sign in</button>
    </form>
  )
}

Wird ohne Argumente aufgerufen, sendet signIn() den Benutzer zur integrierten Seite, die alle konfigurierten Anbieter auflistet. Wenn nur Google konfiguriert ist, überspringt das Übergeben von "google" diesen Zwischenschirm.

Ein entsprechendes Anmeldungs-Ausloggen-Komponente aus den Dokumenten zur Sitzungskontrolle hat die gleiche Struktur:

import { signOut } from "@/auth"

export function SignOut() {
  return (
    <form
      action={async () => {
        "use server"
        await signOut()
      }}
    >
      <button type="submit">Sign Out</button>
    </form>
  )
}

Platzierung der Schaltflächen in einer Navigationsleiste

In den Dokumenten wird nicht genau angegeben, wohin diese Komponenten gehören. Ein natürlicher Ort ist eine Navigationsleiste, die selbst ein asynchroner Server Component ist: Sie ruft auth() auf und rendernt SignOut, wenn sich ein Benutzer befindet, und SignIn ansonsten.

import Link from "next/link";
import { auth } from "../auth"
import SignIn from "./sign-in";
import SignOut from "./sign-out";

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

    return (
        <>
            <nav className="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200">
                <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                    <div className="flex items-center h-16">
                        {/* Logo */}
                        <Link href="/" className="flex items-center space-x-2">
                        <span className="text-xl font-semibold text-[#369b6f]">
                            Prisma + NextAuth + Neon
                        </span>
                        </Link>

                        {/* Spacer */}
                        <div className="flex-1"></div>

                        {/* Navigation Links */}
                        <div className="hidden md:flex items-center mr-8">
                        <Link
                            href="/posts"
                            className="text-gray-700 font-medium hover:text-[#369b6f] transition-all duration-200"
                        >
                            Posts
                        </Link>
                        </div>

                        {/* Auth Buttons */}
                        <div className="flex items-center space-x-2 sm:space-x-4">
                        {session?.user ? (
                            <SignOut/>
                        ) : (
                            <SignIn/>
                        )}
                        </div>
                    </div>
                </div>
            </nav>
        </>
    );
}

Achten Sie auf die Imports hier: Diese Datei verwendet Standardimports für SignIn und SignOut, während die oben genannten Komponenten benannte Exports verwenden. Ändern Sie entweder die Imports zu import { SignIn } from "./sign-in" oder exportieren Sie die Komponenten als Standardexports – andernfalls fehlschlägt der Build.

Rendern Sie anschließend die Navigation im Root-Layout, damit sie auf jeder Seite angezeigt wird:

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
          <Navigation/>
          <main>
            {children}
          </main>
      </body>
    </html>
  );
}

Vergessen Sie nicht, Navigation an den Anfang von app/layout.tsx einzufügen. Nachdem der Entwicklungsserver neu geladen wurde, erscheint über jeder Seite ein Balken mit einem Anmelden- oder Abmelden-Button. Beachten Sie, dass beide Buttons auf inline Server Actions angewiesen sind und daher nur innerhalb von Server Components funktionieren. Ein Client Component kann keine Serverfunktionen inline definieren; in diesem Fall sollten Sie die Aktionen in eine separate Datei mit dem Hinweis "use server" verschieben und diese importieren.

Benutzer mit dem Prisma-Adapter speichern

Bis jetzt funktioniert das Anmelden, doch im Datenbank wird nichts über den Benutzer gespeichert. Der Prisma-Adapter verbindet Auth.js mit Ihrem Prisma-Client, sodass Benutzer sowie mit OAuth verknüpfte Konten und Sessions in Postgres geschrieben werden. Installieren Sie ihn:

npm install @auth/prisma-adapter

Dann aktualisieren Sie auth.ts, um den Adapter mit Ihrem gemeinsamen Prisma-Client zu übergeben und den Google-Anbieter zu registrieren:

import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { PrismaAdapter } from "@auth/prisma-adapter";
import prisma from "@/lib/prisma";

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

Mit einem konfigurierten Adapter speichert Auth.js Standardmäßig Sessions in der Datenbank anstelle in einem signierten Cookie, was es ermöglicht, Sessions serverseitig zu widerrufen – mit dem Nachteil einer Datenbankabfrage bei jeder Session-Überprüfung. Der Google-Anbieter lädt seine Anmeldeinformationen automatisch aus Umgebungsvariablen herunter, sofern diese der Namenskonvention von Auth.js entsprechen.

Ausgleich des Benutzermodells

Der Adapter benötigt bestimmte Tabellen und Spalten, die im Prisma-Adapter-Schema aufgeführt sind. Kopieren Sie diese in schema.prisma. Dies führt zu Konflikten mit der Beispielanwendung: Die Prisma-Anleitung hat ein User-Modell mit eigenem ID-Typ erstellt, während Auth.js eine Zeichenketten-ID sowie mehrere zusätzliche Felder erwartet. Für ein neues Beispielprojekt ist der einfachste Weg, das Modell zu ersetzen und die Datenbank von Grund auf neu aufzubauen. Wenn Sie eine bereits vorhandene Anwendung mit echten Benutzern integrieren, setzen Sie nichts zurück – planen Sie stattdessen eine ordnungsgemäße Migration, die die bestehenden Benutzer auf die neue Struktur abbildet. Das ist ein separates Problem, das außerhalb des Umfangs dieser Anleitung liegt.

Um sauber neu aufzubauen, stoppen Sie zunächst die erneute Ausführung des Seed-Prozesses, da die Seed-Daten auf die alte Benutzerstruktur verweisen. Löschen Sie den Seed-Eintrag in prisma.config.ts:

seed: `tsx prisma/seed.ts`

Dann wird die Datenbank zurückgesetzt, wodurch alle Tabellen und Daten gelöscht werden:

npx prisma migrate reset

Führen Sie dies nur an einer Entwicklungsdatenbank aus. Schließlich sollte eine Migration für das neue Schema erstellt und angewendet werden:

npx prisma migrate dev

Starten Sie die Anwendung erneut (npm run dev) und melden Sie sich über Google an. In der User-Tabelle sollte eine neue Zeile erscheinen, zusammen mit einer entsprechenden Zeile in der Account-Tabelle für den Google-Link. Um die Zeilen zu überprüfen, starten Sie npx prisma studio, den mit Prisma gelieferten visuellen Datenbankbrowser.

Bezug von Beiträgen auf den angemeldeten Benutzer

Das Beispielformular aus der Prisma-Anleitung erstellt Beiträge mit einem fest kodierten authorId. Da nun Sessions vorhanden sind, kann die Seite diese lesen und stattdessen die echte Benutzer-ID verwenden. Die untenstehende Seite ruft auth() auf, zeigt anonymen Besuchern eine Nachricht an und übermittelt bei der Erstellung des Beitrags session.user.id an Prisma. Anschließend wird die Liste der Beiträge erneut validiert und auf diese umgeleitet:

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

  async function createPost(formData: FormData) {
    "use server";

    if(!session?.user?.id){
      return;
    }

    const title = formData.get("title") as string;
    const content = formData.get("content") as string;

    await prisma.post.create({
      data: {
        title,
        content,
        authorId: session.user.id,
      },
    });

    revalidatePath("/posts");
    redirect("/posts");
  }

  if(!session?.user){
    return <div>You need to sign in to create a post</div>
  }

  ...

}

Eine Verbesserung, die vor dem Einsatz in der Produktion vorgenommen werden sollte, betrifft die Art und Weise, wie session über eine Closure aus der Seitenrenderung abgerufen wird. Eine Server Action ist ein öffentlicher Endpunkt, der direkt aufgerufen werden kann; daher ist das sichere Vorgehen, auth() im Funktionskörper der Action aufzurufen und dort das Ergebnis zu überprüfen, sodass die Überprüfung bei jeder Abgabe durchgeführt wird. Wir erklären, warum dies notwendig ist, in warum Server Actions eine Autorisierungsprüfung im Funktionskörper jeder Funktion benötigen. Es ist außerdem ratsam, title und content zu validieren, anstatt sie mit as string zu typisieren.

Zusammenfassung

  • Befolgen Sie die offiziellen Anleitungen von Prisma und Auth.js, korrigieren Sie jedoch den generierten Client-Import so, dass er auf generated/prisma/client verweist, und bleiben Sie konsequent bei der Nutzung von src/app statt app/.
  • Legen Sie alle Geheimnisse und Verbindungsstrings in eine Umgebungsdatei ab, die niemals kommittet wird.
  • Überspringen Sie das dokumentierte middleware.ts; der aktuelle Next.js verwendet die Proxy-Konvention, und hier reichen auth()-Prüfungen auf Seiteebene aus.
  • Durch Hinzufügen des Prisma-Adapters ändert sich die erforderliche Struktur von User. Für eine Demo ist ein Reset in Ordnung, doch vorhandene Daten erfordern eine echte Migration.
  • Überprüfen Sie die Session in jeder Server Action, nicht nur auf der Seite, die das Formular rendernt.

Von hier aus sind die natürlichen nächsten Schritte das Anmelden über Client Components sowie die Einrichtung von DATABASE_URL auf eine gehostete Postgres-Instanz für die Bereitstellung.

Verwandte Artikel

  • Architektur eines AI-Chat-SaaS mit Next.js, FastAPI, Credits und SSE — Einen vollständigen Stack für eine AI-Chat-Architektur mit einem FastAPI-Gatekeeper, einem Kreditbuch, Redis-Rate-Limits sowie SSE-Streaming vorstellen und die ersten zu schließenden Lücken aufzeigen.
  • Blockierung von nicht zugestellten Registrierungs-E-Mails mit einem Supabase Auth Hook und DNS — Verwendung eines vor der Benutzererstellung ausgelösten Supabase-Hooks sowie von MX-, A- und AAAA-Lookups, um Registrierungen abzulehnen, deren E-Mail-Domain keine Mails empfangen kann, gemäß den Regeln in RFC 5321.