OAuth 2.0: Authorization Code, Flows & Access Token
Dieser Beitrag ist eine Begriffserklärung zum OAuth 2.0 Autorisierungsframework – inklusive Flows, Token und praktischer Implementierung.
In a Nutshell
OAuth 2.0 ist ein standardisiertes Autorisierungsframework, das es Drittanbietern erlaubt, auf Benutzerressourcen zuzugreifen, ohne deren Zugangsdaten zu kennen.
Kompakte Fachbeschreibung
OAuth 2.0 ist ein weitverbreitetes Autorisierungsprotokoll für Web-, Desktop- und Mobile-Anwendungen. Es ermöglicht einer Anwendung (Client), im Namen eines Benutzers auf Ressourcen eines anderen Systems (Ressourcenserver) zuzugreifen.
Kerneigenschaften:
- Delegation: Benutzer delegieren Zugriffsrechte an Anwendungen
- Token-basiert: Access Tokens statt Passwörter
- Rollenbasiert: Klare Trennung der beteiligten Akteure
- Standardisiert: RFC 6749 definiert das Framework
Beteiligte Rollen:
- Resource Owner: Benutzer, der die Ressourcen besitzt
- Client: Anwendung, die auf Ressourcen zugreifen möchte
- Authorization Server: Stellt Tokens aus und validiert sie
- Resource Server: Stellt die geschützten Ressourcen bereit
OAuth 2.0 überträgt keine Benutzeridentität, sondern autorisiert lediglich Zugriffe auf Ressourcen. Für Identitätsüberprüfung gibt es OpenID Connect.
Prüfungsrelevante Stichpunkte
- Autorisierungsframework, nicht Authentifizierung
- Rollen: Resource Owner, Client, Authorization Server, Resource Server
- Access Token und optional Refresh Token
- Authorization Code Flow für Web-Anwendungen
- Client Credentials Flow für Server-zu-Server-Kommunikation
- HTTP-basiert und unterstützt REST APIs (IHK-relevant)
- Token-basierter Zugriff erhöht Sicherheit
- Schutz vor Passwortweitergabe durch Delegationsmechanismus
Kernkomponenten
- Resource Owner: Benutzer oder System, das die Ressourcen besitzt
- Client: Anwendung, die Zugriff auf Ressourcen benötigt
- Authorization Server: Stellt Tokens aus und verifiziert sie
- Resource Server: Hostet die geschützten Ressourcen
- Access Token: Kurzlebiges Token für Ressourcenzugriff
- Refresh Token: Langlebiges Token für Token-Erneuerung
- Authorization Code: Temporärer Code für Token-Austausch
- Redirect URI: Ziel-URL nach Autorisierung
- Scope: Definiert den Zugriffsbereich
- Grant Type: Bestimmt den OAuth 2.0 Flow
OAuth 2.0 Flows
1. Authorization Code Flow (für Web-Apps)
1. Client → User: Weiterleitung zu Authorization Server
2. User → Authorization Server: Login & Zustimmung
3. Authorization Server → Client: Authorization Code
4. Client → Authorization Server: Code gegen Access Token einlösen
5. Authorization Server → Client: Access Token + Refresh Token
6. Client → Resource Server: Ressourcen mit Access Token anfordern
2. Client Credentials Flow (für Server-zu-Server)
1. Client → Authorization Server: Client Credentials senden
2. Authorization Server → Client: Access Token
3. Client → Resource Server: Ressourcen mit Access Token anfordern
3. Implicit Flow (veraltet, für Single-Page-Apps)
1. Client → User: Weiterleitung zu Authorization Server
2. User → Authorization Server: Login & Zustimmung
3. Authorization Server → Client: Access Token direkt im Fragment
Praxisbeispiele
Authorization Code Flow Implementation
// Frontend: Autorisierung anfordern
const authUrl = 'https://auth.example.com/authorize';
const params = new URLSearchParams({
response_type: 'code',
client_id: 'your_client_id',
redirect_uri: 'https://yourapp.com/callback',
scope: 'read:profile write:data',
state: generateRandomString() // CSRF-Schutz
});
window.location.href = `${authUrl}?${params}`;
// Callback-Handler
async function handleCallback(code, state) {
// State validieren
if (state !== getStoredState()) {
throw new Error('Invalid state - CSRF attack detected');
}
// Code gegen Token einlösen
const tokenResponse = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: 'your_client_id',
client_secret: 'your_client_secret',
redirect_uri: 'https://yourapp.com/callback'
})
});
const tokens = await tokenResponse.json();
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
}
Backend: Token-Validierung
// Spring Boot Example
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/profile")
public ResponseEntity<UserProfile> getProfile(
@RequestHeader("Authorization") String authHeader) {
// Token extrahieren und validieren
String token = authHeader.replace("Bearer ", "");
if (!tokenValidator.isValid(token)) {
return ResponseEntity.status(401).build();
}
// Token-Claims extrahieren
Claims claims = tokenValidator.getClaims(token);
String userId = claims.getSubject();
List<String> scopes = claims.get("scope", List.class);
// Scope-Prüfung
if (!scopes.contains("read:profile")) {
return ResponseEntity.status(403).build();
}
UserProfile profile = userService.getProfile(userId);
return ResponseEntity.ok(profile);
}
@PostMapping("/data")
public ResponseEntity<?> createData(
@RequestHeader("Authorization") String authHeader,
@RequestBody DataRequest request) {
String token = authHeader.replace("Bearer ", "");
if (!tokenValidator.isValid(token)) {
return ResponseEntity.status(401).build();
}
Claims claims = tokenValidator.getClaims(token);
List<String> scopes = claims.get("scope", List.class);
// Schreib-Berechtigung prüfen
if (!scopes.contains("write:data")) {
return ResponseEntity.status(403).build();
}
Data data = dataService.create(request, claims.getSubject());
return ResponseEntity.ok(data);
}
}
Client Credentials Flow
# Python Beispiel für Server-zu-Server-Kommunikation
import requests
def get_access_token():
token_url = "https://auth.example.com/token"
data = {
'grant_type': 'client_credentials',
'client_id': 'your_client_id',
'client_secret': 'your_client_secret',
'scope': 'api:read api:write'
}
response = requests.post(token_url, data=data)
response.raise_for_status()
token_data = response.json()
return token_data['access_token']
def api_call():
access_token = get_access_token()
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.example.com/data',
headers=headers
)
return response.json()
Refresh Token Flow
// Token-Erneuerung mit Refresh Token
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token available');
}
try {
const response = await fetch('https://auth.example.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: 'your_client_id',
client_secret: 'your_client_secret'
})
});
if (!response.ok) {
throw new Error('Token refresh failed');
}
const tokens = await response.json();
localStorage.setItem('access_token', tokens.access_token);
if (tokens.refresh_token) {
localStorage.setItem('refresh_token', tokens.refresh_token);
}
return tokens.access_token;
} catch (error) {
// Refresh Token ungültig - erneute Anmeldung erforderlich
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
redirectToLogin();
}
}
Sicherheitshinweise
State Parameter (CSRF-Schutz)
function generateState() {
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}
function storeState(state) {
sessionStorage.setItem('oauth_state', state);
}
function validateState(receivedState) {
const storedState = sessionStorage.getItem('oauth_state');
sessionStorage.removeItem('oauth_state');
return storedState === receivedState;
}
PKCE (Proof Key for Code Exchange)
// PKCE für mobile/single-page apps
function generatePKCE() {
const codeVerifier = generateRandomString(128);
const codeChallenge = base64urlEncode(sha256(codeVerifier));
return { codeVerifier, codeChallenge };
}
// Authorization Request mit PKCE
const params = new URLSearchParams({
response_type: 'code',
client_id: 'your_client_id',
code_challenge: pkce.codeChallenge,
code_challenge_method: 'S256',
redirect_uri: 'https://yourapp.com/callback'
});
Vorteile und Nachteile
Vorteile
- Sicherheit: Keine Passweitergabe an Drittanbieter
- Standardisierung: Weit verbreitet und gut dokumentiert
- Flexibilität: Verschiedene Flows für unterschiedliche Anwendungsfälle
- Skalierbarkeit: Zentrale Autorisierung für viele Services
- Revokation: Tokens können jederzeit widerrufen werden
Nachteile
- Komplexität: Mehr Schritte als einfache Authentifizierung
- Performance: Zusätzliche Netzwerkaufrufe für Token-Erneuerung
- State Management: Client muss Token-Status verwalten
- Security Risks: Falsche Implementierung kann Sicherheitslücken schaffen
Häufige Prüfungsfragen
-
Was ist der Unterschied zwischen OAuth 2.0 und OpenID Connect? OAuth 2.0 ist für Autorisierung, OpenID Connect fügt Identität (Authentication) hinzu.
-
Erklären Sie den Authorization Code Flow! Client leitet User zu Authorization Server, erhält Code, tauscht Code gegen Token ein.
-
Wann verwendet man Client Credentials Flow? Für Server-zu-Server-Kommunikation ohne Benutzerinteraktion.
-
Was ist der Zweck des State Parameters? CSRF-Schutz durch Verifizierung, dass die Anfrage vom Client initiiert wurde.
Wichtigste Quellen
- https://tools.ietf.org/html/rfc6749
- https://oauth.net/2/
- https://auth0.com/docs/authorization-framework/overview