TL;DR
OAuth 2.0 ist der De-facto-Standard für delegierte Autorisierung, birgt jedoch bei fehlerhafter Implementierung erhebliche Sicherheitsrisiken, die von Account-Takeover bis zur vollständigen API-Kompromittierung reichen können. Häufige Schwachstellen umfassen offene Weiterleitungen, bei denen der Authorization Code an Angreifer gesendet wird, wenn der Authorization Server beliebige `redirect_uri`s akzeptiert. Ein fehlender CSRF-Schutz durch den `state`-Parameter kann dazu führen, dass Angreifer User-Accounts mit ihren eigenen verknüpfen. Für mobile Apps und SPAs ist der Schutz vor Authorization Code Interception mittels PKCE (Proof Key for Code Exchange) unerlässlich, da sonst abgefangene Codes missbraucht werden könnten. Zudem stellt die unsichere Speicherung von Access Tokens, beispielsweise in `localStorage`, ein XSS-Risiko dar. Schützen Sie sich
Diese Zusammenfassung wurde KI-gestützt erstellt (EU AI Act Art. 52).
Inhaltsverzeichnis (4 Abschnitte)
OAuth 2.0 ist überall: “Login with Google”, “Verbinde mit GitHub”, API-Zugriffe für Drittanbieter. Richtig implementiert ist OAuth sicher und bequem. Falsch implementiert entstehen kritische Sicherheitslücken - von Account-Takeover bis zu vollständiger API-Kompromittierung.
OAuth 2.0 Grundlagen - was tatsächlich passiert
OAuth 2.0 Rollen:
Resource Owner: Der User der Zugriffsrechte erteilt
Client: Die Anwendung die Zugriff beantragt (z.B. Kalender-App)
Authorization Server: Stellt Tokens aus (Entra ID, Keycloak, Auth0)
Resource Server: API die mit Tokens geschützt ist (Google Calendar API)
Authorization Code Flow (Standard für Web-Apps):
1. User klickt "Login mit Google"
2. Client → Authorization Server:
GET /authorize?
response_type=code&
client_id=my-app-123&
redirect_uri=https://myapp.com/callback&
scope=email+calendar.read&
state=RANDOM-CSRF-TOKEN ← WICHTIG!
3. User meldet sich an + genehmigt Zugriff
4. Authorization Server → Client (Redirect):
GET https://myapp.com/callback?code=AUTH_CODE&state=RANDOM-CSRF-TOKEN
5. Client → Authorization Server (Back-Channel):
POST /token
code=AUTH_CODE&
redirect_uri=https://myapp.com/callback&
client_id=my-app-123&
client_secret=MY-SECRET ← Nur der echte Client kennt das!
6. Authorization Server → Client:
{"access_token": "eyJ...", "expires_in": 3600}
7. Client → Resource Server:
GET /calendar/events
Authorization: Bearer eyJ...
Warum Authorization Code Flow sicher ist:
→ Auth Code geht über Browser (unsicher), aber nutzlos ohne Client Secret
→ Access Token geht nur via Server-zu-Server (nie zum Browser)
→ state-Parameter: verhindert CSRF!
Die wichtigsten OAuth-Sicherheitsprobleme
Schwachstelle 1: Offene Weiterleitungen (Open Redirects)
Problem:
Authorization Server erlaubt beliebige redirect_uri:
Angriff:
GET /authorize?
response_type=code&
client_id=my-app&
redirect_uri=https://evil.com/steal ← beliebige URL erlaubt!
&state=...
Ergebnis: Authorization Code wird an Angreifer gesendet!
→ Auth Code + Client Secret = vollständiger Account-Takeover
Schutz:
→ redirect_uri MUSS exakt vorregistriert sein
→ Authorization Server muss Exact-Match prüfen (kein Wildcard!)
→ Beispiel: nur "https://myapp.com/callback" erlaubt
NICHT: "https://myapp.com/*"
NICHT: "https://*.myapp.com/callback"
Schwachstelle 2: Fehlendes CSRF-Schutz (state-Parameter)
Problem:
Angreifer erstellt speziell präparierten OAuth-Link:
Schickt User zu: GET /authorize?...&state=ATTACKER-CONTROLLED
Wenn User seinen Account verbindet: Angreifer-Account verbunden!
Schutz:
→ state-Parameter: kryptografisch zufällig, in Session gespeichert
→ Bei Callback: state aus URL = state in Session?
→ Wenn nicht gleich: Angriff erkannt, Prozess abbrechen!
Implementierung (Node.js):
const state = crypto.randomBytes(32).toString('hex');
req.session.oauthState = state;
const authUrl = `${AUTH_SERVER}/authorize?...&state=${state}`;
// Callback:
if (req.query.state !== req.session.oauthState) {
return res.status(400).json({error: 'CSRF detected!'});
}
Schwachstelle 3: Authorization Code Interception
Problem:
Mobile Apps: code kommt via Custom URL Scheme:
myapp://callback?code=AUTH_CODE
Anderes App mit gleichem URL-Scheme: Code abgefangen!
Schutz: PKCE (Proof Key for Code Exchange) - Pflicht für SPAs und Mobile!
code_verifier = 32 random bytes (geheimgehalten!)
code_challenge = BASE64URL(SHA256(code_verifier))
1. Start Authorization:
GET /authorize?
...&
code_challenge=<SHA256_of_verifier>&
code_challenge_method=S256
2. Token Request:
POST /token
code=AUTH_CODE&
code_verifier=ORIGINAL_VERIFIER ← Nur der echte Client kennt das!
→ Abgefangener Auth Code nutzlos ohne code_verifier!
Schwachstelle 4: Unsichere Token-Speicherung
Problem:
SPA speichert Access Token in localStorage:
localStorage.setItem('token', accessToken);
→ XSS-Angriff: document.cookie oder localStorage auslesen → Token gestohlen!
Schutz:
→ Access Token: in memory-Variablen (nicht persistent)
→ Refresh Token: in HttpOnly Cookie (kein JS-Zugriff!)
→ SameSite=Strict: CSRF-Schutz für Cookies
// FALSCH - XSS-anfällig:
localStorage.setItem('access_token', token);
// RICHTIG - Token in Memory:
let accessToken = null; // In-memory, verschwindet bei Reload
// Refresh Token: Server setzt HttpOnly Cookie:
res.cookie('refresh_token', refreshToken, {
httpOnly: true, // JavaScript kann nicht zugreifen
secure: true, // Nur HTTPS
sameSite: 'strict', // CSRF-Schutz
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 Tage
});
Schwachstelle 5: Zu weite Scopes
Problem:
App fordert alle Berechtigungen an:
scope=email+profile+contacts+calendar+drive+gmail
Konsequenz: Kompromittierter Token = Zugriff auf alles!
Schutz:
→ Minimal Scope: nur was aktuell benötigt wird
→ Incremental Authorization: Scope erst bei Bedarf anfordern
→ Scope Transparency: User sieht was App tatsächlich braucht
// Falsch: alles auf einmal
scope: 'email profile contacts calendar drive admin'
// Richtig: nur was benötigt wird
scope: 'email' // Beim Login
// Später wenn User Kalender-Feature nutzt:
scope: 'email calendar.readonly' // Minimal!
OpenID Connect (OIDC) Sicherheit
OIDC = OAuth 2.0 + Authentifizierung (Identität):
Zusätzlich zu Access Token: ID Token (JWT):
ID Token enthält: sub (User ID), email, name, issued at, expiry, nonce
OIDC-Schwachstellen:
1. ID Token nicht validiert:
Problem: App vertraut ID Token ohne Signatur-Prüfung
Angriff: Gefälschter ID Token → anderer User
Schutz:
→ IMMER signierte ID Tokens (RS256 oder ES256)
→ IMMER Signatur prüfen (mit JWKS des Authorization Server)
→ IMMER Expiry prüfen
→ IMMER Audience (aud) prüfen
Python (Keycloak + python-jose):
from jose import jwks, jwt
keys = requests.get('https://keycloak/realm/certs').json()['keys']
payload = jwt.decode(
id_token,
keys,
algorithms=['RS256'],
audience='my-client-id', # Pflicht!
issuer='https://keycloak/realm'
)
2. Nonce-Replay:
Problem: Ohne nonce: ID Token kann wiederverwendet werden
Schutz:
→ nonce = random, in Session gespeichert
→ nonce im ID Token muss nonce aus Session entsprechen
3. Implicit Flow (veraltet, unsicher):
Problem: Access + ID Token kommen direkt zum Browser via URL-Fragment
→ Token in URL = in Browser History, Logs, Referrer-Header
→ KEIN verwenden! Ersetze durch Authorization Code Flow + PKCE
---
JWT-Sicherheit (da OIDC JWTs nutzt):
JWT-Struktur: Header.Payload.Signature (base64-encoded, nicht verschlüsselt!)
Header: {"alg": "RS256", "typ": "JWT"}
Payload: {"sub": "123", "email": "user@example.com", "exp": 1770000000}
Signature: RSA/ECDSA signature über Header.Payload
JWT-Schwachstellen:
1. Algorithm Confusion (alg=none):
Angriff: Header ändern zu {"alg": "none"}
Signature weglassen
→ Unsichere Libraries akzeptieren das!
Schutz:
→ Algorithmus explizit festlegen beim Validieren
→ "none" niemals erlauben
2. RS256 → HS256 Switch:
Angriff: RS256 (asymmetrisch) zu HS256 (symmetrisch) wechseln
→ HS256-Schlüssel = öffentlicher Schlüssel (den Angreifer kennt!)
Schutz:
→ Algorithmus beim Validieren explizit auf RS256 fixieren
3. Weak HS256 Secret:
jwt.io Debugger + Brute-Force = geknacktes HS256 JWT
Schutz:
→ HS256 Secret: min. 256 Bit (32 Byte) zufällig
→ Besser: RS256/ES256 (asymmetrisch)
4. JWT ohne Expiry:
Schutz:
→ Immer exp Claim setzen
→ Kurze Expiry: Access Token 15-60 Minuten
OAuth für APIs - Client Credentials Flow
Machine-to-Machine Authentication (keine User-Interaktion):
Client Credentials Flow:
Service A → Authorization Server:
POST /token
grant_type=client_credentials&
client_id=service-a&
client_secret=secret123&
scope=api.read
Authorization Server → Service A:
{"access_token": "eyJ...", "expires_in": 3600}
Service A → Service B:
GET /internal-api/data
Authorization: Bearer eyJ...
Sicherheitsprobleme:
1. Client Secret Rotation:
→ Secrets müssen rotiert werden (Mindestens jährlich!)
→ Rotation: neues Secret erstellen → deploymen → altes deaktivieren
→ Secret im Vault (nicht in Konfigurationsdatei!)
2. Scope-Kontrolle:
→ client_id:service-a darf NUR /api/read
→ Nicht: admin-API, User-Daten anderer Services
3. Mutual TLS (mTLS) statt Client Secret:
→ Noch sicherer: Service A authentifiziert sich mit Zertifikat
→ Client Secret: shared secret (kann geleakt werden)
→ mTLS: privater Schlüssel verlässt Service nie
→ Kubernetes: Istio Service Mesh mit mTLS
Best Practices Zusammenfassung:
□ Authorization Code Flow + PKCE für alle Public Clients
□ state-Parameter IMMER (CSRF-Schutz)
□ redirect_uri: exaktes Matching, vorregistriert
□ Tokens: kurze Lifetime, Refresh Token in HttpOnly Cookie
□ Scopes: minimal, incremental authorization
□ ID Token: immer vollständig validieren (sig + aud + exp + nonce)
□ JWT: expliziter Algorithmus, kein "none", starkes Secret
□ Client Secrets: in Vault, rotieren, minimale Scope
OAuth-Implementierungsfehler sind in der OWASP API Security Top 10 prominent vertreten. AWARE7 analysiert OAuth/OIDC-Implementierungen im Rahmen von Penetrationstests und Web-Application-Security-Reviews.
API-Sicherheitstest anfragen | Penetrationstest Web-Applikationen
Nächster Schritt
Unsere zertifizierten Sicherheitsexperten beraten Sie zu den Themen aus diesem Artikel — unverbindlich und kostenlos.
Kostenlos · 30 Minuten · Unverbindlich
