SPA braucht sicheren Login? Authorization Code Flow mit PKCE verwenden

Veröffentlicht am:

Single-Page Applications laufen im Browser.

Das bedeutet: Sie koennen kein Client Secret sicher speichern.

Fuer eine browserbasierte App ist das sichere Muster:

Authorization Code Flow mit PKCE.

PKCE steht fuer Proof Key for Code Exchange.

Unterschied zum Web-App Flow

Der vorherige Trip hat Authorization Code Flow fuer eine serverseitige Web-App verwendet.

Diese App konnte ein client_secret auf dem Server sicher speichern und beim Austausch des Authorization Code gegen Tokens verwenden.

Eine SPA ist anders.

Ihr Code laeuft im Browser des Benutzers. Alles, was in der SPA liegt, kann vom Benutzer eingesehen, kopiert oder veraendert werden.

Deshalb darf eine SPA kein Client Secret verwenden.

Stattdessen verwendet die SPA PKCE:

  • die SPA erzeugt einen zufaelligen code_verifier
  • beim Sign-in sendet sie nur einen gehashten code_challenge
  • spaeter beweist sie den Besitz des urspruenglichen code_verifier
  • Microsoft Entra ID stellt Tokens nur aus, wenn der Verifier zur Challenge passt

Das Ziel ist also aehnlich:

Benutzer anmelden -> Authorization Code erhalten -> Code gegen Tokens tauschen -> API aufrufen

Aber der Schutzmechanismus ist anders:

Serverseitige Web-App: Client Secret schuetzt den Code-Austausch
SPA: PKCE schuetzt den Code-Austausch

Dieses Muster wird verwendet, wenn:

  • ein Benutzer vorhanden ist
  • die App im Browser laeuft
  • kein Client Secret geschuetzt werden kann
  • die Authentifizierung ueber Microsoft Entra ID erfolgt
  • die App delegierten Zugriff auf eine API benoetigt

In diesem Beispiel:

  • CloudTrips SPA ist die Browser-Anwendung
  • CloudTrips API ist die geschuetzte Resource
  • Employee ist der angemeldete Benutzer

Die Authentifizierung erfolgt mit dem OAuth 2.0 Authorization Code Flow mit PKCE. Die Autorisierung wird ueber delegierte Berechtigungen gesteuert.

Architektur

Employee -> CloudTrips SPA -> Microsoft Entra ID -> CloudTrips API

Die SPA leitet den Browser zu Microsoft Entra ID weiter.

Die SPA erhaelt einen Authorization Code.

Die SPA tauscht den Code mit einem PKCE Code Verifier gegen Tokens aus.

Es wird kein Client Secret verwendet.

PKCE Verifier und Challenge

PKCE teilt einen geheimen Nachweis auf zwei Momente auf.

Zuerst erzeugt die SPA eine lange zufaellige Zeichenfolge:

code_verifier=RANDOM_SECRET_CREATED_IN_THE_BROWSER

Die SPA behaelt diesen Wert lokal fuer diesen Sign-in-Versuch.

Danach hasht die SPA diesen Wert mit SHA-256 und kodiert das Ergebnis als base64url:

code_challenge=BASE64URL(SHA256(code_verifier))
code_challenge_method=S256

Der Browser sendet den code_challenge im Authorize Request an Microsoft Entra ID.

Der Browser sendet den code_verifier zu diesem Zeitpunkt noch nicht.

Spaeter, wenn die SPA den Authorization Code erhalten hat, sendet sie den urspruenglichen code_verifier an den Token Endpoint.

Microsoft Entra ID hasht diesen Verifier erneut und prueft, ob das Ergebnis zum frueheren code_challenge passt.

Das ist der zentrale PKCE-Nachweis:

Authorize Request: code_challenge senden
Token Request: code_verifier senden
Entra Pruefung: SHA256(code_verifier) muss zu code_challenge passen

CloudTrips API App erstellen

Erstelle zuerst die Resource-Anwendung.

Gehe zu:

Entra ID > App registrations > New registration

Erstelle eine Anwendung:

Name: CloudTrips-API

Diese App Registration repraesentiert die API, die Access Tokens empfaengt und validiert.

Microsoft Entra App-Registrierung mit CloudTrips API als Anwendungsname

API exponieren

Oeffne in CloudTrips-API:

Expose an API

Setze die Application ID URI:

api://<cloudtrips-api-application-id>

Dieser Bezeichner wird zur Audience fuer Access Tokens, die fuer die CloudTrips API ausgestellt werden.

CloudTrips API App Registration auf der Expose an API Seite mit konfigurierter Application ID URI

Delegierten Scope erstellen

Fuege in CloudTrips-API einen delegierten Scope hinzu:

Expose an API > Add a scope

Beispiel:

Scope name: Trips.Read
Who can consent: Admins and users
Admin consent display name: Read CloudTrips trips
User consent display name: Read your CloudTrips trips
State: Enabled

Die CloudTrips API kann spaeter pruefen, ob das eingehende Token diesen Scope enthaelt.

Add a scope Formular fuer CloudTrips API mit Trips.Read und Consent-Einstellungen

CloudTrips SPA erstellen

Erstelle jetzt die Browser-Client-Anwendung.

Gehe zu:

Entra ID > App registrations > New registration

Erstelle eine Anwendung:

Name: CloudTrips-SPA
Supported account types: Single tenant
Redirect URI platform: Single-page application
Redirect URI: http://localhost:5173/auth/callback

Die Redirect URI ist die Adresse, an die Microsoft Entra ID den Browser nach der Anmeldung zuruecksendet.

In Produktion verwendest du die echte SPA-URL.

Microsoft Entra App-Registrierung mit CloudTrips SPA und Single-page application Redirect URI

SPA Authentication Settings pruefen

