OpenID Connect: Login mit OAuth, Teil 1 – Grundlagen

Seite 2: OpenID Connect

Inhaltsverzeichnis

Die Grundidee von OpenID Connect ist, die Authentifizierung eines Benutzers als eine Erweiterung des OAuth-2.0-Autorisierungsprozesses zu implementieren. Ein OAuth-Client kann die OpenID-Erweiterung anfordern, indem er einfach den Scope-Wert "openid" zu einem Autorisierungs-Request hinzufügt. Informationen über die Identität des Benutzers und die durchgeführte Authentifizierung werden dann in einem zusätzlichen (OpenID-spezifischen) Token, dem sogenannten ID Token, an den Client zurückgegeben. Und dieses Token wiederum ist auf das Login bei diesem Client eingeschränkt, ein anderer Client darf das betreffende ID Token nicht akzeptieren. Zusätzlich wird eine weitere OAuth-geschützte API für den Zugriff auf Benutzerdaten, der UserInfo Endpunkt, eingeführt.

Die Abbildung illustriert die Erweiterungen an der Standard-OAuth-Architektur, wobei die Erweiterungen in grüner Farbe hervorgehoben sind.

Wie zu sehen, wird jeder OAuth-2.0-Autorisierungsserver, der OpenID Connect implementiert, auch als OpenID Provider (OP) bezeichnet. Entsprechend heißt jeder OAuth-2.0-Client, der OpenID Connect benutzt, Relying Party (RP).

Den Ablauf eines einfachen Logins sollen nun ein Beispiel und die Übersichtsdarstellung illustrieren: Max verwendet verschiedene Dienste (wie E-Mail und Medienspeicher) des Anbieters Fun Corp. Dieser bietet ein zentrales Identity Management, um seinen Kunden nicht nur einen einheitlichen User Account, sondern auch eine SSO-Erfahrung über die verschiedenen Portale anbieten zu können. Technisch gesehen handelt es sich um einen OAuth-Autorisierungsserver, der auch als OpenID Provider fungiert.

Max möchte sich nun im Mediendienst einloggen, um seine Bilder anzuschauen. Der Mediendienst implementiert das Login unter Nutzung eines Authentifizierungs-Requests auf der Basis des OAuth-Grant-Type-Autorisierungscode wie folgt:

HTTP/1.1 302 Found
Location: https://accounts.funcorp.com/oauth/auth?
response_type=code
&scope=openid
&client_id=SDFGHJKLUZTREDFGHJ
&state=30096545678909876567
&redirect_uri=https%3A%2F%2Fmediaservice.funcorp.com%2Flogin_cb

Dieser Request leitet den Browser des Benutzers mittels HTTP Redirect um und sendet einen Authentifizierungs-Request an den Autorisierungsserver. Der einzige Unterschied zu einem "gewöhnlichen" OAuth-Autorisierungs-Request ist der Scope-Wert "openid". Dieser signalisiert dem Server, dass der Client Identitätsdaten des Benutzers abfragen möchte, und aktiviert damit dessen OpenID-Connect-"Maschinerie". Wie der Server den Benutzer authentifiziert, ist nicht spezifiziert und liegt in der Entscheidung des OpenID Provider. Für gewöhnlich werden hierfür Benutzername und Passwort verwendet, aber es lassen sich auch andere Mechanismen einsetzen. Wenn bereits eine Login-Sitzung beim OP existiert, ist typischerweise für eine bestimmte Zeit kein erneutes manuelles Login erforderlich (SSO).

Es sei an dieser Stelle darauf hingewiesen, dass diese Entkopplung der Anforderung nach Identitätsdaten sowie der aktuellen Art und Weise der Durchführung der Authentifizierung eine wesentliche Stärke von OpenID ist. Hierdurch kann der OP starke und wechselnde Authentifizierungsmethoden einsetzen und ausgefeilte Sicherheitsmaßnahmen gegen Angriffe implementieren, ohne dass die verschiedenen Anwendungen darauf Rücksicht nehmen müssen oder anzupassen wären.

Nach Abschluss der Authentifizierung stellt der Autorisierungsserver der Anwendung (wie bei OAuth üblich) einen Autorisierungs-Code aus. Dieser wird über den User Agent via HTTP Redirect an den Mediendienst gesendet.

