Files
boilerplate/docs/security.md
Bastian Wagner 543e8273a7 initial
2026-07-16 09:49:22 +02:00

118 lines
4.1 KiB
Markdown

# Security-Modell
Das Boilerplate trennt Browser, Backend und Identity Provider strikt. Der Browser
bekommt keine OIDC-Tokens. Das Backend ist fuer Authentifizierung,
Autorisierung, CSRF und Session-Verwaltung verbindlich.
## OIDC
Der Login nutzt Authorization Code Flow mit PKCE:
1. Browser ruft `/api/auth/login` auf.
2. Backend erzeugt `state`, `nonce`, `code_verifier` und leitet zum IdP weiter.
3. IdP ruft `/api/auth/callback` mit `code` und `state` auf.
4. Backend tauscht den Code gegen Tokens.
5. Backend validiert ID Token, Issuer, Audience, Nonce und Algorithmus.
6. Backend laedt optional UserInfo.
7. Backend legt oder aktualisiert den lokalen Benutzer.
8. Backend erzeugt eine serverseitige Session.
Erlaubte Signaturalgorithmen werden ueber `OIDC_ALLOWED_ALGORITHMS` gesetzt.
`none` ist explizit verboten.
## Sessions
Sessions liegen in MySQL. Das Session-Cookie enthaelt keine Tokens oder
Benutzerdaten, sondern nur eine signierte Session-ID.
Gespeichert werden unter anderem:
- verschluesselte OIDC-Tokens
- Hash des CSRF-Tokens
- Ablaufzeiten fuer Idle Timeout und absolutes Timeout
- User-Agent und IP fuer Anzeige und Audit-Kontext
- Revocation-Zeitpunkt
Die Token-Verschluesselung nutzt `SESSION_ENCRYPTION_KEY`. Cookie-Signaturen
nutzen `SESSION_SECRET`. Beide Werte muessen in Produktion echte Secrets sein.
## CSRF
Schreibende Methoden `POST`, `PUT`, `PATCH` und `DELETE` brauchen ein gueltiges
CSRF-Token. Das Frontend liest das CSRF-Cookie und sendet es als
`X-CSRF-Token`. Das Backend vergleicht nur den Hash gegen die Session.
Public Routes sind vom CSRF-Guard ausgenommen, sofern sie mit `@Public()`
markiert sind.
## Rollen und Permissions
Permissions werden in `apps/backend/src/roles/permissions.ts` definiert. Rollen
sind Datenbankdaten und referenzieren diese Permissions. Benutzer haben keine
direkten Permissions.
Backend-Controller schuetzen Endpunkte mit:
```ts
@RequirePermissions(Permission.ItemsRead)
```
Angular nutzt Permissions nur fuer Navigation und Darstellung. Eine versteckte
Schaltflaeche ist keine Sicherheitsgrenze.
## Benutzerstatus
Deaktivierte Benutzer werden trotz erfolgreichem IdP-Login abgewiesen. Aktive
Sessions deaktivierter Benutzer werden beim Session-Resolve unbrauchbar gemacht.
## Cookies und Proxy
In Produktion sollte TLS vor der Anwendung terminiert werden, zum Beispiel mit
Nginx. Setze `TRUST_PROXY=true`, wenn die Anwendung hinter einem vertrauenswuerdigen
Reverse Proxy laeuft und korrekte Forwarded Header benoetigt.
`APP_BASE_URL` muss die externe URL der Anwendung enthalten, weil daraus die OIDC
Callback URL gebaut wird.
## Logging und Redaction
Das Backend redigiert sensible Werte in Standard-HTTP-Logs:
- `Authorization`
- `Cookie`
- `X-CSRF-Token`
- `Set-Cookie`
- Access-, Refresh- und ID-Tokens
Neue Logs duerfen keine Secrets, Tokens, Session-IDs oder vollstaendige Cookies
enthalten. Fuer Auditing sollten fachliche IDs und Request IDs verwendet werden.
## Swagger
`SWAGGER_ENABLED` ist in Produktion standardmaessig `false`. Wenn Swagger in
Produktion aktiviert wird, sollte der Zugriff ueber Netzwerkregeln oder Reverse
Proxy zusaetzlich eingeschraenkt werden.
## Rate Limiting
Das konfigurierte Rate Limiting ist fuer eine einzelne Containerinstanz ohne
Redis ausgelegt. Die Zaehler liegen im Prozessspeicher. Bei horizontaler
Skalierung muss ein gemeinsamer Store eingefuehrt oder der Schutz an den Reverse
Proxy verlagert werden.
Es gibt zwei fruehe Ebenen:
- Globales IP-Limit fuer alle Requests, bevor Guards und Controller laufen.
- Strengeres IP-Limit fuer sensible Endpunkte, ebenfalls bevor Session-Resolve
und fachliche Datenbankzugriffe laufen.
Sensible Endpunkte werden mit `@SensitiveRateLimit()` markiert. Dazu gehoeren
Login, OIDC Callback, Logout, Session-Revoke sowie Benutzer- und
Rollenverwaltung. Bei Ueberschreitung antwortet die API mit HTTP 429,
`RATE_LIMIT_EXCEEDED`, `Retry-After` und `X-RateLimit-*` Headern.
Ein zusaetzliches Benutzer-Limit waere technisch moeglich, wuerde in diesem
Projekt aber erst nach dem serverseitigen Session-Resolve greifen. Fuer die
geforderte fruehe Lastreduktion ist deshalb das globale IP-Limit die verbindliche
Schutzschicht.