OAuth 2.0 / OpenID Connect
OAuth 2.0 ist ein Autorisierungsframework das Anwendungen limitierten Zugriff auf Ressourcen im Namen eines Nutzers erlaubt. OpenID Connect (OIDC) baut auf OAuth 2.0 auf und fügt Authentifizierung hinzu - die Basis für modernes Single Sign-On und Social Login.
OAuth 2.0 ermöglicht eine fundamentale Sicherheitsverbesserung: Anstatt Third-Party-Apps das Passwort zu geben (was historisch tatsächlich gemacht wurde!), erteilt der Nutzer der App nur die spezifischen Berechtigungen die sie benötigt - für eine begrenzte Zeit, jederzeit widerruflich. OpenID Connect erweitert OAuth um standardisierte Identitätsinformationen.
OAuth 2.0 in einem Satz erklärt
Analogie: Im Hotelbetrieb geben Sie dem Parkservice nicht Ihren Haustürschlüssel, sondern einen Valet-Key der nur das Auto starten kann. Bei OAuth geben Sie der App nicht Ihr Passwort, sondern ein Token das nur bestimmte Aktionen erlaubt.
Ohne OAuth (historisch):
“Bitte geben Sie Ihr Google-Passwort ein, damit wir Ihre Kontakte importieren können” - Die App hatte Vollzugriff auf alles; Widerruf nur durch Passwort ändern möglich.
Mit OAuth 2.0:
“Erlauben Sie XY-App Zugriff auf: Ihre Kontaktliste (nur lesen)” - Token mit eingeschränktem Scope; Widerruf jederzeit in Google-Einstellungen, ohne Passwort zu ändern.
Die OAuth 2.0 Komponenten
Die 4 Rollen
Resource Owner (Nutzer):
- Besitzt die Ressourcen (E-Mails, Kontakte, etc.)
- Erteilt Zugriffserlaubnis
Client (Anwendung):
- App die auf Ressourcen zugreifen möchte
- Registriert beim Authorization Server (erhält Client ID + Secret)
Authorization Server (IdP):
- Stellt Access Tokens aus nach Zustimmung
- Beispiele: Google, Microsoft Entra ID, Auth0, Keycloak
Resource Server (API):
- Hält die Ressourcen (Gmail API, Calendar API)
- Akzeptiert Access Tokens und liefert Daten
Zentrale Konzepte
| Begriff | Bedeutung |
|---|---|
| Access Token | Kurzlebig (15min-1h), für API-Zugriff |
| Refresh Token | Langlebig (7-30 Tage), holt neue Access Tokens |
| Scope | Welche Permissions? (read:contacts, write:calendar) |
| State | CSRF-Schutz beim Authorization Request |
| PKCE | Proof Key for Code Exchange (für Public Clients) |
OAuth 2.0 Flows
1. Authorization Code Flow + PKCE (empfohlen für alle)
Wann: Web-Apps, Mobile Apps, Single Page Apps Sicherheit: Höchste (Code wird gegen Token eingetauscht)
Ablauf:
a) App generiert code_verifier (zufällig) + code_challenge (SHA256 davon)
b) App sendet User zu IdP:
GET /authorize?
client_id=abc
&response_type=code
&scope=openid email
&redirect_uri=https://app.firma.de/callback
&state=zufälligerWert
&code_challenge=<hash>
&code_challenge_method=S256
c) User loggt sich beim IdP ein
d) IdP redirectet zu Callback mit Authorization Code:
GET /callback?code=auth_code_123&state=zufälligerWert
e) App tauscht Code gegen Token:
POST /token
grant_type=authorization_code
&code=auth_code_123
&redirect_uri=https://app.firma.de/callback
&client_id=abc
&code_verifier=<original_verifier> ← PKCE!
f) IdP liefert:
{ "access_token": "...", "refresh_token": "...", "id_token": "..." }
2. Client Credentials Flow
Wann: Server-zu-Server (kein User beteiligt) Beispiel: Microservice ruft andere API auf
POST /token
grant_type=client_credentials
&client_id=service-a
&client_secret=secret
&scope=api:read
Veraltete Flows - nicht verwenden
- Implicit Flow (veraltet): Token in URL → History, Logs, Referer-Header; ersetzt durch Authorization Code + PKCE
- Password Grant Flow (veraltet): App erhält Passwort direkt → OAuth-Sicherheit ausgehebelt; nur für Legacy-Migration
OpenID Connect (OIDC)
OIDC = OAuth 2.0 + Identitätsschicht
Was OIDC hinzufügt:
- ID Token (JWT): wer ist der User?
/userinfoEndpoint: Nutzerinformationen abrufen- Standard-Scopes:
openid,profile,email,phone,address - Discovery Endpoint:
/.well-known/openid-configuration
// ID Token Inhalt (JWT Payload):
{
"iss": "https://accounts.google.com", // Issuer
"sub": "10769150350006150715113082367", // Subject (User-ID)
"aud": "client_id_123", // Audience (deine App)
"exp": 1311281970, // Expiry
"iat": 1311280970, // Issued At
"nonce": "0394852-3190485-2490358", // CSRF-Schutz
"email": "user@example.com",
"email_verified": true,
"name": "Max Muster"
}
Wichtige Validierungen des ID Tokens:
iss== bekannter Issuer (nicht “Evil IdP”!)aud== deine Client ID (kein Token-Substitution-Angriff!)exp> jetzt (Token nicht abgelaufen)nonce== gesendeter Nonce (CSRF-Schutz)- Signatur mit IdP-Public-Key gültig
- Algorithmus: RS256 oder ES256 (nicht
noneoder HS256!)
OAuth-Sicherheitslücken
1. Redirect URI Validation (häufigste Schwachstelle)
FALSCH: Redirect-URI-Whitelist: "https://app.firma.de/*"
Angriff: redirect_uri=https://app.firma.de/callback?next=https://attacker.com
→ OAuth Code wird an attacker.com weitergeleitet!
RICHTIG: Exakte URI (kein Wildcard!):
Whitelist: ["https://app.firma.de/callback"]
2. State Parameter fehlt (CSRF)
Angriff: Angreifer lässt Opfer OAuth-Flow starten mit Angreifer-Code → Opfer loggt sich mit Angreifer-Account in App ein. Schutz: State-Parameter setzen und validieren!
3. Open Redirect über Redirect URI
Wenn App nach OAuth auf user-bestimmte URL redirectet:
Angriff: ?redirect=/nach-login?url=https://phishing.com
Schutz: Nur erlaubte relative URLs als Redirect
4. Authorization Code Interception (ohne PKCE)
Code in URL sichtbar → Referer-Header, Proxy-Logs.
Schutz: PKCE erzwingen (code_challenge)
5. Token Leakage in Logs
Access Tokens in URL-Parametern → Server-Logs enthalten Token. Schutz: Token NUR in Authorization-Header, nie in URL
6. Consent Screen Phishing (OAuth Consent Phishing)
Angreifer registriert App bei Google/Microsoft mit glaubwürdigem Namen (“IT-Support Tool”) und weitem Scope (offline_access, Mail.ReadWrite). User erteilt Consent → Angreifer hat Vollzugriff auf Mailbox.
Schutz: Conditional Access Policy → Admin-Approval für neue Apps
Implementierung: Node.js mit Passport.js
// Express + Passport.js + OIDC
import passport from 'passport';
import { Strategy as OIDCStrategy } from 'passport-openidconnect';
passport.use(new OIDCStrategy({
issuer: 'https://accounts.google.com',
authorizationURL: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenURL: 'https://oauth2.googleapis.com/token',
userInfoURL: 'https://openidconnect.googleapis.com/v1/userinfo',
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'https://app.firma.de/auth/callback',
scope: ['openid', 'email', 'profile'],
// Sicherheit:
pkce: true, // PKCE aktivieren!
state: true, // State-Parameter für CSRF-Schutz
}, async (issuer, profile, done) => {
// User in Datenbank finden oder erstellen
const user = await User.findOrCreate({ googleId: profile.id });
return done(null, user);
}));
// Routes:
app.get('/auth/google', passport.authenticate('openidconnect'));
app.get('/auth/callback',
passport.authenticate('openidconnect', { failureRedirect: '/login' }),
(req, res) => res.redirect('/dashboard')
);