Next.js-Routenschutz und Berechtigungskontrolle: Middleware und mehrschichtige Absicherung – vollständiger Leitfaden

Letzten Monat bei einem Sicherheitsaudit für einen Kunden sah der Prüfer meinen Berechtigungscode und fragte: „Das war’s? Nur eine Middleware-Abfrage?”
Ich war zunächst überzeugt. Middleware ist doch genau dafür da, oder? Nicht eingeloggte Benutzer werden zur Login-Seite umgeleitet, eingeloggte durchgelassen. Sieht wasserdicht aus.
Der Prüfer öffnete die Browser-Entwicklertools und zeigte es mir: Frontend-Routing umgehen, die Backend-API direkt mit Postman aufrufen – und die eigentlich geschützten Daten waren da.
Das war ein Weckruf.
Erst nach weiterer Recherche wurde klar: Next.js-Berechtigungskontrolle lässt sich nie allein mit Middleware lösen. Sie brauchen eine vollständige mehrschichtige Absicherungsarchitektur.
In diesem Artikel geht es um: Wie setzt man Middleware richtig ein? Was ist die Beziehung zu getServerSession? Wie gestaltet man ein RBAC-System für Admin-Backends? Und welche Code-Vorlagen können Sie direkt übernehmen?
Wenn Sie gerade Next.js-Berechtigungskontrolle implementieren oder Middleware und getServerSession kombinieren wollen, sollte dieser Artikel weiterhelfen.
Warum Berechtigungskontrolle nicht nur auf Middleware basieren darf
Was Middleware eigentlich macht
Zuerst die Positionierung von Middleware.
Sie läuft auf der Edge Runtime und ist die erste Schicht, die eingehende Requests erreicht. Hier können Sie Grobfilter setzen: Ist der Benutzer eingeloggt? Admin oder normaler Benutzer? Durchlassen oder umleiten?
Schnell und früh – klingt ideal für Berechtigungskontrolle, oder?
Stimmt. Aber allein reicht es nicht.
Ein realer Schwachstellenfall
Im Januar 2025 veröffentlichten Sicherheitsforscher CVE-2025-29927. Kurz gesagt: Angreifer können mit einem speziellen x-middleware-subrequest-Header in der Request die Middleware-Prüfung umgehen.
Alle Logik in der Middleware wird bei diesem Angriff wirkungslos.
Das ist kein Einzelfall. Als äußerste Verteidigungslinie kann Middleware umgangen, falsch konfiguriert oder durch Edge-Runtime-Einschränkungen bei komplexen Berechtigungsentscheidungen überfordert sein.
Die Next.js-Dokumentation betont ausdrücklich: „While Middleware can be useful for initial checks, it should not be your only line of defense.”
Übersetzt: Setzen Sie nicht alles auf Middleware.
Frontend blockiert – und das Backend?
Ich habe einmal ein Admin-Backend gebaut. In der Middleware stand eine Login-Prüfung: Nicht eingeloggte Benutzer, die /admin aufrufen, werden zur Login-Seite umgeleitet.
Sieht sicher aus. Menüklicks und Routenwechsel laufen durch die Middleware.
Wo lag das Problem? Die API war ungeschützt.
Ein Tester schaute im Network-Panel und fand: Der Endpoint zum Löschen von Benutzern war POST /api/users/delete. Er rief ihn direkt mit curl auf – mit beliebigen Parametern.
Gelöscht.
In der API Route gab es keine Berechtigungsprüfung. Ich hatte nur Frontend-Routen in der Middleware abgesichert und nicht bedacht, dass jemand APIs direkt aufrufen kann.
Das ist das Problem reiner Middleware-Abhängigkeit: Sie sichert die Frontend-Tür, aber nicht das Backend-Fenster.
Die offizielle Next.js-Empfehlung
In der Dokumentation steht das „Proximity Principle” – Berechtigungsprüfungen so nah wie möglich an den Daten platzieren.
Was heißt das?
Daten liegen in der Datenbank. Wer Daten schützen will, prüft Berechtigungen vor dem Datenzugriff – nicht auf Routing- oder Seitenebene, sondern auf Datenebene.
Das heißt nicht, dass Middleware nutzlos ist. Sie kann die erste Schicht sein: Umleitung nicht eingeloggter Benutzer, offensichtlich unberechtigte Rollen ablehnen. Aber Sie brauchen zusätzlich:
- Prüfungen in Server Components: Vor dem Rendern validieren, ob der Benutzer die Seite sehen darf
- Prüfungen in API Routes und Server Actions: Vor jeder Datenoperation Berechtigungen erneut prüfen
- Prüfungen auf Datenbankebene: Row-Level Security oder Query-Filter, damit Benutzer nur autorisierte Daten sehen
Mehrschichtige Absicherung. Wird eine Schicht umgangen, greift die nächste.
Am Anfang wirkte mir das übertrieben. Nach ein paar Fehltritten wurde klar: Genau dort, wo Sicherheit „lästig” wirkt, sind Angreifer interessiert.
Die richtige Kombination aus Middleware und getServerSession
Warum getServerSession nicht in Middleware funktioniert
Als ich NextAuth zum ersten Mal nutzte, stolperte ich über genau diese Frage.
In Server Components holt man die Session mit getServerSession(authOptions). Logisch wollte ich dasselbe in der Middleware.
Fehler.
Nach längerer Recherche: Middleware läuft auf Edge Runtime, getServerSession braucht Node.js Runtime. Beides ist inkompatibel.
Edge Runtime ist Vercels leichtgewichtige Umgebung ohne vollständige Node.js-APIs – schnell und global verteilt. Middleware nutzt Edge Runtime für Performance, kann aber nicht alle Node.js-Funktionen verwenden.
Wie holt man die Session in der Middleware?
Der richtige Weg: getToken oder withAuth
NextAuth bietet zwei APIs speziell für Middleware:
Variante 1: getToken verwenden
// middleware.ts
import { getToken } from "next-auth/jwt"
import { NextResponse } from "next/server"
export async function middleware(req) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET })
if (!token) {
return NextResponse.redirect(new URL('/login', req.url))
}
// Rolle prüfen
if (req.nextUrl.pathname.startsWith('/admin') && token.role !== 'admin') {
return NextResponse.redirect(new URL('/403', req.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/admin/:path*', '/dashboard/:path*']
}
getToken parst den JWT aus dem Cookie und liefert Benutzerinformationen. Es unterstützt nur die JWT-Session-Strategie – bei Database Sessions funktioniert das nicht.
Variante 2: Higher-Order-Funktion withAuth
// middleware.ts
import { withAuth } from "next-auth/middleware"
export default withAuth({
callbacks: {
authorized: ({ token, req }) => {
// Nicht eingeloggt
if (!token) return false
// /admin nur für admin-Rolle
if (req.nextUrl.pathname.startsWith('/admin')) {
return token.role === 'admin'
}
return true
}
}
})
export const config = {
matcher: ['/admin/:path*', '/dashboard/:path*']
}
withAuth übernimmt die Redirect-Logik. Im authorized-Callback geben Sie true oder false zurück – bei false erfolgt die Umleitung zur Login-Seite.
Ich bevorzuge withAuth – der Code bleibt kompakter.
Wo wird getServerSession eingesetzt?
getServerSession gehört in Server Components, API Routes und Server Actions.
In Server Components:
// app/admin/page.tsx
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"
import { redirect } from "next/navigation"
export default async function AdminPage() {
const session = await getServerSession(authOptions)
if (!session) {
redirect('/login')
}
if (session.user.role !== 'admin') {
redirect('/403')
}
// Seite rendern
return <UsersList />
}
Diese Schicht prüft, ob der Benutzer die Seite sehen darf. Keine Berechtigung – kein Zugriff.
In API Routes:
// app/api/users/route.ts
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"
import { NextResponse } from "next/server"
export async function DELETE(req: Request) {
const session = await getServerSession(authOptions)
if (!session || session.user.role !== 'admin') {
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
}
// Löschvorgang ausführen
// ...
return NextResponse.json({ success: true })
}
In Server Actions:
// app/actions.ts
'use server'
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"
export async function deleteUser(userId: string) {
const session = await getServerSession(authOptions)
if (!session || session.user.role !== 'admin') {
throw new Error('Unauthorized')
}
// Löschen ausführen
// ...
}
Die Aufgabenteilung
Kurz gesagt:
- Middleware (mit getToken): Grobe Routenabsicherung – Umleitung nicht eingeloggter Benutzer, grundlegende Rollenprüfung
- getServerSession: Feingranulare Berechtigungskontrolle – finale Validierung vor Datenzugriff
Middleware ist der Pförtner am Wohnkomplex, getServerSession das Türschloss – selbst wenn der Pförtner durchwinkt, ohne Schlüssel kommen Sie nicht rein.
Mehrschichtige Absicherung – genau so.
Vollständiges RBAC-Design für Admin-Backends
Middleware und getServerSession sind wichtig, aber für ein echtes Admin-Backend brauchen Sie ein vollständiges RBAC-System (Role-Based Access Control).
Das RBAC-Modell verstehen
Kernidee: Benutzer → Rolle → Berechtigung.
- Benutzer (User): Anna, Bernd
- Rolle (Role): Admin, Editor, Viewer
- Berechtigung (Permission): Benutzerliste ansehen, Artikel bearbeiten, Kommentare löschen
Ein Benutzer kann mehrere Rollen haben, eine Rolle mehrere Berechtigungen. Anna ist Admin mit allen Rechten; Bernd ist Editor und darf Artikel bearbeiten und Benutzerlisten ansehen.
Berechtigungen lassen sich in drei Granularitäten unterteilen:
- Seitenebene: Darf
/admin/usersaufgerufen werden? - Funktionsebene: Darf der „Löschen”-Button geklickt werden?
- Datenebene: Darf nur eigene Artikel gesehen werden?
Datenbankdesign (Prisma Schema):
model User {
id String @id @default(cuid())
email String @unique
roles Role[]
}
model Role {
id String @id @default(cuid())
name String @unique
permissions Permission[]
users User[]
}
model Permission {
id String @id @default(cuid())
name String @unique // z. B. "user:view", "user:edit"
roles Role[]
}
In vielen Projekten reicht ein einfacheres Modell. Bei wenigen Rollen (Admin, Editor, Viewer) können Sie Berechtigungen auch direkt im Code definieren.
Vier-Schichten-Absicherungsarchitektur
So setzen Sie Berechtigungsprüfungen auf jeder Ebene um.
Schicht 1: Middleware – grobe Absicherung
// middleware.ts
import { withAuth } from "next-auth/middleware"
export default withAuth({
callbacks: {
authorized: ({ token, req }) => {
if (!token) return false
const path = req.nextUrl.pathname
// /admin nur für admin-Rolle
if (path.startsWith('/admin')) {
return token.role === 'admin'
}
// /dashboard erfordert Login
if (path.startsWith('/dashboard')) {
return true
}
return false
}
}
})
export const config = {
matcher: ['/admin/:path*', '/dashboard/:path*']
}
Hier nur grundlegende Rollenprüfung – keine funktionsspezifischen Berechtigungen.
Schicht 2: Server Component – Seitenberechtigung
// app/admin/users/page.tsx
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"
import { checkPermission } from "@/lib/permissions"
import { redirect } from "next/navigation"
export default async function UsersPage() {
const session = await getServerSession(authOptions)
if (!session) {
redirect('/login')
}
// Berechtigung für Benutzerliste prüfen
const hasPermission = await checkPermission(session.user.id, 'user:view')
if (!hasPermission) {
redirect('/403')
}
// Seite rendern
return <UsersList />
}
Schicht 3: UI-Bedingungsrendering – Funktionsberechtigung
// components/UsersList.tsx
'use client'
import { useSession } from "next-auth/react"
import { hasPermission } from "@/lib/permissions-client"
export function UsersList() {
const { data: session } = useSession()
const canEdit = hasPermission(session, 'user:edit')
const canDelete = hasPermission(session, 'user:delete')
return (
<div>
{users.map(user => (
<div key={user.id}>
<span>{user.name}</span>
{canEdit && <button>Bearbeiten</button>}
{canDelete && <button>Löschen</button>}
</div>
))}
</div>
)
}
Buttons werden nach Berechtigung ein- oder ausgeblendet. Das ist UX-Optimierung, keine Sicherheitsmaßnahme – technisch versierte Nutzer können Buttons im Browser wieder einblenden. Die echte Prüfung folgt in der nächsten Schicht.
Schicht 4: Server Action / API – Validierung vor Datenzugriff
// app/actions.ts
'use server'
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"
import { checkPermission } from "@/lib/permissions"
export async function deleteUser(userId: string) {
const session = await getServerSession(authOptions)
if (!session) {
throw new Error('Unauthorized')
}
// Löschberechtigung erforderlich
const hasPermission = await checkPermission(session.user.id, 'user:delete')
if (!hasPermission) {
throw new Error('Forbidden')
}
// Löschen ausführen
await db.user.delete({ where: { id: userId } })
return { success: true }
}
Letzte Verteidigungslinie: Unabhängig von vorherigen Schichten muss vor jeder Datenoperation die Berechtigung erneut geprüft werden.
Berechtigungen zentral verwalten
Überall dieselbe Prüflogik zu schreiben, ist mühsam. Berechtigungsdefinitionen und Prüflogik gehören in eine zentrale Datei.
// lib/permissions.ts
export const PERMISSIONS = {
USER_VIEW: 'user:view',
USER_EDIT: 'user:edit',
USER_DELETE: 'user:delete',
POST_VIEW: 'post:view',
POST_EDIT: 'post:edit',
POST_DELETE: 'post:delete',
} as const
export const ROLES = {
ADMIN: {
name: 'admin',
permissions: Object.values(PERMISSIONS) // Admin hat alle Berechtigungen
},
EDITOR: {
name: 'editor',
permissions: [
PERMISSIONS.USER_VIEW,
PERMISSIONS.POST_VIEW,
PERMISSIONS.POST_EDIT,
]
},
VIEWER: {
name: 'viewer',
permissions: [
PERMISSIONS.USER_VIEW,
PERMISSIONS.POST_VIEW,
]
}
} as const
// Serverseitige Berechtigungsprüfung
export async function checkPermission(userId: string, permission: string) {
const user = await db.user.findUnique({
where: { id: userId },
include: { roles: true }
})
if (!user) return false
// Prüfen, ob die Rolle des Benutzers die Berechtigung enthält
const userRole = ROLES[user.role as keyof typeof ROLES]
return userRole?.permissions.includes(permission) ?? false
}
// lib/permissions-client.ts (Client-Version)
import { Session } from "next-auth"
export function hasPermission(session: Session | null, permission: string) {
if (!session?.user) return false
const userRole = ROLES[session.user.role as keyof typeof ROLES]
return userRole?.permissions.includes(permission) ?? false
}
Berechtigungen an einer Stelle pflegen – kein Suchen im gesamten Code.
Vorteile dieser Architektur
Mehr Code auf mehreren Ebenen – lohnt sich das?
Ja.
- Sicherheit: Wird eine Schicht umgangen, greifen die anderen
- Wartbarkeit: Berechtigungen zentral konfiguriert, Änderungen einfach
- Benutzererfahrung: Unberechtigte Aktionen werden ausgeblendet, nicht erst nach Klick abgelehnt
- Audit-freundlich: Jede Schicht hat klare Berechtigungsprüfungen – Sicherheitsaudits laufen leichter
Am Anfang kostet der Aufbau Zeit. Beim Hinzufügen neuer Features und Anpassen von Regeln sparen Sie später deutlich Aufwand.
Praxisbeispiele und Code-Vorlagen
Nach Theorie und Architektur folgen direkt nutzbare Vorlagen und Tipps zur Fehlersuche.
Vollständige Middleware-Konfiguration
Funktionsreiche Middleware mit Mehrrollen-Support und dynamischem Routing:
// middleware.ts
import { withAuth } from "next-auth/middleware"
import { NextResponse } from "next/server"
export default withAuth(
function middleware(req) {
const token = req.nextauth.token
const path = req.nextUrl.pathname
// Feingranulare Steuerung nach Pfad und Rolle
if (path.startsWith('/admin') && token?.role !== 'admin') {
return NextResponse.redirect(new URL('/403', req.url))
}
if (path.startsWith('/editor') && !['admin', 'editor'].includes(token?.role as string)) {
return NextResponse.redirect(new URL('/403', req.url))
}
return NextResponse.next()
},
{
callbacks: {
authorized: ({ token }) => !!token
}
}
)
export const config = {
matcher: [
'/admin/:path*',
'/editor/:path*',
'/dashboard/:path*',
'/api/admin/:path*',
'/api/editor/:path*'
]
}
Wichtige Punkte:
matcherdefiniert geschützte Pfade – Wildcard:path*unterstütztauthorized-Callback für grundlegende Login-Prüfung- In der
middleware-Funktion feinere Rollenprüfung
Dynamisches Menü-Rendering
Menüs nach Berechtigungen zu filtern ist in Admin-Backends Standard:
// components/Sidebar.tsx
'use client'
import { useSession } from "next-auth/react"
import Link from "next/link"
import { hasPermission } from "@/lib/permissions-client"
const menuItems = [
{
label: 'Benutzerverwaltung',
href: '/admin/users',
permission: 'user:view'
},
{
label: 'Artikelverwaltung',
href: '/admin/posts',
permission: 'post:view'
},
{
label: 'Systemeinstellungen',
href: '/admin/settings',
permission: 'setting:manage'
}
]
export function Sidebar() {
const { data: session } = useSession()
// Menüeinträge nach Berechtigung filtern
const visibleItems = menuItems.filter(item =>
hasPermission(session, item.permission)
)
return (
<nav>
{visibleItems.map(item => (
<Link key={item.href} href={item.href}>
{item.label}
</Link>
))}
</nav>
)
}
Menükonfiguration und Berechtigungen an einem Ort – neue Einträge einfach ins menuItems-Array.
Wiederverwendbarer Berechtigungs-Hook
Ein React Hook für Client Components:
// hooks/usePermission.ts
import { useSession } from "next-auth/react"
import { hasPermission } from "@/lib/permissions-client"
export function usePermission(permission: string) {
const { data: session, status } = useSession()
const isLoading = status === 'loading'
const isAllowed = hasPermission(session, permission)
return { isAllowed, isLoading }
}
Verwendung:
// components/DeleteButton.tsx
'use client'
import { usePermission } from "@/hooks/usePermission"
export function DeleteButton({ userId }: { userId: string }) {
const { isAllowed, isLoading } = usePermission('user:delete')
if (isLoading) return <div>Loading...</div>
if (!isAllowed) return null
return (
<button onClick={() => deleteUser(userId)}>
Löschen
</button>
)
}
Häufige Probleme und Lösungen
Problem 1: Endlosschleife bei Middleware-Redirects
Symptom: Browser meldet „Zu viele Weiterleitungen”.
Ursache: Die Login-Seite liegt im matcher und wird ebenfalls abgefangen.
Lösung: Login-Seite und öffentliche Seiten vom matcher ausschließen.
export const config = {
matcher: [
/*
* Alle Pfade außer:
* - /login (Login-Seite)
* - /api/auth (NextAuth API)
* - /_next (Next.js intern)
* - /favicon.ico, /robots.txt (statische Dateien)
*/
'/((?!login|api/auth|_next|favicon.ico|robots.txt).*)',
]
}
Problem 2: getServerSession gibt null zurück
Symptom: Benutzer ist eingeloggt, aber getServerSession liefert null.
Ursachen:
authOptionsfalsch konfiguriert oder nicht übergeben- Cookie-Probleme (Cross-Domain, HTTPS)
getServerSessionfälschlich in Middleware verwendet
Lösung:
authOptions-Import prüfen- Nur in Server Components oder API Routes verwenden, nicht in Middleware
- In der Entwicklung
NEXTAUTH_URLprüfen
// Korrekte Verwendung
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/app/api/auth/[...nextauth]/route"
const session = await getServerSession(authOptions)
Problem 3: Performance bei Berechtigungsprüfungen
Symptom: Seiten laden langsam wegen Datenbankabfragen bei jeder Berechtigungsprüfung.
Ursache: Kein Caching – jeder Request fragt die Datenbank ab.
Lösung:
- Rolle und Berechtigungen im JWT-Token speichern
// app/api/auth/[...nextauth]/route.ts
export const authOptions: NextAuthOptions = {
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role
token.permissions = user.permissions
}
return token
},
async session({ session, token }) {
session.user.role = token.role
session.user.permissions = token.permissions
return session
}
}
}
- React Query oder SWR für Client-Caching
// hooks/usePermissions.ts
import useSWR from 'swr'
export function usePermissions() {
const { data: permissions } = useSWR('/api/me/permissions', {
revalidateOnFocus: false,
dedupingInterval: 60000 // 1 Minute kein erneuter Request
})
return permissions
}
Schnell-Checkliste vor dem Go-Live
Vor dem Deployment prüfen:
- Macht Middleware nur Grobfilter?
- Haben alle Server-Component-Seiten Berechtigungsprüfungen?
- Haben alle API Routes Berechtigungsprüfungen?
- Haben alle Server Actions Berechtigungsprüfungen?
- Werden sensible Buttons nach Berechtigung ausgeblendet?
- Sind Berechtigungen zentral verwaltet?
- Enthält der JWT-Token Rolleninformationen?
- Sind Login-Seite und öffentliche Seiten vom Middleware-
matcherausgeschlossen?
Wenn alles erfüllt ist, steht Ihre Berechtigungskontrolle auf solidem Fundament.
Fazit
Zurück zur Ausgangsfrage: Wie macht man Next.js-Berechtigungskontrolle richtig?
Antwort: Es gibt keine Ein-Lösung-für-alles.
Middleware ist wichtig – erste Verteidigungslinie, hält die meisten unautorisierten Zugriffe ab. Aber nicht alles. Sie brauchen Prüfungen in Server Components, Validierung in Server Actions und API Routes, und UX-Optimierung in der UI.
Mehrschichtige Absicherung wirkt aufwendig. Sicherheit hat aber selten eine Silver Bullet. Wird eine Schicht umgangen, greifen die anderen – das ist der zuverlässige Weg.
Nur Middleware vs. mehrschichtige Absicherung
| Vergleichsdimension | Nur Middleware | Mehrschichtige Absicherung |
|---|---|---|
| Sicherheit | Niedrig, umgehbar | Hoch, mehrere Fallback-Schichten |
| Entwicklungsaufwand | Niedrig, eine Konfiguration | Mittel, Prüfungen an mehreren Stellen |
| Wartbarkeit | Schlecht, Logik verstreut | Gut, zentrale Konfiguration |
| Benutzererfahrung | Mittel, Fehler erst nach Klick | Gut, unberechtigte Funktionen vorab ausgeblendet |
| Audit-freundlich | Schlecht, ein Prüfpunkt | Gut, jede Schicht dokumentiert |
Mehrschichtige Absicherung kostet etwas mehr Aufwand – die Vorteile sind konkret.
Sofort handeln
Wenn Sie an einem Next.js-Projekt arbeiten, prüfen Sie jetzt:
- Basieren Berechtigungen nur auf Middleware?
- Haben API Routes und Server Actions eigene Berechtigungsprüfungen?
- Sind Berechtigungsregeln über viele Dateien verstreut?
Trifft eine Antwort zu, planen Sie den Umbau auf mehrschichtige Absicherung. Nicht alles auf einmal – beginnen Sie mit den kritischsten Datenzugriffen und erweitern Sie schrittweise.
Bei Sicherheit gilt: Früher reparieren ist besser als später.
Noch Fragen?
Dieser Artikel deckt Kernarchitektur und Praxis ab, aber nicht jeden Fall. Wenn Sie in Projekten auf andere Herausforderungen stoßen, zum Beispiel:
- Datenebene-Berechtigungen (nur eigene Datensätze sehen)
- Prisma Row-Level Security
- Mandantenisolierung in Multi-Tenant-Systemen
hinterlassen Sie einen Kommentar – gemeinsam finden wir Lösungen.
Berechtigungskontrolle wird nie irrelevant. Hoffentlich ersparen Ihnen diese Hinweise ein paar typische Fehler.
Vollständiger Workflow für mehrschichtigen Next.js-Berechtigungsschutz
Von Middleware-Basisprüfung über getServerSession-Detailvalidierung bis zur Datenbank-Berechtigungsprüfung
⏱️ Estimated time: 3 hr
- 1
Step 1: Schicht 1: Middleware-Basisprüfung
middleware.ts erstellen:
```ts
import { withAuth } from 'next-auth/middleware'
export default withAuth({
pages: {
signIn: '/login'
}
})
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
```
Funktion:
• Prüft, ob der Benutzer eingeloggt ist
• Nicht eingeloggte Benutzer werden zur Login-Seite umgeleitet
• Schnell und früh im Request-Lebenszyklus
Einschränkungen:
• Kann umgangen werden (x-middleware-subrequest-Schwachstelle)
• Nur Grobfilter möglich
• Keine detaillierte Berechtigungsprüfung
Wichtig: Middleware ist die erste Schutzschicht, aber nicht die einzige. - 2
Step 2: Schicht 2: getServerSession-Detailvalidierung
In der Seite verwenden:
```tsx
import { getServerSession } from 'next-auth'
import { authOptions } from '@/app/api/auth/[...nextauth]/route'
export default async function DashboardPage() {
const session = await getServerSession(authOptions)
if (!session) {
redirect('/login')
}
// Benutzerrolle prüfen
if (session.user.role !== 'admin') {
redirect('/unauthorized')
}
return <div>Dashboard</div>
}
```
Funktion:
• Detaillierte Identitätsvalidierung
• Benutzerrollen prüfen
• Feingranulare Berechtigungskontrolle
Vorteile:
• Sicherer als Middleware allein
• Detaillierte Berechtigungsprüfung möglich
• Nicht umgehbar
Wichtig: getServerSession ist die zweite Schutzschicht für Detailvalidierung. - 3
Step 3: Schicht 3: Datenbank-Berechtigungsprüfung
In API Route prüfen:
```ts
import { getServerSession } from 'next-auth'
import { db } from '@/lib/db'
export async function GET(request: Request) {
const session = await getServerSession(authOptions)
if (!session) {
return new Response('Unauthorized', { status: 401 })
}
// Datenbank-Berechtigungsprüfung
const user = await db.user.findUnique({
where: { id: session.user.id },
include: { role: { include: { permissions: true } } }
})
const hasPermission = user.role.permissions.some(
p => p.resource === 'users' && p.action === 'read'
)
if (!hasPermission) {
return new Response('Forbidden', { status: 403 })
}
// Daten zurückgeben
return Response.json({ users: [...] })
}
```
Funktion:
• Finale Berechtigungsvalidierung
• Berechtigungen in der Datenbank prüfen
• Datensicherheit gewährleisten
Wichtig: Die Datenbank ist die dritte Schutzschicht für finale Validierung. - 4
Step 4: RBAC-Berechtigungssystem implementieren
Datenbank-Schema:
```prisma
model User {
id String @id @default(cuid())
email String @unique
role Role @relation(fields: [roleId], references: [id])
roleId String
}
model Role {
id String @id @default(cuid())
name String @unique
permissions Permission[]
users User[]
}
model Permission {
id String @id @default(cuid())
resource String
action String
roles Role[]
}
```
Berechtigung prüfen:
```ts
async function checkPermission(
userId: string,
resource: string,
action: string
) {
const user = await db.user.findUnique({
where: { id: userId },
include: { role: { include: { permissions: true } } }
})
return user.role.permissions.some(
p => p.resource === resource && p.action === action
)
}
```
Wichtig:
• Benutzer haben Rollen
• Rollen haben Berechtigungen
• Berechtigungen steuern Ressourcenzugriff
FAQ
Warum darf Berechtigungskontrolle nicht nur auf Middleware basieren?
Schwachstellenfall:
• CVE-2025-29927: Angreifer können durch den Request-Header x-middleware-subrequest die Middleware-Prüfung umgehen
• Mit Postman direkt Backend-APIs aufrufen und geschützte Daten abrufen
Middleware-Positionierung:
• Läuft auf Edge Runtime, macht Grobfilter
• Prüft Login-Status und Benutzerrolle
• Schnell und früh, allein aber nicht ausreichend
Lösung: Mehrschichtige Absicherung
• Schicht 1: Middleware (Basisprüfung)
• Schicht 2: getServerSession (Detailvalidierung)
• Schicht 3: Datenbank-Berechtigungsprüfung (finale Validierung)
Wichtig: Drei Schichten für Sicherheit – keine darf fehlen.
Was ist der Unterschied zwischen Middleware und getServerSession?
• Läuft auf Edge Runtime
• Schnell und früh im Request-Lebenszyklus
• Nur Grobfilter (Login-Status, Benutzerrolle)
• Kann umgangen werden (x-middleware-subrequest-Schwachstelle)
getServerSession:
• Läuft auf Node.js Runtime
• Detaillierte Validierung möglich
• Benutzerrollen und Berechtigungen prüfen
• Nicht umgehbar
Gemeinsame Nutzung:
• Middleware für Basisprüfung (Schicht 1)
• getServerSession für Detailvalidierung (Schicht 2)
• Datenbank für finale Berechtigungsprüfung (Schicht 3)
Codebeispiel:
```ts
// middleware.ts (Schicht 1)
export default withAuth({
pages: { signIn: '/login' }
})
// page.tsx (Schicht 2)
const session = await getServerSession(authOptions)
if (session.user.role !== 'admin') {
redirect('/unauthorized')
}
// api/route.ts (Schicht 3)
const hasPermission = await checkPermission(userId, 'users', 'read')
if (!hasPermission) {
return new Response('Forbidden', { status: 403 })
}
```
Wie implementiert man ein RBAC-Berechtigungssystem?
Datenbank-Schema:
```prisma
model User {
id String @id
email String @unique
role Role @relation(fields: [roleId], references: [id])
roleId String
}
model Role {
id String @id
name String @unique
permissions Permission[]
users User[]
}
model Permission {
id String @id
resource String // Ressource: users, posts usw.
action String // Aktion: read, write, delete
roles Role[]
}
```
Berechtigung prüfen:
```ts
async function checkPermission(
userId: string,
resource: string,
action: string
) {
const user = await db.user.findUnique({
where: { id: userId },
include: { role: { include: { permissions: true } } }
})
return user.role.permissions.some(
p => p.resource === resource && p.action === action
)
}
```
Wichtig:
• Benutzer haben Rollen
• Rollen haben Berechtigungen
• Berechtigungen steuern Ressourcenzugriff
Wie implementiert man eine mehrschichtige Absicherungsarchitektur?
Schicht 1: Middleware (Basisprüfung)
```ts
// middleware.ts
export default withAuth({
pages: { signIn: '/login' }
})
```
Schicht 2: getServerSession (Detailvalidierung)
```tsx
// page.tsx
const session = await getServerSession(authOptions)
if (session.user.role !== 'admin') {
redirect('/unauthorized')
}
```
Schicht 3: Datenbank-Berechtigungsprüfung (finale Validierung)
```ts
// api/route.ts
const hasPermission = await checkPermission(userId, 'users', 'read')
if (!hasPermission) {
return new Response('Forbidden', { status: 403 })
}
```
Wichtig:
• Drei Schichten für Sicherheit
• Keine Schicht darf fehlen
• Middleware macht Grobfilter, getServerSession Feinfilter, Datenbank finale Validierung
Wie schützt man sich vor der x-middleware-subrequest-Schwachstelle?
Schutzmaßnahmen:
• Nicht nur auf Middleware verlassen
• Berechtigungen auch in API Routes prüfen
• getServerSession zur Validierung nutzen
• Datenbank für finale Berechtigungsprüfung
Codebeispiel:
```ts
// api/route.ts
export async function GET(request: Request) {
// Auch wenn Middleware umgangen wird, wird hier geprüft
const session = await getServerSession(authOptions)
if (!session) {
return new Response('Unauthorized', { status: 401 })
}
// Datenbank-Berechtigungsprüfung
const hasPermission = await checkPermission(session.user.id, 'users', 'read')
if (!hasPermission) {
return new Response('Forbidden', { status: 403 })
}
return Response.json({ users: [...] })
}
```
Wichtig:
• Mehrschichtige Absicherung
• API Routes müssen Berechtigungen prüfen
• Datenbank für finale Validierung
Empfehlung: Niemals nur auf Middleware verlassen – Berechtigungen auch in API Routes prüfen.
Was sind Best Practices für Berechtigungskontrolle?
• Middleware für Basisprüfung (Schicht 1)
• getServerSession für Detailvalidierung (Schicht 2)
• Datenbank für finale Berechtigungsprüfung (Schicht 3)
RBAC-System:
• Benutzer haben Rollen
• Rollen haben Berechtigungen
• Berechtigungen steuern Ressourcenzugriff
Code-Organisation:
• Hilfsfunktionen für Berechtigungsprüfung erstellen
• Einheitlich in API Routes verwenden
• Code-Duplikation vermeiden
Sicherheitsempfehlungen:
• Niemals nur auf Middleware verlassen
• Berechtigungen auch in API Routes prüfen
• Datenbank für finale Validierung
• Berechtigungskonfiguration regelmäßig prüfen
Wichtig:
• Mehrschichtige Absicherung für Sicherheit
• Keine Schicht darf fehlen
• Berechtigungssystem kontinuierlich verbessern
Merken: Berechtigungskontrolle ist die Basis der Sicherheit – nicht unterschätzen.
13 Min. Lesezeit · Veröffentlicht am: 19. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js Middleware Praxisleitfaden: Pfad-Matching, Edge-Runtime-Einschränkungen und häufige Fallstricke
Vom Produktions-Bug zur vollständigen Lösung: Next.js Middleware matcher-Konfiguration, Edge-Runtime-Limits und drei Praxisszenarien – damit Sie die häufigsten Fallstricke vermeiden
Teil 11 von 51
Nächster
Next.js OAuth-Anmeldung: Google, GitHub, WeChat – Konfiguration und Best Practices
OAuth 2.0 verständlich erklärt: Schritt-für-Schritt-Konfiguration von Google-, GitHub- und WeChat-Login in Next.js – inklusive Lösungen für Callback-Fehler und Sicherheitsfallen.
Teil 13 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen