Moderne Web‑Anwendungen, die mit Node.js und Next.js gebaut werden, bedienen häufig Hunderte bis Tausende von Benutzern mit unterschiedlichen Berechtigungsstufen. Eine unzureichende Zugriffskontrolle ist einer der häufigsten Gründe für Sicherheitsverletzungen – klassifiziert im OWASP Top 10 als Broken Access Control. In diesem Artikel stellen wir einen praktischen Ansatz zum Aufbau eines RBAC‑Systems (Role‑Based Access Control) auf Basis von NextAuth vor, das den Anforderungen von OWASP, CWE und dem Prinzip des Least‑Privilege entspricht.
Bedrohungsmodell – was kann schiefgehen?
Der grundlegende Angriffsvektor besteht darin, unautorisierten Zugriff auf Ressourcen zu erlangen, die geschützt sein sollten. Typische Szenarien sind:
- Ein Benutzer mit niedrigerer Berechtigungsstufe (z. B. Gast) erhält Zugriff auf einen Endpunkt, der ausschließlich für Administratoren vorgesehen ist.
- Manipulation von Parametern in der URL oder im Request‑Body ermöglicht das Umgehen der Berechtigungsprüfung.
- Eine Sitzung wird abgefangen und nach Ablauf oder nach einer Rollenänderung weitergenutzt.
All diese Fälle sind in CWE‑284 (Improper Access Control) und CWE‑285 (Improper Authorization) beschrieben. Ein wirksamer Schutz erfordert konsequente Berechtigungsprüfungen auf jeder Ebene der Anwendung – von der Routing‑Schicht bis zur Daten‑Schicht.
Mechanismus – wie funktioniert RBAC in NextAuth?
NextAuth liefert ein fertiges Authentifizierungssystem (Session, JWT) sowie Hooks, die das Einbringen eigener Autorisierungslogik ermöglichen. Schlüsselkomponenten:
callbacks.jwt– erweitert das Token um die Rollenliste, die aus der Datenbank geladen wird.callbacks.session– stellt die Rollen im Objektsession.userauf der Client‑Seite bereit.- Middleware (
next.config.jsoder eine dedizierte Dateimiddleware.ts) – prüft zentral, ob der aktuelle Benutzer die erforderlichen Berechtigungen für den jeweiligen Pfad besitzt.
In Kombination mit einer einfachen Rollen‑ und Berechtigungstabelle in der Datenbank (z. B. PostgreSQL) erhalten wir ein vollständiges RBAC‑Modell, das sich leicht um zusätzliche Attribute (z. B. Organisations‑Scope) erweitern lässt.
Beispiel: verwundbarer Code vs. verbesserte Implementierung
Zu Beginn schauen wir uns einen Code‑Abschnitt an, in dem keine Rollenprüfung erfolgt – ein typischer Fehler, der zu Broken Access Control führt.
// vulnerable API endpoint in Next.js (pages/api/admin/users.ts)
import { getSession } from 'next-auth/react';
export default async function handler(req, res) {
const session = await getSession({ req });
// we only check if the user is logged in
if (!session) {
return res.status(401).json({ error: 'Unauthorized' });
}
// perform administrative operation
const users = await db.user.findMany();
res.status(200).json(users);
}
Jeder angemeldete Benutzer, unabhängig von seiner Rolle, kann die komplette Benutzerliste abrufen – ein klassisches Beispiel für CWE‑285.
Die verbesserte Version nutzt die im Token gespeicherten Rollen und eine zentrale Middleware.
// middleware.ts – global access control
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
export async function middleware(req) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
const { pathname } = req.nextUrl;
// map paths to required roles
const roleMap = {
'/api/admin': ['admin'],
'/api/manager': ['admin', 'manager'],
};
for (const [prefix, roles] of Object.entries(roleMap)) {
if (pathname.startsWith(prefix)) {
if (!token || !roles.some(r => token.roles?.includes(r))) {
return NextResponse.redirect(new URL('/unauthorized', req.url));
}
}
}
return NextResponse.next();
}
Jetzt haben nur noch Benutzer mit der Rolle admin Zugriff auf /api/admin. Zusätzlich können wir im eigentlichen Handler eine kontextbezogene Validierung durchführen.
Checklisten – was Sie prüfen müssen, um Broken Access Control zu vermeiden
- Stellen Sie sicher, dass das JWT die Rollenliste enthält und mit einem starken Algorithmus (HS256 oder RS256) signiert ist.
- Alle API‑Endpunkte und geschützten Seiten müssen durch Middleware oder dedizierte Autorisierungs‑Funktionen laufen.
- Wenden Sie das Prinzip des Least‑Privilege an – vergeben Sie nur das minimal notwendige Berechtigungsset für jede Aufgabe.
- Testen Sie Berechtigungen regelmäßig mit Penetration‑Testing‑Tools (z. B. OWASP ZAP) und automatischen statischen Scannern.
- Loggen Sie Zugriffsverweigerungen mit Hinweis auf die fehlende Rolle, geben Sie jedoch keine Details zu den Ressourcen preis.
„Die beste Zugriffskontrolle ist die, die nicht umgangen werden kann, weil es keinen Ort gibt, an dem man sie umgehen könnte.“
Typische Fehler und Kompromisse bei der RBAC‑Implementierung
Even experienced teams popełniają pewne pułapki:
- Hardcoding von Rollen im Code – erschwert spätere Erweiterungen und führt zu Inkonsistenzen zwischen Code und Datenbank.
- Kein Token‑Refresh nach Rollenänderung – der Benutzer kann alte Berechtigungen bis zum Ablauf der Sitzung behalten.
- Nur Front‑End‑Verifizierung verwenden – schützt nicht vor direktem Aufruf des Endpunkts.
- Übermäßig permissive Standardregeln – z. B. das Zuweisen der Rolle
userzu allen neuen Konten ohne zusätzliche Prüfung.
Lösungen:
- Speichere Rollen‑ und Berechtigungsdefinitionen in einer zentralen Tabelle, nicht im Code.
- Implementiere einen Revocation‑List‑Mechanismus oder kurze TTL‑Tokens, um nach Rollenänderungen ein Refresh zu erzwingen.
- Überprüfe Berechtigungen immer serverseitig – das Front‑End darf nur die UI darstellen.
- Verwende das Muster „deny‑by‑default“ – fehlende explizite Erlaubnis = Verweigerung.
Praktisches Beispiel: Vollständiger Ablauf von der Anmeldung bis zur Autorisierung
Nachfolgend ein komplettes Mini‑Projekt in TypeScript, das NextAuth, PostgreSQL und RBAC verbindet.
// lib/prisma.ts – Prisma‑Client (ORM)
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();
// prisma schema (gekürzt)
model User {
id Int @id @default(autoincrement())
email String @unique
password String
roles Role[] @relation("UserRoles")
}
model Role {
id Int @id @default(autoincrement())
name String @unique
users User[] @relation("UserRoles")
}
// pages/api/auth/[...nextauth].ts – NextAuth‑Konfiguration
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { prisma } from '../../../lib/prisma';
export default NextAuth({
providers: [
CredentialsProvider({
async authorize(credentials) {
const user = await prisma.user.findUnique({
where: { email: credentials.email },
include: { roles: true },
});
if (user && /* hier Passwort‑Prüfung */ true) {
return { id: user.id, email: user.email, roles: user.roles.map(r => r.name) };
}
return null;
},
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) token.roles = user.roles; // Rollen im JWT speichern
return token;
},
async session({ session, token }) {
session.user.roles = token.roles as string[];
return session;
},
},
secret: process.env.NEXTAUTH_SECRET,
});
// middleware.ts – Zugriffskontrolle
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
export async function middleware(req) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
const url = req.nextUrl.clone();
const protectedRoutes = [
{ prefix: '/admin', roles: ['admin'] },
{ prefix: '/manager', roles: ['admin', 'manager'] },
];
for (const route of protectedRoutes) {
if (url.pathname.startsWith(route.prefix)) {
if (!token?.roles?.some(r => route.roles.includes(r))) {
url.pathname = '/unauthorized';
return NextResponse.rewrite(url);
}
}
}
return NextResponse.next();
}
Dieser Code erfüllt alle oben genannten Prinzipien: Rollen werden in der Datenbank gespeichert, das Token wird um Rollen erweitert, das Middleware erzwingt den Zugriff basierend auf der protectedRoutes-Map, und die UI kann session.user.roles nutzen, um Elemente zu verbergen.
Fazit und nächste Schritte
Die Implementierung eines soliden Zugriffskontrollsystems in Node.js und Next.js erfordert keine komplexen Lösungen – entscheidend ist die konsequente Nutzung von Middleware, das Speichern von Rollen in einem sicheren Token sowie die Einhaltung der Prinzipien Least‑Privilege und Deny‑by‑Default. Regelmäßige Code‑Reviews, Autorisierungstests und die Aktualisierung der Dokumentation helfen, die Sicherheit auf dem Niveau von OWASP Top 10 und CWE‑284/285 zu halten.
Wenn du Unterstützung bei der Prüfung bestehender Anwendungen benötigst oder von Grund auf eine sichere Architektur mit NextAuth und RBAC aufbauen willst, kontaktiere das Team von Coderia.it – wir helfen dir, deine Lösungen in ein solides Fundament für zukünftiges Wachstum zu verwandeln.



