Vom Passwort zu OAuth2: Eine Reise durch moderne Authentifizierung

Vom Passwort zu OAuth2: Eine Reise durch moderne Authentifizierung
By Matthias Petermann / on 09.11.2025

Einleitung

Wenn man Webentwicklung schon länger macht, kennt man das Muster: Loginformular → Benutzername + Passwort → Session-Cookie – und alles funktioniert. Bis zu dem Moment, wo man plötzlich eine API braucht, eine Mobile-App hinzukommt oder externe Partner sich anbinden sollen. Dann merkt man schnell: Das alte Modell stößt an Grenzen.

Ich bin in diesen Themen schon öfter mit OAuth in Berührung gekommen – allerdings meist aus der Konsumentenperspektive: „Login mit GitHub“, „Zugriff über ein API-Token“, all diese Dinge, die im Alltag so selbstverständlich wirken. Doch hinter diesen einfachen Klicks steckt ein komplexes Geflecht aus Protokollen, Tokens und Sicherheitsmechanismen.

Aus einer gewissen Unzufriedenheit mit den bestehenden Lösungen – oft zu proprietär, zu schwergewichtig oder schlicht zu komplex für kleine, dezentrale Edge-Anwendungen – entstand der Wunsch, das Thema wirklich zu verstehen. Mein Ziel: einen eigenen, schlanken Auth-Service zu entwickeln, der offen, nachvollziehbar und robust ist.

Dafür braucht es ein tiefes Verständnis der zugrunde liegenden Prinzipien – und genau hier setzt dieser Artikel an: eine kompakte und nachvollziehbare Erklärung, wie ich sie mir selbst gewünscht hätte.


1. Klassisches Login: vertraut, aber begrenzt

Beim klassischen Web-Login läuft alles in einem geschlossenen System:

Browser --> POST /login (username, password)
Server  --> Set-Cookie: session_id=abc123
Browser --> nutzt Cookie für alle weiteren Requests

Das ist bequem, aber problematisch, sobald man aus der „einen Anwendung“ herauswächst.

Wo es knirscht:

  • Mehrgerätewelt: Nutzer sind gleichzeitig in Web, Mobile und Desktop aktiv – Cookies funktionieren nicht überall gleich.
  • APIs & Microservices: Eine Session ist an einen Server gebunden, nicht an einen verteilten Verbund.
  • Sicherheit: Session Hijacking, CSRF und Session-Leaks sind notorische Dauerbrenner.
  • Integrationen: Externe Dienste sollen Benutzer autorisiert, aber nicht direkt „einloggen“ können.
💡 Merke
Das klassische Login-Modell ist zustandsbehaftet. Der Server weiß, wer Sie sind – aber nur, solange er Ihre Session-ID kennt.

2. Warum moderne Systeme auf Tokens setzen

Statt einer Session im Server zu speichern, packt man heute alle nötigen Informationen in ein Token, das beim Request mitgeschickt wird.

Eigenschaft Session-Cookie Token (z. B. JWT)
Speicherung Serverseitig Im Client (Header, Storage)
Zustand Stateful Stateless
Gültigkeit Bis Logout oder Timeout Zeitlich begrenzt (exp)
Überprüfung Nur am Auth-Server möglich An jeder API validierbar
Skalierbarkeit Eingeschränkt Hoch (keine zentrale Session)
Client --> Authorization: Bearer <token>
API    --> prüft Signatur + Ablaufzeit
💡 Merke
Ein Token ist wie eine signierte Eintrittskarte. Der Auth-Server unterschreibt sie, und jede API kann selbst prüfen, ob sie echt ist.

3. Vom Eigenbau zu Standards: OAuth 2.0 & OpenID Connect

Früher hat jede Anwendung ihre eigene Login-Logik gebaut. Heute gibt es etablierte Standards:

  • OAuth 2.0 → regelt die Autorisierung (Darf App X auf Ressource Y zugreifen?)
  • OpenID Connect (OIDC) → ergänzt die Authentifizierung (Wer ist der Nutzer?)

Zentrale Rollen dabei:

Rolle Beschreibung
Resource Owner Der Nutzer, dessen Daten geschützt sind
Client Die Anwendung, die im Auftrag des Nutzers handelt
Authorization Server Führt Login durch und stellt Tokens aus
Resource Server (API) Prüft Tokens und liefert Daten
💡 Merke
OAuth beantwortet: „Darf diese App etwas tun?“ OIDC ergänzt: „Wer ist der Nutzer?“
📝 Hinweis
Nicht verwechseln: OAuth 1.0 war ein älterer, komplizierter Vorläufer und spielt heute keine Rolle mehr. Wenn hier von OAuth 2.0 die Rede ist, geht es um den modernen Standard, der gemeinsam mit OpenID Connect genutzt wird.

