Zum Hauptinhalt springen

Session-Sicherheit und User-Validierung

Übersicht​

Das System implementiert eine mehrschichtige Sicherheitsarchitektur, um sicherzustellen, dass nur aktive und gültige User auf geschützte Inhalte zugreifen können.

Rollen und Berechtigungen​

UserRole Enum​

enum UserRole {
ADMIN // Systemrolle — bypasses alle Permission-Checks
MEMBER // Standardrolle — benötigt explizit zugewiesene Custom-Roles
}

ADMIN ist eine Systemrolle und nicht mit fachlichen Rollen (z. B. „Lehrer", „Schulleiter") gleichzusetzen. MEMBER war früher USER — Migration erfolgte mit ALTER TYPE "UserRole" RENAME VALUE 'USER' TO 'MEMBER'.

is_owner Flag​

Das is_owner Boolean @default(false) Feld auf dem User-Modell kennzeichnet den Tenant-Inhaber:

  • Exakt ein User pro Tenant hat is_owner = true
  • Der Owner kann nicht gelöscht werden (deleteUserById wirft DatabaseError)
  • Der Owner kann nicht zu MEMBER degradiert werden (updateUserById wirft DatabaseError)
  • Inhaberschaft kann per transferOwnership() übertragen werden — nur an einen anderen ADMIN

is_owner in JWT und Session​

is_owner wird beim Login in den JWT geschrieben und bei jeder JWT-Validierung aus der DB aktualisiert:

// jwt-Callback in auth.ts — bei initialem Login und GitHub OAuth
token.isOwner = dbUser.is_owner ?? false;

// session-Callback — Weitergabe an Frontend
session.user.isOwner = (token.isOwner as boolean) ?? false;

Die Session-Typdefinition:

declare module 'next-auth' {
interface Session {
user: {
id: string;
email: string;
role: UserRole; // 'ADMIN' | 'MEMBER'
status: Status;
tenantId: string;
isOwner: boolean; // true = Tenant-Inhaber
emailVerifiedAt?: Date;
};
}
}

Sicherheitsebenen​

1. JWT-Callback (auth.ts)​

  • Wann: Bei jeder JWT-Anfrage
  • Was: Prüft, ob der User noch in der Datenbank existiert und aktiv ist; synchronisiert role, isOwner und tenantId
  • Aktion: Gibt leeren Token {} zurück, wenn der User inaktiv oder gelöscht wurde

2. Session-Callback (auth.ts)​

  • Wann: Bei jeder Session-Anfrage
  • Was: Überträgt alle Token-Felder inklusive isOwner in die Session
  • Aktion: Gibt eine leere Session zurück, wenn der Token ungültig ist

3. Middleware (proxy.ts)​

Die Next.js-Middleware liegt in webapp/proxy.ts (in Next.js 15.5 wurde die Konventionsdatei von middleware.ts zu proxy.ts umbenannt; die Funktion heißt export default async function proxy(req)).

  • Wann: Vor dem Zugriff auf geschützte Routen
  • Was: Prüft den Session-Status ohne DB-Abfrage
  • Aktion: Leitet zum Login weiter, wenn der User inaktiv ist

Funktionsweise​

Automatische Session-Validierung​

// Bei jeder JWT-Anfrage wird geprüft:
if (token.id && token.email && token.status) {
const dbUser = await prisma.user.findFirst({
where: { id: token.id, email: token.email }
});

if (!dbUser || dbUser.status !== Status.active) {
// Leerer Token invalidiert die Session
return {};
}

// Aktuelle DB-Werte in den Token schreiben
token.role = dbUser.role;
token.status = dbUser.status;
token.tenantId = dbUser.tenant_id?.toString() || '';
token.isOwner = dbUser.is_owner ?? false;
}

Session-Status-Behandlung​

  • User gelöscht: Leerer Token {} → Session wird invalidiert
  • User inaktiv: Leerer Token {} → Session wird invalidiert
  • User archiviert: Leerer Token {} → Session wird invalidiert
  • DB-Fehler: Leerer Token {} → Session wird invalidiert (fail-safe)

Middleware-Validierung (proxy.ts)​

// Einfache Prüfung ohne DB-Abfrage
if (isProtectedRoute && isAuthenticated && session!.user.status !== 'active') {
return NextResponse.redirect(new URL('/login', req.nextUrl));
}

Vereinfachte Login-Logik​

  • Credentials-Login: Inaktive User werden direkt abgewiesen (return false)
  • GitHub OAuth: Inaktive User werden zur Login-Seite weitergeleitet (return '/login?error=auth_failed')
  • Keine separaten Deaktivierungs-Seiten: Einfache und konsistente Behandlung
  • Generische Fehlermeldungen: Alle Fehler zeigen die gleiche, sichere Meldung
  • Maximale Sicherheit: Keine Information über existierende User oder deren Status
  • Einheitliche Weiterleitung: Alle Fehler führen zur Login-Seite

Berechtigungssystem​

Das granulare Permission-System sitzt oberhalb der Session-Sicherheit. Während die Session-Sicherheit den Zugang zum Backend grundsätzlich schützt, regelt das Permission-System den Zugriff auf einzelne Ressourcen.

Vollständige Dokumentation: Berechtigungssystem (permissions.md)

Kurzzusammenfassung​

RolleVerhalten
ADMINBypasses alle Permission-Checks automatisch
MEMBERBenötigt explizit zugewiesene Custom-Roles mit Permissions

Fehlender Zugriff führt zu redirect('/dashboard?no_access=1') — nicht zu einem HTTP-Fehler.