HTTP/1.1 302 Found
Location: https://mediaservice.funcorp.com/login_cb?
code=287654789765678976556789
&state=30096545678909876567

Diese Nachricht entspricht dem OAuth-2.0-Standard und enthält keine OpenID-Connect-Spezifika. Neben dem Autorisierungs-Code enthält sie den Wert des state-Parameters, so wie ihn der Mediendienst an den Authentifizierungs-Request übergeben hatte. Der Dienst tauscht dann den Autorisierungs-Code am Token-Endpunkt des Autorisierungsservers gegen Tokens um.

POST /oauth/token HTTP/1.1
Host: accounts.funcorp.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic U0RGR0hKS0xVWlRSRURGR0hKOnRlc3QxMjM0
grant_type=authorization_code&
code=287654789765678976556789&
redirect_uri=https%3A%2F%2Fmediaservice.funcorp.com%2Flogin_cb

Auch dieser Request entspricht unverändert dem OAuth-Standard, die Response vom Autorisierungsserver hingegen enthält den neuen Ergebnis-Parameter "id_token":

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
"access_token": "IiwKICJleHAiOiAxMzExMjgxOTcwLAo",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOiJub25lIn0.
eyJpc3MiOiAiaHR0cHM6Ly9hY2NvdW50cy5mdW5jb3JwLm
NvbSIsCiAic3ViIjogIjc4Njc2NzY3NyIsCiAiYXVkIjogIlNERkdISk
tMVVpUUkVERkdISiIsCiAiZXhwIjogMTM4ODcwMDM2MDAw
MCwKICJpYXQiOjEzODg2OTY3NjAwMDAsCiAiYXV0aF90aW1
lIjogMTM4ODY5Njc2MDAwMAogImFjciI6ICJ1cm46ZnVuY29
ycDpjb206bGV2ZWwtMSJ9."
}

Wie der Name vermuten lässt, enthält dieser Parameter das erwähnte ID Token. Es enthält Identitätsdaten und Informationen über den Authentifizierungsprozess in Form eines sogenannten JSON Web Token. Um dieses gegen Modifikation und andere Angriffe abzusichern, lässt es sich digital signieren und verschlüsseln. Welche Verfahren im konkreten Fall zum Einsatz kommen, erfährt der Client, wenn er den ersten Teil des ID Token bis zum ersten Trennzeichen '.' Base64URL-dekodiert, um den sogenannten Header zu extrahieren. Im Beispiel ergibt das folgendes JSON-Objekt:

{"alg":"none"}

Dieser Header bedeutet, dass im Beispiel keine Signatur (und auch keine Verschlüsselung) verwendet wird. Das ist auch nicht unbedingt erforderlich, da der Client die Daten vom OP über einen HTTPS-geschützten Kanal erlangt.

Die eigentlichen Nutzdaten des JSON Web Token erlangt der Client, indem er den zweiten Teil des ID Token bis zum nächsten Trennzeichen '.' Base64URL-dekodiert:

{
"iss": "https://accounts.funcorp.com",
"sub": "786767677",
"aud": "SDFGHJKLUZTREDFGHJ",
"exp": 1388700360000,
"iat":1388696760000,
"auth_time": 1388696760000
"acr": "urn:funcorp:com:level-1"
}

Wie zu sehen ist, handelt es sich bei einem JSON Web Token um ein einfach zu verarbeitendes JSON-Dokument mit diversen Feldern, jedes enthält einen sogenannten "Claim" zum Benutzer oder zum Authentifizierungsprozess.

Der Claim "iss" (Issuer) identifiziert den OpenID Provider, in diesem Fall den Autorisierungsserver von Fun Corp. Der Claim "sub" (Subject) enthält die Benutzer-ID des in diesem Prozess authentifizierten User Account. Der Wert von sub ist in Bezug auf einen bestimmten Client stabil und lässt sich zur Anlage und Wiedererkennung eines Kontos beim Client verwenden. Wichtig ist anzumerken, dass "sub" immer nur innerhalb des Issuers eindeutig ist. Clients, die verschiedene OPs für das Login verwenden, sollten also das Tupel (iss, sub) zur Identifikation des betreffenden Accounts verwenden.