4. Authorization Code Flow mit PKCE – Schritt für Schritt

Der Authorization Code Flow ist der Standard für Browser- und Mobile-Apps. Er besteht aus drei klaren Phasen: Login, Token-Austausch und API-Zugriff.


🧩 Phase 1: Login & Code-Erzeugung

Teilnehmer: Nutzer | Client | Authorization Server

Nutzer           Client                   Auth-Server
 |                |                           |
 | 1) App öffnen  |                           |
 |--------------->|                           |
 |                | 2) GET /authorize?...     |
 |                |-------------------------->|
 |                | 3) Login-UI anzeigen      |
 |                |<--------------------------|
 | 4) Login-Daten eingeben                    |
 |--------------->|                           |
 |                | 5) Weiterleiten an Auth   |
 |                |-------------------------->|
 |                |                           | 6) Prüfe Nutzer & Consent
 |                |<--------------------------| 7) Redirect (callback)
 |                |                           | mit code=abc123&state=xyz

Der Nutzer wird vom Client an den Authorization Server weitergeleitet, um sich anzumelden. Nach erfolgreichem Login prüft der Server Identität und Zustimmung (Consent) und erzeugt einen einmaligen Authorization Code. Dieser wird per Redirect (Callback) an den Client zurückgegeben und dient als temporärer Nachweis des erfolgreichen Logins. Der Redirect erfolgt an die registrierte redirect_uri des Clients. Mit diesem Code kann der Client in der nächsten Phase sicher Tokens anfordern – ohne Passwörter oder Sitzungen weiterzugeben.

💡 Was ist PKCE?
PKCE (Proof Key for Code Exchange) schützt den Authorization Code Flow vor „Code Intercept Attacks“. Der Client erzeugt zu Beginn einen zufälligen Code Verifier und leitet nur dessen Hash (Code Challenge) an den Auth-Server weiter. Beim Token-Austausch muss er den ursprünglichen Verifier mitsenden – nur wenn beide Werte zusammenpassen, wird der Code akzeptiert. So kann ein abgefangener Authorization Code nicht von einem anderen Client wiederverwendet werden.

🧩 Phase 2: Token-Austausch mit PKCE

Teilnehmer: Client | Authorization Server

Client                     Auth-Server
 |                             |
 | 1) POST /token              |
 |    grant_type=authorization_code
 |    code=abc123              |
 |    code_verifier=<PKCE>     |
 |---------------------------->|
 |                             | 2) Validiere Code & PKCE
 |                             | 3) Signiere Tokens (Access, ID, Refresh)
 |<----------------------------| 4) Token Response
 |                             |

Der Client tauscht den erhaltenen Authorization Code gegen echte Tokens aus. Er sendet dazu eine Anfrage an den Token-Endpoint des Authorization Servers, inklusive des zuvor erzeugten PKCE-Verifiers. Der Server prüft, ob Code und Verifier zusammenpassen, signiert die neuen Tokens und gibt sie zurück. Das Ergebnis sind ein Access Token (für API-Aufrufe), ein ID Token (Identität des Nutzers) und optional ein Refresh Token (zur späteren Erneuerung).


🧩 Phase 3: Zugriff auf die API

Teilnehmer: Client | Resource Server (API)

Client                       API
 |                            |
 | 1) GET /resource           |
 |    Authorization: Bearer <access_token>
 |--------------------------->|
 |                            | 2) Verifiziere Token:
 |                            |    - Signatur gültig?
 |                            |    - Claims: exp, iss, aud, scope
 |                            | 3) Zugriff erlauben oder verweigern
 |<---------------------------| 4) 200 OK (Daten) / 401–403 Fehler
 |                            |

Der Client nutzt jetzt das erhaltene Access Token, um eine geschützte API-Ressource anzufragen. Der Resource Server (also die API) prüft das Token selbstständig: Signatur, Ablaufzeit und Claims müssen stimmen. Ist alles gültig, werden die angeforderten Daten zurückgegeben – andernfalls folgt eine 401/403-Antwort.

💡 Merke
Der Auth-Server authentifiziert, die API autorisiert – auf Basis eines überprüfbaren Tokens.

5. Machine-to-Machine: Client Credentials Flow

Teilnehmer: Service A (Client) | Authorization Server | Service B (API)

Service A                 Auth-Server                 Service B
 |                            |                           |
 | 1) POST /token             |                           |
 |    grant_type=             |                           |
 |        client_credentials  |                           |
 |    client_id=...           |                           |
 |    client_secret=...       |                           |
 |--------------------------->|                           |
 |                            | 2) Prüfe Client-ID        |
 |                            |    & Secret               |
 |                            |    Signiere Access Token  |
 |<---------------------------| access_token              |
 |                                                        |
 | 3) GET /resource                                       |
 |    Authorization: Bearer <access_token>                |
 |------------------------------------------------------->|
 |                                                        | 4) Verifiziere
 |                                                        |    Token
 |                                                        |    (JWKS, Claims)
 |<-------------------------------------------------------| 200 OK (Daten)
 |                                                        |