Session-Zugriff in Server Components​

import { auth } from '@/auth';
import { hasPermission } from '@/lib/permissions';
import { redirect } from 'next/navigation';

export default async function MyPage() {
const session = await auth();
if (!session?.user) redirect('/login');

const allowed = await hasPermission(session, 'students', 'read');
if (!allowed) redirect('/dashboard?no_access=1');

// ...
}

Vorteile der neuen Architektur​

Keine Endlosschleifen​

  • JWT-Callback: Setzt nur den Status, löscht nicht die Session
  • Middleware: Prüft den bereits gesetzten Status ohne DB-Abfrage
  • Effizient: Weniger Datenbankzugriffe

Bessere Performance​

  • Keine redundanten DB-Abfragen in der Middleware
  • Status wird im Token gespeichert und wiederverwendet
  • Schnellere Route-Validierung

Robuste Fehlerbehandlung​

  • Session bleibt bestehen bis zur nächsten JWT-Validierung
  • Keine abrupten Session-Abbrüche
  • Graceful Degradation bei DB-Problemen

Admin-Tools​

Session-Bereinigung​

  • Route: /api/auth/cleanup-sessions
  • Zugriff: Nur Administratoren
  • Funktion: Löscht alle Sessions für inaktive User

Session-Statistiken​

  • Route: GET /api/auth/cleanup-sessions
  • Zugriff: Nur Administratoren
  • Funktion: Zeigt Übersicht über aktive/inaktive User und Sessions

Verwendung​

Manuelle Session-Bereinigung​

# Alle Sessions für inaktive User löschen
curl -X POST /api/auth/cleanup-sessions \
-H "Authorization: Bearer <admin-token>"

Session-Statistiken abrufen​

# Statistiken über User und Sessions
curl GET /api/auth/cleanup-sessions \
-H "Authorization: Bearer <admin-token>"

Utility-Funktionen​

isUserActive(userId, email)​

Prüft, ob ein User existiert und aktiv ist.

userExists(userId, email)​

Prüft, ob ein User existiert (unabhängig vom Status).

getUserStatus(userId, email)​

Holt den aktuellen Status eines Users.

Konfiguration​

Session-Lebensdauer​

session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 Tage
}

JWT-Lebensdauer​

jwt: {
maxAge: 30 * 24 * 60 * 60, // 30 Tage
}

Monitoring und Logging​

Logs​

  • User-Validierung: User ${email} existiert nicht mehr oder ist inaktiv. Session wird gelöscht.
  • Middleware-Validierung (proxy.ts): Keine DB-Abfragen, nur Status-Prüfung
  • Fehler: Fehler bei der User-Validierung: ${error}

Dashboard​

Die SessionManagement-Komponente bietet eine visuelle Übersicht über:

  • Gesamtanzahl User
  • Aktive/Inaktive User
  • Aktive Sessions
  • OAuth Accounts

Sicherheitsaspekte​

Vorteile​

  • Echtzeit-Validierung: User-Status wird bei jeder Anfrage geprüft
  • Mehrschichtige Sicherheit: Mehrere Validierungsebenen
  • Keine Endlosschleifen: Optimierte Architektur
  • Admin-Kontrolle: Manuelle Session-Verwaltung möglich

Best Practices​

  • Status-basierte Validierung: Keine DB-Abfragen in der Middleware
  • Token-basierte Sicherheit: Status wird im JWT gespeichert
  • Effiziente Middleware: Schnelle Route-Validierung
  • Admin-Tools: Für manuelle Kontrolle

Sicherheitsfeatures​

  • Lautlose Fehlerbehandlung: Keine Information über existierende User
  • Einheitliche Fehlermeldungen: Alle Login-Fehler werden gleich behandelt
  • Keine User-Enumeration: Angreifer können nicht validieren, ob Emails existieren
  • Keine Status-Preisgabe: User-Status wird nicht verraten
  • Enterprise-Sicherheit: Erfüllt Sicherheitsstandards großer Plattformen

Troubleshooting​

Häufige Probleme​

  1. ERR_TOO_MANY_REDIRECTS

    • Ursache: Endlosschleife zwischen proxy.ts und Auth
    • Lösung: Status-basierte Validierung ohne DB-Abfragen
  2. User wird nach Login sofort abgemeldet

    • Ursache: User-Status ist nicht active in der Datenbank
    • Lösung: Prüfen Sie den User-Status in der DB
  3. Session wird nicht gelöscht

    • Ursache: JWT-Callback läuft nicht oder DB-Fehler
    • Lösung: Überprüfen Sie die Logs und DB-Verbindung

Debugging​

// Logs aktivieren
console.log('Session-Daten:', session);
console.log('Token-Daten:', token);
console.log('User-Status:', session?.user?.status);

// proxy.ts-Debugging
console.log('Route:', path);
console.log('Session vorhanden:', !!session);
console.log('User-Status:', session?.user?.status);

Architektur-Übersicht​

Request → proxy.ts → NextAuth → JWT-Callback → Session-Callback
↓ ↓ ↓ ↓ ↓
Route-Check Status Token-Valid. DB-Check Session-Format
↓ ↓ ↓ ↓ ↓
Redirect Status Status-Set Status-Update Response

Datenfluss​

  1. Request kommt an
  2. proxy.ts (Middleware) prüft Route und Session-Status (ohne DB)
  3. NextAuth validiert den JWT-Token
  4. JWT-Callback prüft User-Status in der DB
  5. Session-Callback formatiert die Session
  6. Response wird zurückgegeben