Eine Mobile-App meldet einen Benutzer bei einem Identity Provider an und reicht das Access-Token an dein Backend weiter. Das Backend ruft den Profil-Endpunkt des Providers auf, bekommt eine E-Mail-Adresse und legt eine Session an. Alle Tests laufen durch. Damit kann sich aber auch jede andere App, bei der sich der Benutzer mit demselben Provider angemeldet hat, mit ihrem eigenen Access-Token als dieser Benutzer bei deiner App anmelden, weil nichts prüft, für welche App das Token ausgestellt wurde.
OpenID Connect (OIDC) schließt diese Lücke. Es ergänzt OAuth 2.0 um ein ID-Token: ein signiertes JWT, in dem der Provider angibt, wer der Benutzer ist, für welche App das Token bestimmt ist und wann sich der Benutzer angemeldet hat.
Dieser Beitrag zeichnet den Authorization Code Flow mit PKCE als Sequenzdiagramm und behandelt, was OIDC hinzufügt: den Scope openid, nonce, die Validierung des ID-Tokens, Discovery, Schlüsselrotation, UserInfo und Logout.
OAuth 2.0 vs. OpenID Connect
Bei OAuth 2.0 geht es um Zugriff: ein Token, um im Namen des Benutzers eine API aufzurufen. Bei OpenID Connect geht es um Identität: wer sich angemeldet hat.
| OAuth 2.0 | OpenID Connect | |
|---|---|---|
| Beantwortete Frage | Darf diese App diese API aufrufen? | Wer ist der Benutzer, und wann hat er sich angemeldet? |
| Token | Access-Token | ID-Token |
| Token ist gedacht für | Die API | Deine App |
| Token-Format | Nicht festgelegt: opak oder JWT | Ein signiertes JWT |
| So fragst du an | Scopes, die die API definiert | scope enthält openid |
| Definiert | Grants und Endpunkte | Claims, UserInfo, Discovery, Logout |
Auf HTTP-Ebene ist eine OIDC-Anmeldung ein OAuth-Autorisierungsrequest mit openid im Scope, und die Token-Antwort enthält neben dem Access-Token ein id_token. Das OAuth-2.0-Flow-Diagramm behandelt die OAuth-Hälfte im Detail.
Der Ablauf Schritt für Schritt
OpenID Connect Core 1.0 nennt deine App Relying Party (RP) und den Autorisierungsserver OpenID Provider (OP). Was durch den Browser läuft, der Front-Channel, kann in URLs, den Verlauf und Logs gelangen; Aufrufe von deinem Backend an den Provider, der Back-Channel, nicht. Die Nummern entsprechen dem Diagramm.
- Start (1–3). Der Benutzer klickt auf „Anmelden“. Deine App speichert frische Werte für
state,nonceund den PKCE-code_verifierin der Session und leitet den Browser zum Provider weiter. - Authentifizierungsrequest (4). Der Browser folgt dem Redirect:
GET /authorize?response_type=code
&client_id=orders-web
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
&scope=openid%20profile%20email
&state=7fJq2LbX9aQe
&nonce=n-0S6_WzA2Mj
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256 HTTP/1.1
Host: op.example.com
- Login und Einwilligung (5). Der Provider authentifiziert den Benutzer (Passwort, Passkey, MFA oder eine bestehende Session) und holt die Einwilligung ein. Deine App sieht nie Zugangsdaten.
- Code zurück (6–7). Der Provider leitet an die registrierte
redirect_uriweiter, mit einem kurzlebigencodeund demselbenstate, den deine App mit der Session vergleicht. - Token-Request (8–9). Dein Backend sendet
code,redirect_uriundcode_verifierper POST zusammen mit seinen Client-Credentials. Der Provider prüft den Verifier gegen die Challenge und gibt einid_token, einaccess_tokenund oft einrefresh_tokenzurück. - Validierung (10). Deine App validiert das ID-Token und holt Signaturschlüssel nur dann, wenn der darin genannte Schlüssel nicht im Cache liegt.
- UserInfo (11–12), optional. Nur wenn dem ID-Token Claims fehlen, die du brauchst.
- Session (13). Deine App legt ihre eigene Session an und setzt ein Cookie.
Wovor state, nonce und PKCE schützen
| Wert | Übertragen in | Geprüft von | Verhindert |
|---|---|---|---|
state |
Schritt 4 und dem Redirect zurück | Deine App | Eine Login-Antwort, die der Benutzer nie gestartet hat (CSRF) |
nonce |
Schritt 4, dann im ID-Token | Deine App | Replay des ID-Tokens und, bei vertraulichen Clients, Code-Injection |
code_verifier |
Sein SHA-256-Hash in Schritt 4, der Wert selbst in Schritt 8 | Der Provider | Das Einlösen eines gestohlenen Codes |
Die OAuth Security Best Current Practice, RFC 9700, erlaubt in manchen Fällen, dass ein Wert einen anderen ersetzt, aber jeder kostet nur einen Zufallswert, also schick alle drei. Öffentliche Clients wie Single-Page- und Mobile-Apps müssen PKCE (RFC 7636) mit S256 verwenden.
Ein Blick ins ID-Token
Das ID-Token aus Schritt 9, dekodiert:
{
"iss": "https://op.example.com",
"sub": "248289761001",
"aud": "orders-web",
"nonce": "n-0S6_WzA2Mj",
"iat": 1793736400,
"exp": 1793740000,
"auth_time": 1793736390
}
| Claim | Bedeutung | Deine App |
|---|---|---|
iss |
Issuer, eine https-URL | Muss exakt dem per Discovery ermittelten issuer entsprechen |
sub |
Benutzer-ID, beim Issuer nie neu vergeben, höchstens 255 ASCII-Zeichen | Nutzt iss plus sub als Kontoschlüssel |
aud |
Audience, ein String oder ein Array | Muss darin ihre client_id finden |
exp, iat |
Ablauf- und Ausstellungszeit in Unix-Sekunden | Lehnt abgelaufene oder sehr alte Tokens ab |
nonce |
Der in Schritt 4 gesendete Wert | Muss zur Session passen |
auth_time |
Wann sich der Benutzer tatsächlich authentifiziert hat | Prüft ihn, wenn du max_age sendest |
acr, amr |
Vertrauensniveau und Methoden, etwa pwd oder mfa |
Prüft sie vor sensiblen Aktionen |
Core stellt klar, dass nur iss plus sub einen Benutzer identifiziert: E-Mail-Adressen ändern sich und können neu vergeben werden.
Das ID-Token validieren
Abschnitt 3.1.3.7 von Core listet die Regeln auf:
- Fixiere den Algorithmus (standardmäßig RS256), statt dem
alg-Header zu vertrauen, und akzeptiere niemalsnone. - Prüfe die Signatur mit dem JWKS-Schlüssel, den
kidnennt. Core erlaubt einem Client im Code Flow, sich stattdessen auf TLS zu verlassen, weil das Token direkt vom Token-Endpunkt kam, aber die Prüfung kostet wenig. - Prüfe
issgegen den per Discovery ermittelten Issuer, Zeichen für Zeichen. - Prüfe
audauf deineclient_idund lehne Audiences ab, denen du nicht vertraust. - Prüfe
expundiatund erlaube dabei Sekunden bis wenige Minuten Uhrenabweichung. - Prüfe
noncegegen die Session und lösche den Wert danach. - Prüfe
auth_timeundacr, wenn du sie angefordert hast.
Erst dann legst du eine Session an. Mit der Bibliothek jose in Node.js:
import { createRemoteJWKSet, jwtVerify } from 'jose'
// issuer and jwks_uri come from the discovery document.
// The key set is cached; an unknown kid triggers a refetch
// (at most once every 30 seconds by default).
const JWKS = createRemoteJWKSet(new URL(oidc.jwks_uri))
const { payload } = await jwtVerify(tokens.id_token, JWKS, {
issuer: oidc.issuer, // exact match
audience: CLIENT_ID, // must appear in aud
algorithms: ['RS256'], // never trust the header alone
clockTolerance: 60, // seconds of clock skew
maxTokenAge: 600, // rejects an old iat
})
if (payload.nonce !== session.nonce) throw new Error('nonce mismatch')
Discovery und Schlüsselrotation
OpenID Connect Discovery 1.0 legt die Einstellungen eines Providers in einem einzigen JSON-Dokument ab, erreichbar unter der Issuer-URL plus /.well-known/openid-configuration. Das Dokument von Google, gekürzt auf die Felder, die dieser Flow nutzt, zum Zeitpunkt des Schreibens (November 2026):
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"id_token_signing_alg_values_supported": ["RS256"],
"code_challenge_methods_supported": ["plain", "S256"]
}
Der issuer muss mit der Issuer-URL, von der du das Dokument abgerufen hast, und mit dem iss jedes ID-Tokens identisch sein. Konfiguriere den erwarteten Issuer selbst; übernimm ihn nie aus einem Token, das du nicht geprüft hast.
Schlüssel rotieren über die jwks_uri: Der Provider fügt einen neuen Schlüssel hinzu, signiert damit und lässt den alten noch eine Weile veröffentlicht (Core, Abschnitt 10.1.1). Cache das Schlüsselset, beachte Cache-Control, lade es bei einer unbekannten kid neu und begrenze die Rate dieser Neuabrufe. Das JWT-Authentifizierungs-Flow-Diagramm behandelt dieselbe Mechanik für Access-Tokens.
UserInfo
UserInfo liefert Claims für die gewährten Scopes (profile, email, address, phone), wenn du den Endpunkt mit dem Access-Token aufrufst. Die Antwort enthält immer sub, das exakt dem sub des ID-Tokens entsprechen muss, sonst verwirfst du die Antwort.
Logout-Optionen
Drei finale OpenID-Spezifikationen behandeln die Abmeldung:
| Spezifikation | So funktioniert sie | Worauf du achten musst |
|---|---|---|
| RP-Initiated Logout 1.0 | Deine App leitet mit id_token_hint zum end_session_endpoint des Providers weiter |
Deine App muss auch ihre eigene Session beenden |
| Front-Channel Logout 1.0 | Der Provider lädt die Logout-URL jeder App in einem iframe | Das Blockieren von Drittanbieter-Cookies kann es aushebeln |
| Back-Channel Logout 1.0 | Der Provider schickt per POST ein signiertes Logout-Token an jede App | Deine App muss die Session serverseitig finden und beenden |
Core weist außerdem darauf hin, dass das exp des ID-Tokens nichts mit der Lebensdauer der Session zu tun hat.
Fehler, die in Reviews auffallen
- Ein Access-Token als Identitätsnachweis. Die Szene vom Anfang. Melde Benutzer nur mit einem ID-Token an, das dein Backend vom Token-Endpunkt bekommen und validiert hat.
audodernoncenicht prüfen. Jede beim selben Provider registrierte App bekommt gültig signierte ID-Tokens mit demselbeniss; nuraudsagt, dass ein Token für dich ausgestellt wurde, und nurnoncebindet es an diesen Login.- Der Implicit Flow.
response_type=id_token tokenlegt Tokens ins URL-Fragment; laut RFC 9700 sollten Clients keine Response Types verwenden, die Access-Tokens in der Autorisierungsantwort zurückgeben. Nutzecodemit PKCE, auch in Single-Page-Apps. - ID-Tokens, die an APIs geschickt werden. APIs sollten nur Access-Tokens akzeptieren, die für sie ausgestellt wurden; das API-Gateway-Architekturdiagramm zeigt, wo diese Prüfung meist sitzt.
Das Diagramm zum OpenID-Connect-Ablauf
Diagramminhalt
- User
- Browser
- Relying party (your app)
- OpenID provider
- all checks must pass
- claims missing from the ID token
- ID token: for your app (aud = client_id)
- Access token: for UserInfo and APIs
- 1. Click Sign in
- 2. GET /login
- 3. 302 to the provider's /authorize
- 4. GET /authorize: scope=openid, state, nonce, code_challenge
- 5. Log in (password, passkey, MFA) and consent
- 6. 302 to redirect_uri with code, state
- 7. GET /callback?code&state
- 8. POST /token: code, code_verifier, client auth
- 9. id_token, access_token
- 10. GET jwks_uri (when kid is not cached)
- 11. GET /userinfo, Bearer access_token
- 12. Claims; sub must match the ID token
- 13. 302 to the app, Set-Cookie: session
So liest du es:
- Vier Teilnehmer: der Benutzer, der Browser, deine App (die Relying Party) und der OpenID Provider. Die Zeit läuft nach unten.
- Farben zeigen den Kanal: Blau durch den Browser, Petrol von deinem Backend zum Provider, Violett für die Aktionen des Benutzers. Gestrichelte Pfeile sind Antworten und Redirects.
- Schritte 3 und 4 tragen
scope=openid,state,nonceund die PKCE-Challenge; Schritt 8 trägt den Verifier. - Das
critical-Fragment enthält den Schlüsselabruf und die Prüfungen, die bestehen müssen, bevor eine Session existiert. Dasopt-Fragment ist der UserInfo-Aufruf. - Die rechte Spalte ordnet Discovery-Felder den Schritten zu, listet die PKCE-Parameter auf, dekodiert ein ID-Token und zeigt, welches Token für wen bestimmt ist.
OpenID-Connect-Diagramme klar zeichnen
- Gib dem Browser eine eigene Lebenslinie. Die Trennung zwischen Front- und Back-Channel ist die Sicherheitsgeschichte dieses Flows.
- Schreib die geprüften Parameter an die Pfeile:
state,nonce,code_challenge,code_verifier. - Schreib die Validierung aus. „Token validieren“ lässt sich nicht reviewen; „Signatur, iss, aud, exp, iat, nonce“ schon.
- Sag, welches Token wohin geht, samt Audience. Viele OIDC-Bugs sind ein Token, das bei der falschen Partei gelandet ist.
- Ein Flow pro Diagramm. Zeichne Login, Refresh und Logout getrennt, mit Platzhalter-IDs und -Tokens.
Das Sequenzdiagramm-Beispiel behandelt die Notation; das SAML-SSO-Flow-Diagramm zeigt das XML-basierte Protokoll, nach dem Enterprise-Kunden immer noch fragen.
In Archboard nachbauen
- Melde dich mit Google oder GitHub an und leg ein Diagramm an.
- Füge aus dem Sequenz-Pack einen Akteur für den Benutzer und drei gleich hohe Lebenslinie-Nodes hinzu: Browser, Relying party (your app) und OpenID provider.
- Zeichne die 13 Nachrichten als gerade, nummerierte Kanten von oben nach unten. Zeichne Antworten gestrichelt und färbe Front- und Back-Channel unterschiedlich.
- Füge aus dem Sequenz-Pack ein Kombiniertes Fragment mit dem Operator
criticalum den Schlüsselabruf und die Prüfungen hinzu und ein zweites Kombiniertes Fragment mitoptum den UserInfo-Aufruf. - Füge neben der Relying Party Text aus dem Basis-Pack für die Session-Werte und Prüfungen hinzu, dazu eine Farblegende.
- Füge einen Markdown-Block aus dem Basis-Pack für Discovery, PKCE und ein dekodiertes ID-Token hinzu und zwei Token-Nodes aus dem Sicherheits-Pack für das ID-Token und das Access-Token.
Das Beispiel nutzt 9 Diagramm-Nodes und bleibt damit im Free-Plan. Dein KI-Agent kann es auch über MCP zeichnen.
FAQ
Was ist der Unterschied zwischen OAuth 2.0 und OpenID Connect?
OAuth 2.0 gibt einer App ein Access-Token, um eine API aufzurufen. OpenID Connect setzt die Anmeldung obendrauf: den Scope openid, ein ID-Token, das sagt, wer der Benutzer ist, sowie standardisierte Discovery- und UserInfo-Endpunkte.
Ist ein ID-Token dasselbe wie ein Access-Token? Nein. Das ID-Token ist ein signiertes JWT für deine App, das sagt, wer sich angemeldet hat. Das Access-Token ist für APIs bestimmt, auch für UserInfo. Schick ID-Tokens nie an APIs und akzeptiere Access-Tokens nie als Login-Nachweis.
Brauche ich PKCE, wenn ich schon eine Nonce sende? Nutze beides. RFC 9700 schreibt PKCE für öffentliche Clients vor. Ein vertraulicher Client darf sich mit zusätzlichen Vorkehrungen auf die Nonce verlassen, aber PKCE verhindert, dass ein gestohlener Code eingelöst wird, während die Nonce einen eingeschleusten Code erst nach der Token-Antwort erkennt.
Wie validiere ich ein ID-Token?
Prüfe die Signatur mit dem JWKS-Schlüssel, der zu seiner kid passt, und prüfe dann, dass iss dem Issuer entspricht, aud deine Client-ID enthält, exp nicht überschritten ist, iat aktuell ist und nonce zur Session passt.
Was ist /.well-known/openid-configuration?
Das Discovery-Dokument von OpenID Connect. Es listet Issuer, Endpunkte, Algorithmen und jwks_uri eines Providers auf, sodass sich deine App über eine einzige URL selbst konfigurieren kann.