Social Login mit Claude Code implementieren: Next.js, Auth.js, Google und GitHub OAuth
Praxisleitfaden für sicheren Social Login mit Claude Code, Next.js/Auth.js, Google/GitHub OAuth, Code und Produktionsfallen.
Social Login ist mehr als ein kürzeres Registrierungsformular. Sobald Nutzer über Google oder GitHub OAuth einsteigen, entwirfst du auch Identität, Account Linking, Sessions, Cookies, CSRF-Schutz, Provider-Scopes, Audit-Logs und Support-Prozesse. Wenn Claude Code nur den Auftrag “Login einbauen” bekommt, entsteht oft ein funktionierender Button, aber es bleiben Risiken: falsche redirect URI, automatische Verknüpfung über ungeprüfte E-Mails, zu breite Scopes oder client secrets im Code.
Dieser Leitfaden nutzt Next.js App Router, Auth.js im Stil von NextAuth v5, TypeScript und Prisma Adapter. Der OAuth authorization code ist ein kurzlebiger Code, den der Server gegen Tokens eintauscht. state ist ein Zufallswert gegen CSRF. Die redirect URI ist die exakte URL, an die Google oder GitHub den Browser zurückschickt. Account linking bedeutet, dass ein bereits angemeldeter Nutzer bewusst einen weiteren Provider mit demselben Konto verbindet.
Erst die Sicherheitsgrenze festlegen
Die erste Frage lautet nicht “wie viele Provider?”, sondern “was soll der Login beweisen?”. Für SaaS, Schulungsseiten, interne Tools und Entwicklerprodukte reichen Google und GitHub häufig aus. Google passt zu allgemeinen Nutzern und Firmenadressen. GitHub passt zu Entwickler-Workflows. Google Drive, Calendar oder GitHub repo schon beim Login anzufordern, senkt Vertrauen und Conversion.
Gib Claude Code kleine, prüfbare Aufgaben:
- Auth.js Provider, Umgebungsvariablen und Callback-Route hinzufügen.
- Login-Seite und geschützte Seite erstellen.
- In der Session nur
user.idergänzen, keine Access Tokens. - Account linking nur aus einer Settings-Seite mit angemeldetem Nutzer erlauben.
- Löschen der letzten Login-Methode verhindern.
- Client secrets, refresh tokens und access tokens niemals in Code, Logs, Issues oder Entwürfe schreiben.
In Masas Testprojekt lag der größte Aufwand nicht im generierten Code, sondern in einer kleinen Abweichung zwischen Google Cloud Console redirect URI undAUTH_URL. Claude Code kann das Repository ändern, aber externe Konsolenwerte nicht sicher kennen. Deshalb müssen URL, Scope, E-Mail-Verifikation und Cookie-Regeln vorab feststehen.
OAuth-Ablauf als Diagramm
Google empfiehlt für Web-Apps den authorization code flow. Der Browser erhält einen temporärencode; der Server tauscht ihn mit dem Provider aus. Das client secret gehört nie in Browser-Code.
sequenceDiagram
participant User as User
participant App as Next.js app
participant Auth as Auth.js route
participant Provider as Google or GitHub
User->>App: Click "Continue with Google"
App->>Auth: signIn("google")
Auth->>Provider: Redirect with client_id, redirect_uri, scope, state
Provider->>User: Consent screen
Provider->>Auth: Redirect back with code and state
Auth->>Provider: Exchange code on the server
Provider->>Auth: Return tokens
Auth->>App: Create session cookie
App->>User: Show dashboard
state bestätigt, dass der Callback zu der gestarteten Login-Anfrage gehört. Auth.js übernimmt das bei Standardrouten. Wenn Claude Code eigene Callback-Logik schreibt, muss state zwingend validiert werden. Auch GitHub empfiehlt einen nicht erratbaren Wert und das Abbrechen bei Abweichung.
Umgebungsvariablen und minimale Scopes
Diese Konfiguration dient nur dem Login. Google nutztopenid email profile; GitHub nutztread:user user:email.
npm install next-auth@beta @auth/prisma-adapter prisma @prisma/client
npx prisma init
npm exec auth secret
# .env.local
AUTH_SECRET="Wert aus npm exec auth secret"
AUTH_URL="http://localhost:3000"
AUTH_GOOGLE_ID="client ID aus Google Cloud Console"
AUTH_GOOGLE_SECRET="client secret aus Google Cloud Console"
AUTH_GITHUB_ID="client ID der GitHub OAuth App"
AUTH_GITHUB_SECRET="client secret der GitHub OAuth App"
DATABASE_URL="postgresql://user:password@localhost:5432/app"
Echte Werte bleiben in Umgebungsvariablen oder einem Secret Manager. Das Beispiel enthält nur leere Schlüssel.
# .env.example
AUTH_SECRET=
AUTH_URL=
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=
DATABASE_URL=
Für Google lautet die lokale redirect URIhttp://localhost:3000/api/auth/callback/google, in Produktionhttps://example.com/api/auth/callback/google. Für GitHub nutzehttps://example.com/api/auth/callback/github. Protokoll, Domain, Pfad und Provider müssen exakt stimmen.
Auth.js-Konfiguration
Diese Konfiguration lehnt Google-Logins ohneemail_verified ab und akzeptiert GitHub nur, wenn eine E-Mail verfügbar ist. Provider-Tokens werden nicht in die Browser-Session gelegt.
// auth.ts
import NextAuth, { type NextAuthConfig } from "next-auth";
import Google from "next-auth/providers/google";
import GitHub from "next-auth/providers/github";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
type GoogleProfile = {
sub: string;
name?: string;
email: string;
email_verified: boolean;
picture?: string;
};
export const authConfig = {
adapter: PrismaAdapter(prisma),
session: { strategy: "database" },
providers: [
Google({
authorization: {
params: {
scope: "openid email profile",
response_type: "code",
},
},
profile(profile: GoogleProfile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
emailVerified: profile.email_verified ? new Date() : null,
};
},
}),
GitHub({
authorization: {
params: {
scope: "read:user user:email",
},
},
}),
],
callbacks: {
async signIn({ account, profile, user }) {
if (account?.provider === "google") {
const googleProfile = profile as GoogleProfile | undefined;
return Boolean(googleProfile?.email && googleProfile.email_verified);
}
if (account?.provider === "github") {
return Boolean(user.email);
}
return true;
},
async session({ session, user }) {
session.user.id = user.id;
return session;
},
},
pages: {
signIn: "/login",
error: "/login",
},
} satisfies NextAuthConfig;
export const { handlers, auth, signIn, signOut } = NextAuth(authConfig);
// src/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;
};
}
}
// src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
Mit Prisma Adapter sollte Claude Code auch die StandardmodelleUser, Account, Session undVerificationToken anlegen. DieAccount-Tabelle ist die Grundlage für nachvollziehbares Account linking.
Login-Seite und geschützte Seite
Der Aufruf vonsignIn aus einer Server Action hält den Ablauf im Auth.js-Standardflow. Das ist robuster als manuell zusammengesetzte Provider-URLs.
// src/app/login/page.tsx
import { signIn } from "@/auth";
const providers = [
{ id: "google", label: "Mit Google fortfahren" },
{ id: "github", label: "Mit GitHub fortfahren" },
] as const;
export default function LoginPage({
searchParams,
}: {
searchParams: { error?: string };
}) {
return (
<main className="mx-auto flex min-h-screen max-w-sm flex-col justify-center gap-6 px-6">
<div>
<h1 className="text-2xl font-bold">Anmelden</h1>
<p className="mt-2 text-sm text-gray-600">
Wähle das Arbeitskonto für diese Anwendung.
</p>
</div>
{searchParams.error ? (
<p className="rounded-md bg-red-50 p-3 text-sm text-red-700">
Anmeldung fehlgeschlagen. Versuche ein anderes Konto oder kontaktiere den Support.
</p>
) : null}
<div className="grid gap-3">
{providers.map((provider) => (
<form
key={provider.id}
action={async () => {
"use server";
await signIn(provider.id, { redirectTo: "/dashboard" });
}}
>
<button
type="submit"
className="w-full rounded-md border px-4 py-3 text-sm font-medium hover:bg-gray-50"
>
{provider.label}
</button>
</form>
))}
</div>
</main>
);
}
// src/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 (
<main className="mx-auto max-w-3xl p-8">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="mt-4 text-gray-700">
Signed in as {session.user.email}.
</p>
</main>
);
}
Account linking und Entfernen
Verknüpfe Konten nicht automatisch nur wegen gleicher E-Mail. Der Nutzer muss angemeldet sein und die Verknüpfung bewusst in den Einstellungen starten. Beim Entfernen prüft die API die Herkunft und verhindert das Löschen der letzten Login-Methode.
// src/app/api/settings/linked-accounts/route.ts
import { auth } from "@/auth";
import { prisma } from "@/lib/prisma";
import { NextResponse } from "next/server";
function isSameOrigin(request: Request) {
const origin = request.headers.get("origin");
const host = request.headers.get("host");
if (!origin || !host) return false;
try {
return new URL(origin).host === host;
} catch {
return false;
}
}
export async function DELETE(request: Request) {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (!isSameOrigin(request)) {
return NextResponse.json({ error: "Bad origin" }, { status: 403 });
}
const body = (await request.json()) as { provider?: string };
const accounts = await prisma.account.findMany({
where: { userId: session.user.id },
select: { provider: true },
});
if (accounts.length <= 1) {
return NextResponse.json(
{ error: "Die letzte Login-Methode kann nicht entfernt werden." },
{ status: 400 },
);
}
await prisma.account.deleteMany({
where: { userId: session.user.id, provider: body.provider },
});
return NextResponse.json({ ok: true });
}
Prompt und Review-Checkliste
Füge Google/GitHub Social Login zu dieser Next.js App Router App mit Auth.js v5 hinzu.
Anforderungen:
- Client secrets nur aus .env.local oder Deployment Secrets lesen
- Google scope ist nur openid email profile
- GitHub scope ist nur read:user user:email
- Google nur erlauben, wenn email_verified true ist
- Session nur um user.id erweitern und access_token nie an den Client senden
- Account linking nur aus einer Settings-Seite mit angemeldetem Nutzer
- Letzte Login-Methode darf nicht entfernt werden
- Danach Lint-Ergebnis und manuelle OAuth-Testschritte melden
| Review-Punkt | Prüfung |
|---|---|
| OAuth flow | Authorization code flow und exakt passende redirect URI |
| state und CSRF | Auth.js-Standardrouten bleiben erhalten, eigene APIs prüfen same-origin |
| Cookie und Session | Provider-Tokens bleiben serverseitig, Cookies beachten Secure, HttpOnly, SameSite |
| Account linking | Verifizierte E-Mail und bewusste Nutzeraktion vor Verknüpfung |
Praktische Anwendungsfälle
Erstens: ein B2B-SaaS-Adminbereich. Google ist der Hauptzugang, später kommen Workspace-Domainprüfung und RBAC hinzu. GitHub bleibt für technische Nutzer.
Zweitens: ein Entwicklerwerkzeug. GitHub verkürzt das Onboarding; Repository-Rechte werden erst in der Funktion angefragt, die sie wirklich braucht.
Drittens: ein bestehendes Produkt mit E-Mail und Passwort. Der sichere Weg ist, angemeldete Nutzer Google oder GitHub in den Einstellungen verknüpfen zu lassen, statt Konten automatisch zu verschmelzen.
Viertens: eine Schulungs- oder Webinar-Seite. Google verified email reduziert falsche Anmeldungen und manuellen Support.
Häufige Produktionsfehler
Eine falsche redirect URI ist der Klassiker. Wenn lokal alles funktioniert und Produktion nicht, vergleicheAUTH_URL, Proxy Host Header und Callback URLs bei Google/GitHub.
Automatisches Linking über E-Mail ist gefährlich. Google liefertemail_verified, aber nicht jeder Provider bietet dieselbe Sicherheit. Eine gleiche E-Mail-Zeichenkette reicht nicht als Beweis.
Zu breite Scopes senken PV und Anfragen. Wer sich nur anmelden will, bricht eher ab, wenn Repository- oder Dateirechte verlangt werden.
Ein client secret im Code muss den Review stoppen. Nutze.env.local, Deployment Secrets oder einen Secret Manager.
Google refresh tokens brauchen ein eigenes Design. Google kann sie nur beim ersten Consent zurückgeben. Für reinen Login nicht speichern; für Hintergrund-APIs separat Token Rotation planen.
Offizielle Quellen und interne Links
Prüfe vor Änderungen die Primärquellen: Auth.js, Google Identity Services OAuth, GitHub OAuth Apps und OWASP Authentication Cheat Sheet.
Vertiefend helfen OAuth-Implementierung mit Claude Code, JWT-Authentifizierung, Claude Code Security Best Practices und Umgebungsvariablen verwalten.
Beratung, Training und Prüfpunkte
ClaudeCodeLab unterstützt Teams dabei, aus einem Login-Button einen überprüfbaren Authentifizierungsflow zu machen: Anforderungen, Claude-Code-Aufgaben, Code Review, Secret Management und manuelle OAuth-Tests. Wer mehr qualifizierte Anfragen will, sollte Zielnutzer, Provider, minimale Rechte und Fehlermeldungen vor der Implementierung klären.
Teste zum Schluss, ob Entwicklungs- und Produktions-redirect-URI exakt gleich konfiguriert sind, Googleemail_verified geprüft wird, GitHub ohne öffentliche E-Mail sauber fehlschlägt, state und Cookies im Auth.js-Standardflow bleiben, die letzte Login-Methode nicht löschbar ist und kein client secret in Code oder Logs erscheint.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.