Blog
Beseitigen Sie Client-Geheimnisse mit dem OAuth-Autorisierungscode PKCE flow

In diesem Artikel befassen wir uns mit dem OAuth 2.0 Proof Key for Code Exchange (PKCE) Fluss, einer Erweiterung des Autorisierungscodeflusses, die hilft, CSRF- und Autorisierungscode-Abfangangriffe zu verhindern. PKCE wurde ursprünglich entwickelt, um den Autorisierungscodefluss in öffentlichen Clients zu schützen (Anwendungen, die aufgrund ihrer Ausführungsumgebung keine Geheimnisse sicher speichern können, z.B. Single-Page-Webanwendungen). Seine Fähigkeit, die Injektion von Autorisierungscode zu verhindern, macht es jedoch für jede Art von OAuth-Client nützlich, sogar für vertrauliche Clients (Anwendungen, die Geheimnisse sicher speichern können, z. B. serverbasierte Webanwendungen), die Client-Geheimnisse verwenden. In OAuth 2.1 ist PKCE für alle OAuth-Clients vorgeschrieben, die den Autorisierungscodefluss verwenden, nicht nur für öffentliche Clients.
PKCE kann zwar in vertraulichen Clients eingesetzt werden, um die Sicherheit weiter zu erhöhen, aber es kann auch Client-Geheimnisse und die damit verbundenen Herausforderungen beseitigen. Es ist jedoch ratsam, mit Ihrem CSO oder Sicherheitsteam zu klären, ob dies ein akzeptabler Kompromiss für Ihren Anwendungsfall ist.
Im Laufe der Zeit stellte sich heraus, dass öffentliche Clients, die den Autorisierungscode-Fluss verwenden, anfällig für den Autorisierungscode-Abfangangriff sind. Bei diesem Angriff fängt der Angreifer den vom Autorisierungsendpunkt zurückgegebenen Autorisierungscode innerhalb eines Kommunikationspfades ab, der nicht durch Transport Layer Security (TLS) geschützt ist, wie z.B. die Kommunikation zwischen Anwendungen innerhalb des Betriebssystems des Clients. Ein Beispiel hierfür sind Betriebssysteme, die es Anwendungen erlauben, sich als Handler für bestimmte URI-Schemata zu registrieren (z.B. '
Anfordern von Token mit
Wie bereits weiter oben in diesem Artikel erwähnt, wird bei der Wahl von 'plain' als Anfordern von Wertmarken mit
Der PKCE RFC besagt, dass S256 obligatorisch zu implementieren ist, wenn der Client dazu in der Lage ist (MTI). Im Fall unserer .NET-Demo-Webanwendung ist dies möglich. Die 
Einführung in OAuth und PKCE
Kurzer Überblick über OAuth 2.0
OAuth 2.0 ist das branchenübliche Protokoll für die Autorisierung. Es ermöglicht es Diensten von Drittanbietern, Webressourcen im Namen eines Benutzers auszutauschen, ohne die Anmeldedaten des Benutzers preiszugeben. Es ist aufgrund seiner Flexibilität und Sicherheit weit verbreitet und dient als Rückgrat für moderne Authentifizierungssysteme in Webanwendungen. OAuth 2.0 bietet zwar einen soliden Rahmen, ist aber nicht ohne Herausforderungen, insbesondere bei der Verwaltung von Client-Geheimnissen. Traditionell wurden Client-Geheimnisse verwendet, um die Kommunikation zwischen Clients und Autorisierungsservern zu sichern. Diese Geheimnisse können jedoch in Umgebungen kompromittiert werden, in denen die Vertraulichkeit nicht gewährleistet ist, wie z.B. bei mobilen oder einseitigen Anwendungen. Da es sich bei diesen Geheimnissen im Grunde um Passwörter handelt, die irgendwo gespeichert werden müssen, um verwendet werden zu können, landen sie außerdem oft fälschlicherweise in Quellcode-Repositories. Sie müssen rotiert werden, um das Risiko einer langfristigen, unbemerkten Kompromittierung des Geheimnisses zu verringern. Diese Rotation hat zur Folge, dass auch die Clients, die diese Geheimnisse verwenden, aktualisiert werden müssen, was zu Ausfallzeiten oder sogar zu nicht mehr funktionierenden Client-Anwendungen führen kann.Einführung in den Autorisierungscodefluss
Der OAuth 2.0 Autorisierungscode-Gewährungstyp oder Autorisierungscodefluss ermöglicht es einer Client-Anwendung, autorisierten Zugriff auf geschützte Ressourcen wie Web-APIs zu erhalten. Das folgende Diagramm, mit freundlicher Genehmigung von Postman, zeigt einen Überblick über den Ablauf auf hoher Ebene.
Im Laufe der Zeit stellte sich heraus, dass öffentliche Clients, die den Autorisierungscode-Fluss verwenden, anfällig für den Autorisierungscode-Abfangangriff sind. Bei diesem Angriff fängt der Angreifer den vom Autorisierungsendpunkt zurückgegebenen Autorisierungscode innerhalb eines Kommunikationspfades ab, der nicht durch Transport Layer Security (TLS) geschützt ist, wie z.B. die Kommunikation zwischen Anwendungen innerhalb des Betriebssystems des Clients. Ein Beispiel hierfür sind Betriebssysteme, die es Anwendungen erlauben, sich als Handler für bestimmte URI-Schemata zu registrieren (z.B. 'xebia.ms.app://'). Da mehrere Anwendungen als Handler für einen bestimmten URI registriert werden können, besteht die Schwachstelle dieses Ablaufs darin, dass sich ein bösartiger Client auch als Handler für dasselbe URI-Schema registrieren könnte, das eine legitime Anwendung verarbeitet. In diesem Fall kann das Betriebssystem den Autorisierungscode an den böswilligen Client senden. Das folgende Diagramm, mit freundlicher Genehmigung von WSO2 IdentityServer, zeigt den Angriff:
Einführung in Proof Key for Code Exchange (PKCE)
Um dieses Problem zu lösen, wurde der Proof Key for Code Exchange (PKCE, ausgesprochen "pixy") eingeführt. Ursprünglich für mobile Anwendungen entwickelt, bietet PKCE eine zusätzliche Sicherheitsebene für OAuth 2.0 unter Verwendung dynamischer, einmaliger Codes. Diese Methode stellt sicher, dass der Autorisierungscode, selbst wenn er abgefangen wird, ohne den entsprechenden Code-Prüfer im Besitz des Clients nutzlos wäre. Das folgende Diagramm, mit freundlicher Genehmigung von postman, zeigt einen Überblick über den Ablauf des Autorisierungscodes mit PKCE. Hier werden zwei neue Konzepte eingeführt:- Code-Verifizierer: Eine vom Client erstellte kryptografische Zeichenfolge mit hoher Entropie.
- Code-Herausforderung: Eine Umwandlung (unter Verwendung einer zwischen dem Client und dem Server vereinbarten Hashing-Methode) des in der ursprünglichen Autorisierungsanfrage gesendeten Code-Verifizierers.
Vergleich des Authorization Code Flow mit PKCE mit dem OAuth Implicit Flow
Der OAuth Implicit Flow, der einst als Standard empfohlen wurde und wegen seiner Einfachheit in clientseitigen Anwendungen beliebt war, birgt Sicherheitsrisiken aufgrund von freiliegenden Token in URLs und dem leichteren Abfangen von Token. Der Authorization Code Flow mit PKCE, der einen Rückkanalaufruf (Skript XHR) verwendet, um das Token abzurufen, gilt jetzt als sicherere Alternative für öffentliche Clients wie mobile und einseitige Anwendungen. In OAuth 2.1 wurde der Implicit Flow entfernt.Gibt es einen Unterschied bei den Token?
Im PKCE-Flow, der für öffentliche Clients verwendet wird, wie z.B. in Single-Page-Anwendungen, ist die Lebensdauer der von Entra ID ausgestellten Zugangs- und ID-Tokens dieselbe wie die für vertrauliche Clients, nämlich etwa 1 Stunde. Der Hauptunterschied liegt in der Lebensdauer des Refresh-Tokens. Für eine einseitige Anwendung (ein öffentlicher Client) ist das Refresh-Token 24 Stunden lang gültig. Für vertrauliche Clients ist die genaue Lebensdauer eines Aktualisierungs-Tokens nicht festgelegt, aber im Allgemeinen viel länger.Verwendung von PKCE mit Microsoft Entra ID
In den folgenden Abschnitten werden wir Token (Zugriffs-, Aktualisierungs- und ID-Token) von Entra ID als vertraulicher und öffentlicher Client anfordern. Bei öffentlichen Kunden erlaubt Entra ID nicht die Übermittlung eines Kundengeheimnisses bei der Einlösung des Autorisierungscodes. Für vertrauliche Kunden ist die Übermittlung des Kundengeheimnisses erforderlich. Ich habe eine kleine ASP.NET Core-Demoanwendung erstellt, die Sie hier finden: Github.com - PKCEarticle. Diese Anwendung stellt drei Endpunkte zur Verfügung:/pkceconfidential: Dieser Endpunkt fordert ein Token unter Verwendung von PKCE mit einem Kundengeheimnis an./pkcepublic: Dieser Endpunkt fordert ein Token unter Verwendung von PKCE ohne ein Client-Geheimnis an./pkcepublics256: Dieser Endpunkt fordert ein Token mit PKCE ohne ein Client-Geheimnis an und verwendet einen SHA256 Hash-Code als Herausforderung.
Konfigurieren Sie eine Anwendungsregistrierung
Erstellen Sie in Microsoft Entra ID eine neue App-Registrierung mit zwei Plattformen:- Web: Dies sollte mit dem Endpunkt
/pkceconfidentialals Umleitungs-URI konfiguriert werden. - Mobile und Desktop-Anwendung: Dies sollte mit zwei Umleitungs-URIs konfiguriert werden:
/pkcepublicund/pkcepublics256.
"replyUrlsWithType": [
{
"url": "http://localhost:5218/pkcepublics256",
"type": "InstalledClient"
},
{
"url": "http://localhost:5218/pkcepublic",
"type": "InstalledClient"
},
{
"url": "http://localhost:5218/pkceconfidential",
"type": "Web"
}
]
Drei Dinge sind hier erwähnenswert:
- Die URLs verwenden das Schema
http://, das nur für localhost zulässig ist. - Für die Endpunkte
/pkcepublicoder/pkcepublics256könnten wir auch den Typ spa verwenden, aber Entra erwartet dann, dass die Token-Anfrage CORS verwendet - Die Portnummer kann für Ihre lokale Instanz meiner Demo-App anders sein. Portnummern sind optional, wenn Sie die App auf Port 80 oder 443 hosten.
Erwerb von Token mit dem Autorisierungscode PKCE flow
Der Erwerb von Token im Autorisierungscodefluss ist immer ein zweistufiger Prozess:- Senden Sie eine GET-Anfrage an den Endpunkt
/authorize, um einen Autorisierungscode anzufordern. In meinem Beispiel werden die folgenden Query-String-Parameter gesendet (einige zusätzliche optionale Parameter können ebenfalls gesendet werden):client_id: the application ID of your app reg response_type: code, for authorization code flow redirect_uri: endpoint in your app where authentication responses can be received scope: the scope(s) the user should consent to code_challenge_method: plain or S256. This should be S256, but the spec allows the use of plain if the client can't support SHA256. In case of plain, code_challenge = code_verifier code_challenge: a string between 43 and 128 characters. Could be anything, but should have enough entropy to make it impractical to guess the value. - Senden Sie eine POST-Anfrage an den Endpunkt
/tokenmit dem Autorisierungscode, um Token anzufordern. In meinem Beispiel werden die folgenden formularverschlüsselten Daten im Körper der Anfrage gesendet:client_id: the application ID of your app reg scope: the scopes that should be returned in the token(s) redirect_uri: The same redirect_uri value that was used to acquire the authorization code. code: the authorization code received back from the call to the /authorize endpoint grant_type: authorization_code, for the authorization code flow. code_verifier: the code verifier, derived by means of the code_challenge_method from the code_challenge that was sent to and stored by Entra ID on the call to the /authorize endpoint. client_secret: as mentioned before, required for confidential clients, disallowed for public clients
Anfordern von Token mit code_challenge_method plain
Wie bereits weiter oben in diesem Artikel erwähnt, wird bei der Wahl von 'plain' als code_challenge_method, die code_verifier und die code_challenge sollte das gleiche sein. Wie Sie in der Demo-App sehen können, habe ich als Code-Herausforderung (und damit auch als Überprüfer) '~ThisIsThe1stArticleI_veWrittenForXmsMagazine.IHopeYouFindItInformative-' gewählt, um Ihnen zu zeigen, welche Zeichen erlaubt sind. Die vollständige URL zum Abrufen des Autorisierungscodes lautet dann wie folgt:
https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize?client_id=00000000-0000-0000-0000-000000000000&response_type=code&redirect_uri=http://localhost:5218/pkceconfidential&response_mode=query&scope=openid&code_challenge=~ThisIsThe1stArticleI_veWrittenForXmsMagazine.IHopeYouFindItInformative-&code_challenge_method=plain
Anfordern von Wertmarken mit code_challenge_method S256
Der PKCE RFC besagt, dass S256 obligatorisch zu implementieren ist, wenn der Client dazu in der Lage ist (MTI). Im Fall unserer .NET-Demo-Webanwendung ist dies möglich. Die code_verifier und code_challenge können mit den standardmäßigen .NET Core-Bibliotheken erstellt werden RandomNumberGenerator und SHA256:
public static string CreateCodeVerifier()
{
const int size = 32; // Size recommended by RFC 7636 for code verifier
using var rng = RandomNumberGenerator.Create();
var bytes = new byte[size];
rng.GetBytes(bytes);
// Using URL-safe base64 encoding without padding
return Convert.ToBase64String(bytes)
.TrimEnd('=') // Remove any base64 padding
.Replace('+', '-') // 62nd char of encoding
.Replace('/', '_'); // 63rd char of encoding
}
public static string CreateCodeChallenge(string codeVerifier)
{
using var sha256 = SHA256.Create();
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
// Using URL-safe base64 encoding without padding
return Convert.ToBase64String(challengeBytes)
.TrimEnd('=') // Remove any base64 padding
.Replace('+', '-') // 62nd char of encoding
.Replace('/', '_'); // 63rd char of encoding
}
Wie bereits erwähnt, speichert der Server die code_challenge, die er beim Aufruf von /authorize erhalten hat. Da code_verifier laut PKCE RFC ein "kryptographischer Zufallsstring mit hoher Entropie" sein sollte und code_challenge von code_verifier abgeleitet wurde, muss der Client auch die code_verifier irgendwo speichern, um sie beim Aufruf von /token an den Server senden zu können. In der Demo-Anwendung habe ich mich dafür entschieden, die code_verifier auch an den Server zu senden, indem ich den state Querystring-Parameter verwende, der speziell für den Zweck verwendet werden kann, den Status zu speichern, ohne dass der Status tatsächlich clientseitig gespeichert werden muss. Ob dies auch für die code_verifier verwendet werden sollte, ist eine andere interessante Diskussion, aber für Demozwecke ist es in Ordnung. Die vollständige URL zum Abrufen des Autorisierungscodes lautet dann wie folgt: https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/authorize?client_id=00000000-0000-0000-0000-000000000000&response_type=code&redirect_uri=http://localhost:5218/pkcepublics256&response_mode=query&scope=openid&state=8p1BQjDGG_t6mymu0UJJfIWVX7ycZvxaN97jbNVt898&code_challenge=bnxEgm7cqE38fMI3AoW4RrKQ_b--Q9uwjPI65M-f_FU&code_challenge_method=S256. Den code_verifier erhalte ich dann vom Server im state Querystring der HTTP 302 Antwort des Servers zurück, der dann bei der Anfrage an den /token Endpunkt als code_verifier an den Server gesendet werden kann.
Zusammenfassung
Da der Authorization Code mit PKCE-Flow inzwischen als Standard für vertrauliche und öffentliche Clients empfohlen wird, unterstützen ihn die verschiedenen offiziellen Microsoft-Bibliotheken für die Authentifizierung (z.B. MSAL.NET, MSAL.js, Microsoft.Identity.Web) sowie beliebte Open-Source-Projekte wie OAuth2-proxy. Ich empfehle Ihnen, diese Bibliotheken zu verwenden, anstatt Ihren eigenen Code zu erstellen, um diecode_verifier und code_challenge zu generieren, auch wenn Sie gesehen haben, dass es nicht schwer ist, eine grundlegende Implementierung zu erstellen.
In diesem Artikel habe ich gezeigt, dass PKCE mit Client-Geheimnissen verwendet wird, um die Sicherheit vertraulicher Kunden zu erhöhen. Für öffentliche Kunden wird es ohne Kundengeheimnisse verwendet. Aber nichts hindert uns daran, dies auch mit unseren vertraulichen Kunden zu tun und alle damit verbundenen Herausforderungen, die Kundengeheimnisse mit sich bringen, loszuwerden. Wie bereits erwähnt, sollten Sie mit Ihrem CISO oder Sicherheitsteam abklären, ob dies für Ihren Anwendungsfall angemessen ist.
Quellen - RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients: https://www.rfc-editor.org/rfc/rfc7636 - OAuth 2.1 Entwurf: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10 - Mitigate Authorization Code Interception Attacks: https://is.docs.wso2.com/en/latest/deploy/mitigate-attacks/mitigate-authorization-code-interception-attacks/ - OAuth 2.0: Implicit Flow ist tot, versuchen Sie stattdessen PKCE: https://blog.postman.com/pkce-oauth-how-to/
Dieser Artikel ist Teil von XPRT.#16. Laden Sie das Magazin hier herunter.
Verfasst von
Michael van Rooijen
Michael is a FinOps practitioner, hands-on architect and consultant with 18+ years of experience delivering solutions on Microsoft’s platforms.
Unsere Ideen
Weitere Blogs
Contact
Let’s discuss how we can support your journey.