Oeffne in CloudTrips-SPA:

Authentication

Pruefe, dass die Plattform ist:

Single-page application

Und dass die Redirect URI lautet:

http://localhost:5173/auth/callback

Erstelle kein Client Secret fuer die SPA.

Eine SPA ist ein Public Client. Jedes Secret im Browser-Code kann von Benutzern gelesen werden.

CloudTrips SPA Authentication Seite mit Single-page application Redirect URI

API-Berechtigung hinzufuegen

Gehe zu:

App registrations > CloudTrips-SPA > API permissions

Waehle:

Add a permission

Waehle:

My APIs > CloudTrips-API > Delegated permissions > Trips.Read

Damit darf die SPA ein Access Token fuer die CloudTrips API im Namen des angemeldeten Benutzers anfordern.

CloudTrips SPA API permissions Seite mit ausgewaehlter delegierter CloudTrips API Berechtigung Trips.Read

Wenn dein Tenant Administratorfreigabe verlangt, klicke:

Grant admin consent

CloudTrips SPA API permissions Seite nach erteiltem Admin Consent

Authorization Request mit PKCE

Zur Laufzeit wird der Browser weitergeleitet zu:

GET https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize

Die Request-Parameter enthalten:

client_id=CLOUDTRIPS_SPA_CLIENT_ID
response_type=code
redirect_uri=http://localhost:5173/auth/callback
scope=openid profile api://<cloudtrips-api-application-id>/Trips.Read
state=<random-state-value>
code_challenge=<hashed-code-verifier>
code_challenge_method=S256

Die wichtigen PKCE-Werte sind:

Wert Bedeutung
code_verifier geheimer Zufallswert, den die SPA erzeugt und bis zum Token Request lokal behaelt
code_challenge gehashter Wert, der aus dem Verifier abgeleitet und im Authorize Request gesendet wird
code_challenge_method S256, also SHA-256 fuer die Challenge

Microsoft Entra ID speichert die Challenge zusammen mit dem Authorization Code.

Zu diesem Zeitpunkt hat Microsoft Entra ID die Challenge gesehen, aber noch nicht den Verifier.

Microsoft Entra Anmeldeseite fuer den SPA-Benutzer

Callback mit Authorization Code

Nach der Anmeldung leitet Microsoft Entra ID den Browser zurueck an:

http://localhost:5173/auth/callback

Der Callback enthaelt einen Authorization Code.

Die SPA prueft den state Wert und bereitet danach den Token Request vor.

Der Authorization Code allein reicht nicht aus.

Die SPA muss ihn zusammen mit demselben code_verifier einloesen, der vor der Weiterleitung erzeugt wurde.

Browser Developer Tools oder Callback-Seite mit SPA Callback und Authorization Code

Code mit Code Verifier austauschen

Die SPA sendet den Authorization Code an Microsoft Entra ID:

POST https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token

Request Body:

client_id=CLOUDTRIPS_SPA_CLIENT_ID
grant_type=authorization_code
code=AUTHORIZATION_CODE
redirect_uri=http://localhost:5173/auth/callback
code_verifier=ORIGINAL_RANDOM_CODE_VERIFIER
scope=openid profile api://<cloudtrips-api-application-id>/Trips.Read

Es gibt kein client_secret.

Microsoft Entra ID hasht den gesendeten code_verifier und vergleicht ihn mit dem code_challenge aus dem Authorization Request.

Wenn beide Werte passen, stellt Entra Tokens aus.

Wenn der Verifier fehlt, veraendert wurde oder aus einer anderen Browser-Session stammt, schlaegt der Token Request fehl.

Browser Developer Tools mit Token Request, grant_type authorization_code und code_verifier aber ohne client_secret

Access Token Claims

Die SPA erhaelt ein Access Token fuer die CloudTrips API.

Decodiere das Access Token.

Wichtige Claims:

Claim Bedeutung
aud API, die das Token akzeptieren soll
scp Delegierte Berechtigungen, die der SPA erteilt wurden
sub Stabiler Benutzerbezeichner fuer diese App
tid Tenant ID
iss Token Issuer

Der wichtige Nachweis in diesem Trip ist:

{
  "aud": "api://<cloudtrips-api-application-id>",
  "scp": "Trips.Read"
}

Decodiertes Access Token mit CloudTrips API Audience und delegiertem Trips.Read Scope

Warum PKCE wichtig ist

Ohne PKCE koennte jemand versuchen, einen gestohlenen Authorization Code einzuloesen.

Mit PKCE ist der Authorization Code an den urspruenglichen code_verifier gebunden.

Das bedeutet: Der Token Endpoint verlangt den Nachweis, dass derselbe Browser-Flow, der die Anmeldung gestartet hat, auch den Code einloest.

Unterschied zum serverseitigen Web-App Flow

Thema Serverseitige Web-App SPA mit PKCE
Client-Typ Confidential Public
Client Secret Ja Nein
Token Exchange Serverseitig Browser/Public Client
Schutz Client Secret PKCE Code Verifier
Typische Redirect URI /signin-oidc /auth/callback

Enterprise-Hinweis

Echte Enterprise-Umgebungen haben normalerweise mehrere Stages:

  • DEV
  • TEST oder STAGE
  • PROD

Verwende separate App Registrations pro Umgebung, wenn Redirect URIs, API Audiences oder Consent-Grenzen unterschiedlich sind.

Beispiel:

CloudTrips-SPA-DEV
CloudTrips-SPA-TEST
CloudTrips-SPA-PROD

CloudTrips-API-DEV
CloudTrips-API-TEST
CloudTrips-API-PROD

Das bietet:

  • isolierte Redirect URIs
  • klarere Token Audiences
  • sicherere Consent-Grenzen fuer Produktion
  • saubereres Auditing und Troubleshooting