Bei diesem Flow ist kein Nutzer beteiligt – ein technischer Client (z. B. ein Service oder Cronjob) authentifiziert sich direkt beim Authorization Server mit seiner client_id und seinem client_secret. Er erhält ein Access Token, mit dem er auf eine andere API zugreifen kann. So werden Service-zu-Service-Kommunikation und interne API-Aufrufe sicher geregelt – ganz ohne Benutzerkontext.

💡 Merke
Der Client Credentials Flow dient der Authentifizierung technischer Dienste. Er liefert nur ein Access Token – keine Benutzeridentität.

6. Refresh Tokens & Rotation

Teilnehmer: Client | Authorization Server

Client                     Auth-Server
 |                             |
 | 1) POST /token              |
 |    grant_type=refresh_token |
 |    refresh_token=rt-xyz     |
 |---------------------------->|
 |                             | 2) Prüfe Refresh Token:
 |                             |    - gültig?
 |                             |    - noch nicht verwendet?
 |                             | 3) Rotiere Token:
 |                             |    - alten ungültig machen
 |                             |    - neuen Access & Refresh Token ausstellen
 |<----------------------------| 4) Neue Tokens
 |                             |

Wenn das Access Token abläuft, nutzt der Client sein Refresh Token, um neue Tokens zu erhalten – ohne dass sich der Nutzer erneut anmelden muss. Der Authorization Server prüft die Gültigkeit des Refresh Tokens, stellt neue Tokens aus und macht das alte Refresh Token sofort ungültig. Dieses Verfahren nennt man Token Rotation und verhindert, dass ein abgefangenes Token mehrfach verwendet werden kann.

💡 Merke
Rotation verhindert Token-Reuse: Jeder Refresh Token ist Einweg und wird nach Gebrauch ersetzt.

7. Tokenprüfung und JWKS (API-Sicht)

Teilnehmer: API | OIDC Discovery | JWKS Endpoint

API                      Discovery                 JWKS
 |                           |                       |
 | 1) GET /.well-known/      |                       |
 |     openid-configuration  |                       |
 |-------------------------->|                       |
 |                           | 2) Antwort mit        |
 |                           |     jwks_uri          |
 |<--------------------------|                       |
 | 3) GET /jwks.json (vom jwks_uri)                  |
 |-------------------------------------------------->|
 | 4) Antwort mit Keys [{ kid, ... }]                |
 |<--------------------------------------------------|
 |
 | Bei Request mit Bearer-Token:
 |  - Header.kid → passenden Schlüssel aus JWKS wählen
 |  - Signatur prüfen (z. B. Ed25519/RSA)
 |  - Claims prüfen: exp, nbf, iss, aud, scope
 |
 |→ 200 OK (erlaubt) oder 401/403 (verweigert)

Die Discovery-Abfrage liefert der API alle wichtigen Metadaten des Authorization Servers – darunter die URL des JWKS-Endpunkts, wo die öffentlichen Schlüssel hinterlegt sind. Anhand dieser Schlüssel kann die API später jedes JWT-Token prüfen, ohne sich direkt beim Authorization Server rückzuversichern.

💡 Merke
Die API nutzt zuerst den Discovery-Endpoint, um den JWKS-Endpoint zu finden, und validiert dann Tokens selbstständig über die öffentlichen Schlüssel. APIs cachen die JWKS-Schlüssel typischerweise einige Minuten, um Requests zu sparen.

8. Zusammenfassung: Bausteine moderner Authentifizierung

Prinzip Bedeutung
Statelessness Keine Sitzungen, nur überprüfbare Tokens
Kurzlebigkeit Tokens laufen schnell ab – Refresh statt Dauerlogin
Standardisierung OAuth 2.0 / OIDC statt Eigenbau
PKCE Schutz für Public Clients
JWKS Öffentliche Schlüssel für Signaturprüfung
Rotation Tokens und Keys regelmäßig erneuern

Fazit

Moderne Authentifizierung ist kein Feature, sondern Architektur. Wer heute APIs entwickelt, sollte verstehen, dass Sicherheit und Vertrauen nicht mehr am Server kleben, sondern im Token stecken – überprüfbar, standardisiert, unabhängig vom Systemkontext.

OAuth 2.0 und OpenID Connect bieten dafür den Rahmen: Sicher, interoperabel, transparent.

✅ Merke
Verstehe zuerst die Rollen und Flows – der Code ist danach nur noch Handwerk.
📝 Weiterführende Quellen

Offizielle Spezifikationen und Standards: