Authentication ist einer der fehleranfälligsten Bereiche in der Web-Entwicklung. Falsch implementierte Auth-Systeme führen zu Sicherheitslücken, DSGVO-Problemen und schlechter User Experience. Clerk löst dieses Problem mit einer vollständigen, hostbaren Auth-Infrastruktur — und Claude Code kennt jeden Winkel davon.
In diesem Guide zeigen wir, wie du mit Claude Code ein vollständiges User Management System für Next.js 2026 aufbaust: von der ersten Installation über Middleware und Hooks bis hin zu Organizations, Rollen und Datenbank-Synchronisation via Webhooks.
Warum Clerk statt Next-Auth oder selbst gebautem Auth?
Next-Auth ist flexibel, aber du musst Session-Management, Token-Rotation und Security-Updates selbst managen. Clerk übernimmt das vollständig — mit integrierten UI-Komponenten, MFA, Social Logins, Passkeys und einer gehosteten Benutzeroberfläche, die du einfach in deine App einbettest.
Claude Code Vorteil: Claude kennt die aktuelle Clerk-Dokumentation und schreibt korrekte TypeScript-Typen für auth(), currentUser() und alle Webhook-Payloads — ohne dass du selbst recherchieren musst.
SETUP Clerk in Next.js App Router installieren
Claude Code führt die Installation durch und konfiguriert alle erforderlichen Dateien automatisch:
# Claude Code: Installation und Grundkonfiguration
npm install @clerk/nextjs
# .env.local (von Clerk Dashboard kopieren)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxx
CLERK_SECRET_KEY=sk_live_xxx
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding
Dann umschließt Claude Code das Root-Layout mit dem ClerkProvider:
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<ClerkProvider>
<html lang="de">
<body className={inter.className}>
{children}
</body>
</html>
</ClerkProvider>
)
}
MIDDLEWARE Route-Schutz mit Clerk Middleware
Die Middleware entscheidet, welche Routen öffentlich zugänglich sind und welche eine Anmeldung erfordern. Claude Code schreibt die Konfiguration passend zu deiner App-Struktur:
// middleware.ts (Projekt-Root)
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/blog(.*)',
'/api/webhooks(.*)', // Clerk Webhooks immer public!
])
export default clerkMiddleware(async (auth, request) => {
if (!isPublicRoute(request)) {
await auth.protect()
}
})
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
}
Wichtig: /api/webhooks(.*) muss immer öffentlich sein, sonst kann Clerk keine Webhook-Events senden und dein User-Sync schlägt fehl.
HOOKS useAuth und useUser in Client Components
Für Client Components bietet Clerk die Hooks useAuth (Session-Daten) und useUser (User-Profil). Claude Code nutzt immer den richtigen Hook für den jeweiligen Anwendungsfall:
// components/UserGreeting.tsx — Client Component
'use client'
import { useAuth, useUser } from '@clerk/nextjs'
export default function UserGreeting() {
const { isLoaded, isSignedIn, userId } = useAuth()
const { user } = useUser()
if (!isLoaded) return <div>Laden...</div>
if (!isSignedIn) return <div>Nicht angemeldet</div>
return (
<div className="greeting">
<p>Hallo, {user?.firstName}!</p>
<p>Email: {user?.primaryEmailAddress?.emailAddress}</p>
<p>User ID: {userId}</p>
<img src={user?.imageUrl} alt="Avatar" width={40} height={40} />
</div>
)
}
In Server Components und Route Handlers verwendest du dagegen auth() und currentUser():
// app/dashboard/page.tsx — Server Component
import { auth, currentUser } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const { userId } = await auth()
if (!userId) redirect('/sign-in')
const user = await currentUser()
return (
<main>
<h1>Dashboard — {user?.firstName}</h1>
<p>Willkommen zurück, {user?.emailAddresses[0].emailAddress}</p>
</main>
)
}
KOMPONENTEN SignIn / SignUp Seiten einrichten
Clerk liefert fertige, vollständig anpassbare UI-Komponenten. Claude Code erstellt die Catch-All-Routen korrekt:
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs'
export default function SignInPage() {
return (
<div className="auth-container">
<SignIn
appearance={{
elements: {
rootBox: 'mx-auto',
card: 'shadow-lg rounded-xl',
headerTitle: 'text-2xl font-bold',
}
}}
/>
</div>
)
}
// app/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from '@clerk/nextjs'
export default function SignUpPage() {
return (
<div className="auth-container">
<SignUp />
</div>
)
}
Die Navigation integriert UserButton (Profil-Dropdown) und SignedIn/SignedOut für bedingte Anzeige:
// components/Navbar.tsx
'use client'
import { SignedIn, SignedOut, SignInButton, UserButton } from '@clerk/nextjs'
export default function Navbar() {
return (
<nav className="navbar">
<a href="/">MeineSaaS</a>
<SignedOut>
<SignInButton mode="modal">
<button className="btn-primary">Anmelden</button>
</SignInButton>
</SignedOut>
<SignedIn>
<UserButton afterSignOutUrl="/" />
</SignedIn>
</nav>
)
}
ORGANIZATIONS Multi-Tenancy mit Clerk Organizations
Clerk Organizations ermöglichen echtes Multi-Tenancy: Jeder User kann Mitglied mehrerer Organisationen sein, mit unterschiedlichen Rollen (admin, member). Claude Code implementiert den kompletten Org-Flow:
Voraussetzung: Organizations müssen im Clerk Dashboard unter "Organizations" aktiviert werden. Dann Rollen und Berechtigungen definieren bevor du Code schreibst.
// Org-Kontext in der Middleware prüfen
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
export default clerkMiddleware(async (auth, req) => {
if (isAdminRoute(req)) {
const { orgRole } = await auth()
if (orgRole !== 'org:admin') {
return Response.redirect(new URL('/dashboard', req.url))
}
}
})
// hooks/useOrgRole.ts — Rolle im Client prüfen
'use client'
import { useOrganization, useAuth } from '@clerk/nextjs'
export function useOrgRole() {
const { membership } = useOrganization()
const { orgRole } = useAuth()
return {
isAdmin: orgRole === 'org:admin',
isMember: orgRole === 'org:member',
role: membership?.role ?? null,
}
}
// Nutzung in einer Komponente:
export function AdminPanel() {
const { isAdmin } = useOrgRole()
if (!isAdmin) return <p>Kein Zugriff.</p>
return <div>Admin-Panel</div>
}
Die OrganizationSwitcher-Komponente gibt Users die Möglichkeit, zwischen ihren Organisationen zu wechseln — inklusive Einladungsflows:
import { OrganizationSwitcher } from '@clerk/nextjs'
export function OrgNav() {
return (
<OrganizationSwitcher
hidePersonal={false}
afterSelectOrganizationUrl="/dashboard/org/:id"
afterLeaveOrganizationUrl="/dashboard"
afterCreateOrganizationUrl="/onboarding/org"
/>
)
}
WEBHOOKS User-Sync mit Prisma und Supabase
Clerk ist der Auth-Provider — deine Datenbank (Prisma/Supabase) enthält die App-Daten. Webhooks halten beide synchron. Claude Code implementiert den sicheren Webhook-Handler mit Svix-Signaturprüfung:
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix'
import { headers } from 'next/headers'
import { WebhookEvent } from '@clerk/nextjs/server'
import { db } from '@/lib/db' // Prisma Client
export async function POST(req: Request) {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET
if (!WEBHOOK_SECRET) throw new Error('CLERK_WEBHOOK_SECRET fehlt')
// Svix-Header für Signaturprüfung
const headerPayload = headers()
const svixId = headerPayload.get('svix-id')
const svixTimestamp = headerPayload.get('svix-timestamp')
const svixSignature = headerPayload.get('svix-signature')
if (!svixId || !svixTimestamp || !svixSignature) {
return new Response('Fehlende Svix-Header', { status: 400 })
}
const body = await req.text()
const wh = new Webhook(WEBHOOK_SECRET)
let evt: WebhookEvent
try {
evt = wh.verify(body, {
'svix-id': svixId,
'svix-timestamp': svixTimestamp,
'svix-signature': svixSignature,
}) as WebhookEvent
} catch (err) {
return new Response('Ungültige Signatur', { status: 400 })
}
// Event-Handler
const { type } = evt
if (type === 'user.created') {
const { id, email_addresses, first_name, last_name, image_url } = evt.data
await db.user.create({
data: {
clerkId: id,
email: email_addresses[0].email_address,
firstName: first_name ?? '',
lastName: last_name ?? '',
imageUrl: image_url,
plan: 'free',
}
})
}
if (type === 'user.updated') {
const { id, email_addresses, first_name, last_name, image_url } = evt.data
await db.user.update({
where: { clerkId: id },
data: {
email: email_addresses[0].email_address,
firstName: first_name ?? '',
lastName: last_name ?? '',
imageUrl: image_url,
}
})
}
if (type === 'user.deleted') {
await db.user.delete({
where: { clerkId: evt.data.id }
})
}
return new Response('', { status: 200 })
}
Clerk Dashboard: Unter "Webhooks" eine neue Endpoint-URL eintragen (https://deine-domain.com/api/webhooks/clerk) und die Events user.created, user.updated, user.deleted abonnieren. Den generierten Signing Secret als CLERK_WEBHOOK_SECRET in dein .env eintragen.
SUPABASE Clerk + Supabase: JWT-basierter User-Sync
Statt Webhooks kannst du bei Supabase auch den JWT-basierten Ansatz nutzen: Clerk stellt ein JWT aus, das Supabase direkt validieren kann — für Row-Level-Security ohne separaten Sync:
// lib/supabase-clerk.ts — Supabase Client mit Clerk JWT
import { createClient } from '@supabase/supabase-js'
import { auth } from '@clerk/nextjs/server'
export async function createClerkSupabaseClient() {
const { getToken } = await auth()
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
global: {
fetch: async (url, options = {}) => {
const clerkToken = await getToken({ template: 'supabase' })
const headers = new Headers(options?.headers)
headers.set('Authorization', `Bearer ${clerkToken}`)
return fetch(url, { ...options, headers })
}
}
}
)
return supabase
}
// Nutzung im Server Component / Route Handler:
const supabase = await createClerkSupabaseClient()
const { data: projects } = await supabase
.from('projects')
.select('*')
.eq('user_id', userId) // Clerk userId direkt in Supabase
RLS-Tipp: In Supabase kannst du Policies mit auth.jwt() ->> 'sub' schreiben — das entspricht dem Clerk userId. Claude Code erstellt passende RLS-Policies auf Anfrage automatisch.
Claude Code als Clerk-Experte: Was der KI-Agent automatisch erledigt
Die größte Stärke von Claude Code bei Clerk-Projekten ist nicht nur das Schreiben von Code — es ist das Wissen über häufige Fehler und Best Practices:
- Catch-All-Routen (
[[...sign-in]]) werden immer korrekt erstellt — ein häufiger Anfängerfehler
- Svix-Signaturprüfung wird nie vergessen — Sicherheit by default
- Server vs. Client — Claude weiß, wann
auth() (Server) und wann useAuth() (Client) verwendet wird
- Webhook-Secret vs. Publishable Key — korrekte Umgebungsvariablen, niemals verwechselt
- Org-Rollen werden als
org:admin und org:member formatiert — das richtige Format für Clerk Permissions
Häufiger Fehler: Die Middleware-Datei muss im Projekt-Root liegen (middleware.ts), nicht im app/-Verzeichnis. Claude Code platziert sie automatisch richtig.
Fazit: Vollständiges Auth-System in Minuten
Mit Clerk und Claude Code baust du ein vollständiges, produktionsreifes Authentication-System in einem Bruchteil der Zeit. Middleware, Hooks, Organizations, MFA und Datenbank-Sync — alles folgt klaren Patterns, die Claude Code zuverlässig implementiert. Das Ergebnis: weniger Sicherheitsrisiken, schnellere Entwicklung und eine professionelle User Experience von Tag 1.
Auth-Modul im Kurs
Im Claude Code Mastery Kurs: vollständiges Auth-Modul mit Clerk, Next-Auth, Middleware-Setup, Organizations, Webhooks und Datenbankintegration — für sichere SaaS-Applikationen.
14 Tage kostenlos testen →