Der Claim "aud" (Audience) identifiziert die legitimen Empfänger des Tokens, im einfachsten Fall enthält er nur die client_id der Relying Party. Er kann aber auch weitere Empfänger besitzen. Wie erwähnt, darf ein ID Token ausschließlich zum Login beim betreffenden Client und den zusätzlichen Empfängern verwendet werden (das wird auch als "Audience Restriction" bezeichnet). Durch diese Maßnahme soll das Hijacking von Identitäten durch böswillige Anwendungen (Substitutionsangriff) verhindert werden. Für weitere Informationen zu Substitutionsangriffen sei auf dieses und dieses Dokument verwiesen.

Es ist also essenziell, dass der Client prüft, dass der Claim "aud" seine client_id enthält. Diese Anforderung ist im Beispiel (wenig überraschend) erfüllt. Der Wert von "aud" ist derselbe Wert, wie ihn der Client im initialen Authentifizierungs-Request in den Parameter client_id übergeben hat. Damit darf der Client das Login-Ergebnis verarbeiten.

Die Claims "iat" (Issued At) und "exp" (Expires At) geben Auskunft, wann das Token erzeugt wurde und bis wann es gültig ist. Die Empfänger dürfen das Token nach Ablauf der Gültigkeit nicht mehr
verwenden. Typischerweise besteht ein Zusammenhang zwischen der Gültigkeitsdauer und dem kryptographischen Schutz des Tokens.

Im Claim "auth_time" gibt der OP Auskunft darüber, wann der Benutzer authentifiziert wurde. Wenn der OP SSO und gegebenenfalls sogar langlebige Sitzungen (solche Sitzungen überdauern auch das Schließen des Browsers) unterstützt, kann dieser Zeitpunkt weit in der Vergangenheit liegen. Im Claim "acr" (Authentication Context Class Reference) kann der Client erfahren, welche Methode(n) für die Authentifizierung des Benutzers zum Einsatz kam. Der Wert von "acr" ist eine Abstraktion der Authentifizierungsmethoden und der Gesamtheit der angewendeten Policies (auch Account Management etc.). Wenn der Client spezielle Anforderungen an den Zeitpunkt und die Qualität der Authentifizierung hat, dann kann er diese durch weitere Parameter des Authentifizierungs-Requests ausdrücken. Diese Möglichkeiten werden im Folgeartikel beschrieben. Das ID Token lässt sich darüber hinaus auch als Container für beliebige weitere Benutzerdaten verwenden, auch dieses Thema beleuchtet der zweite Artikel.

Auf der Basis der Daten im ID Token kann der Mediendienst den Benutzer auf sichere Art und Weise identifizieren und einloggen. Im Beispiel sucht und findet der Mediendienst den Account mit der Benutzer-ID "786767677" und lädt dessen Daten.

Die gezeigte Vorgehensweise lässt sich prinzipiell mit jedem OAuth Grant Type kombinieren. Der Standard definiert die Abbildung für Autorisierungs-Code, Implicit Grant und Refresh Tokens. Beim Implicit Grant, der insbesondere für die Integration von JavaScript-Anwendungen gedacht ist, sendet der OAuth-Autorisierungsserver die Ergebnisse des Autorisierungsprozesses in einem sogenannten URL-Fragment der Redirect-URL direkt zum Client. Bei Verwendung dieses Grant Type im Kontext von OpenID wird das ID Token folgerichtig als weiteres Element des Redirects vom Autorisierungsserver zum Client wie folgt gesendet:

HTTP/1.1 302 Found
Location: https://client.example.org/cb#
access_token=SlAV32hkKG
&token_type=bearer
&id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso
&expires_in=3600
&state=af0ifjsldkj

Daraus ergeben sich wichtige Unterschiede zum Autorisierungs-Code in Bezug auf die Sicherheit des Ablaufs. Da das ID Token nicht auf einem direkten und geschützten Kanal zwischen OP und RP, sondern über den Umweg des Browsers des Benutzers ausgetauscht wird, ist das ID Token unbedingt durch eine digitale Signatur gegen Modifikation zu schützen. Ansonsten könnte sich der Benutzer mit einer beliebigen Benutzer-ID beim Client einloggen. Des weiteren muß das ID Token gegen Replay-Angriffe abgesichert werden. Hierzu wird es durch ein sogenanntes "Nonce" an einen bestimmt Authentifizierungs-Vorgang gebunden.