PKCE
PKCE (Proof Key for Code Exchange, ausgesprochen "Pixy") ist eine Sicherheitserweiterung für den OAuth 2.0 Authorization Code Flow. Sie schützt Anwendungen, die keine geheimen Zugangsdaten sicher speichern können, vor sogenannten Authorization Code Interception Attacks. PKCE ist im RFC 7636 standardisiert und gilt heute als Best Practice für alle OAuth-Implementierungen.
Warum wurde PKCE entwickelt?
Der ursprüngliche OAuth 2.0 Authorization Code Flow hatte eine grundlegende Sicherheitslücke: Der Authorization Server konnte nicht unterscheiden, ob die Anwendung, die den Authorization Code einlöst, dieselbe ist, die den Authentifizierungsprozess gestartet hat. Ein Angreifer konnte den Authorization Code abfangen und ihn selbst gegen ein Access Token eintauschen.
Dieses Problem betrifft besonders Public Clients - also Anwendungen, die keinen Client Secret sicher speichern können:
- Single-Page Applications (SPAs): Der gesamte Quellcode ist im Browser einsehbar
- Native Mobile Apps: Können dekompiliert werden, wodurch eingebettete Secrets offengelegt würden
- Desktop-Anwendungen: Ebenfalls anfällig für Reverse Engineering
PKCE schließt diese Lücke, indem es einen kryptografischen Nachweis einführt, dass die Anwendung, die den Token anfordert, dieselbe ist, die den Authentifizierungsvorgang initiiert hat.
Funktionsweise von PKCE
PKCE erweitert den Authorization Code Flow um drei neue Parameter: code_verifier, code_challenge und code_challenge_method. Der Ablauf lässt sich in fünf Schritten beschreiben:
Schritt 1: Code Verifier generieren
Wenn ein Benutzer sich anmelden möchte, erzeugt die Anwendung zunächst einen kryptografisch sicheren Zufallsstring - den Code Verifier. Dieser String ist zwischen 43 und 128 Zeichen lang und wird lokal in der Anwendung gespeichert.
// Beispiel: Code Verifier generieren
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}
const codeVerifier = generateCodeVerifier();
// Beispiel: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
Schritt 2: Code Challenge ableiten
Aus dem Code Verifier wird durch Hashing die Code Challenge berechnet. Die empfohlene Methode ist S256 (SHA-256), bei der der Code Verifier gehasht und Base64-URL-kodiert wird. Die Alternative plain verwendet den Code Verifier direkt ohne Hashing - dies wird jedoch nicht empfohlen.
// Code Challenge mit SHA-256 berechnen
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64UrlEncode(new Uint8Array(hash));
}
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Beispiel: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
Schritt 3: Authorization Request
Die Anwendung leitet den Benutzer zum Authorization Server weiter. Dabei werden die code_challenge und die code_challenge_method als Parameter mitgeschickt. Der Code Verifier selbst wird niemals an den Server gesendet - nur die gehashte Code Challenge.
GET /authorize?
response_type=code&
client_id=meine-app&
redirect_uri=https://meine-app.de/callback&
scope=openid profile&
state=abc123&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256
Schritt 4: Benutzerauthentifizierung
Der Authorization Server authentifiziert den Benutzer (z.B. durch Login-Formular) und speichert die Code Challenge zusammen mit dem ausgestellten Authorization Code. Nach erfolgreicher Authentifizierung wird der Benutzer mit dem Authorization Code zur Anwendung zurückgeleitet.
Schritt 5: Token-Austausch mit Code Verifier
Die Anwendung tauscht den Authorization Code gegen ein Access Token ein. Dabei sendet sie den ursprünglichen Code Verifier mit. Der Authorization Server hasht diesen Verifier und vergleicht das Ergebnis mit der gespeicherten Code Challenge. Nur wenn beide übereinstimmen, wird das Token ausgestellt.
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE_VOM_SERVER&
redirect_uri=https://meine-app.de/callback&
client_id=meine-app&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Sicherheitsvorteile von PKCE
PKCE bietet Schutz gegen mehrere Angriffsszenarien, die beim klassischen Authorization Code Flow möglich waren:
Authorization Code Interception Attack
Bei diesem Angriff fängt ein Angreifer den Authorization Code während der Weiterleitung ab - etwa durch eine böswillige App auf dem Gerät, die sich als Redirect-URI registriert hat. Ohne PKCE könnte der Angreifer diesen Code direkt gegen ein Token eintauschen. Mit PKCE ist der abgefangene Code wertlos, da der Angreifer den Code Verifier nicht kennt und diesen aus dem SHA-256-Hash nicht zurückrechnen kann.
Code Injection Attack
Hierbei versucht ein Angreifer, einen eigenen Authorization Code in den Authentifizierungsfluss einer anderen Anwendung einzuschleusen. PKCE verhindert dies, da nur die Anwendung den korrekten Code Verifier besitzt, die den Authentifizierungsvorgang gestartet hat.
PKCE vs. Implicit Flow
Vor PKCE wurde für Browser-basierte Anwendungen oft der Implicit Flow verwendet. Dieser gab das Access Token direkt im URL-Fragment zurück, ohne einen Authorization Code als Zwischenschritt. Das hatte erhebliche Sicherheitsnachteile:
| Aspekt | Implicit Flow | Authorization Code + PKCE |
|---|---|---|
| Token-Übertragung | Im URL-Fragment sichtbar | Über sicheren Backend-Kanal |
| Browser-History | Token wird gespeichert | Nur Code wird gespeichert |
| Refresh Tokens | Nicht möglich | Möglich |
| Token-Validierung | Clientseitig | Serverseitig |
Der Implicit Flow gilt heute als veraltet und sollte nicht mehr verwendet werden. PKCE ist der empfohlene Standard für alle Public Clients und seit OAuth 2.1 sogar für alle Clients vorgeschrieben - auch für serverseitige Anwendungen mit Client Secret.
Wann sollte PKCE eingesetzt werden?
Die aktuelle Empfehlung lautet: PKCE sollte bei jeder OAuth 2.0 Implementierung verwendet werden. Die ursprüngliche Unterscheidung zwischen Public und Confidential Clients ist für die Entscheidung nicht mehr relevant. PKCE bietet auch bei serverseitigen Anwendungen zusätzlichen Schutz, etwa gegen kompromittierte Netzwerk-Intermediaries oder Browser-Erweiterungen.
- Single-Page Applications (SPAs): Pflicht, da kein Client Secret möglich
- Mobile Apps (iOS, Android): Pflicht, da Apps dekompiliert werden können
- Desktop-Anwendungen: Pflicht aus denselben Gründen
- Serverseitige Webanwendungen: Empfohlen für zusätzliche Sicherheit
- Machine-to-Machine (M2M): Nicht erforderlich bei Client Credentials Flow
Best Practices für PKCE
Bei der Implementierung von PKCE solltest du folgende Empfehlungen beachten:
- Immer S256 verwenden: Die plain-Methode bietet keinen kryptografischen Schutz
- Kryptografisch sichere Zufallszahlen: Nutze
crypto.getRandomValues()in JavaScript oder entsprechende APIs in anderen Sprachen - Code Verifier Länge: Mindestens 43 Zeichen, empfohlen 128 Zeichen
- State-Parameter zusätzlich verwenden: Schützt gegen CSRF-Angriffe
- Code Verifier sicher speichern: Nur temporär im Speicher, nicht in Cookies oder localStorage
PKCE in der Praxis
Als Fachinformatiker für Anwendungsentwicklung wirst du PKCE bei der Entwicklung moderner Webanwendungen und Mobile Apps regelmäßig einsetzen. Viele Identity Provider wie Auth0, Keycloak, Azure AD oder Okta unterstützen PKCE standardmäßig. Auch beliebte OAuth-Libraries in verschiedenen Programmiersprachen implementieren PKCE automatisch.
In der täglichen Arbeit musst du PKCE selten selbst implementieren - die meisten OAuth-Libraries übernehmen das für dich. Wichtig ist jedoch, das Konzept zu verstehen, um Sicherheitsprobleme zu erkennen und die richtigen Konfigurationsoptionen zu wählen.
Quellen und weiterführende Links
- RFC 7636 - Proof Key for Code Exchange - Die offizielle IETF-Spezifikation
- OAuth 2.0 PKCE - oauth.net - Übersicht und Erklärungen
- PKCE - oauth.com - Praktische Implementierungshinweise