OAuth 2.0 und OpenID Connect: Grundlagen für Entwickler
OWASP
0x00
Security · OAuth 2.0 · OpenID Connect · Web-Standards
OAuth 2.0 und OpenID Connect: Grundlagen für Entwickler
Delegation, PKCE und Identitätsprüfung praxisnah erklärt

OAuth 2.0 regelt Autorisierung, OpenID Connect ergänzt die fehlende Identitätsschicht, und wer beides verwechselt, baut unsichere Logins. Dieser Artikel erklärt den Authorization Code Flow mit PKCE als aktuellen Standard, zeigt typische Implementierungsfehler bei Redirect URIs, State und Nonce, und beschreibt, wie sich ein Social Login sicher in einen Magento Shop integrieren lässt.

17 Min. Lesezeit Authorization Code Flow · PKCE OpenID Connect · Social Login

1. Delegation statt Authentifizierung: OAuth 2.0 richtig einordnen

OAuth 2.0 ist ein Autorisierungsprotokoll: Es erlaubt einem Client, im Namen eines Resource Owners begrenzten Zugriff auf eine geschützte Ressource zu erhalten, ohne dass der Client jemals das Passwort des Nutzers sieht. Der Kern ist Delegation, nicht Identitätsnachweis. Ein Access Token beweist lediglich, dass ein Autorisierungsserver einem Client bestimmte Rechte eingeräumt hat, für einen begrenzten Zeitraum und mit begrenztem Scope.

Der häufigste konzeptionelle Fehler in der Praxis: Entwickler behandeln den erfolgreichen Erhalt eines Access Tokens als Login und leiten daraus die Identität des Nutzers ab. OAuth trifft dazu keine verlässliche Aussage, denn der Token-Endpunkt liefert weder eine signierte Nutzeridentität noch einen definierten Weg, sie abzufragen. Genau diese Lücke schließt OpenID Connect, das in Abschnitt 4 im Detail behandelt wird.

2. Rollen im OAuth-Modell: Client, Resource Owner, Authorization Server

Das OAuth-Modell definiert vier Rollen mit klar getrennten Verantwortlichkeiten. Der Resource Owner ist üblicherweise der Endnutzer, der Zugriff gewährt. Der Client ist die Anwendung, die im Namen des Nutzers auf Ressourcen zugreifen möchte, etwa ein Magento-Storefront. Der Authorization Server authentifiziert den Resource Owner und stellt nach dessen Zustimmung Access Tokens aus. Der Resource Server hält die geschützten Daten vor und akzeptiert gültige Access Tokens als Zugriffsnachweis.

Diese Trennung ermöglicht es, dass ein Client niemals Anmeldedaten des Nutzers verarbeitet oder speichert, was Angriffsfläche und Haftungsrisiko drastisch reduziert. In der Praxis übernehmen große Identity-Provider wie Google oder Microsoft sowohl Authorization- als auch Resource-Server-Rolle für ihre eigenen APIs, während ein Magento-Shop ausschließlich als Client agiert und niemals Passwörter des Google-Kontos zu Gesicht bekommt.

3. Der Authorization Code Flow mit PKCE

Der Authorization Code Flow ist der einzige OAuth-Flow, der für Web-Anwendungen mit vertraulichem oder öffentlichem Client heute empfohlen wird. Der Nutzer wird zum Autorisierungsserver umgeleitet, authentifiziert sich dort, und der Client erhält über den Redirect nur einen kurzlebigen Autorisierungscode, keinen Token direkt im Browser. Erst der anschließende, serverseitige Tausch dieses Codes gegen Access- und Refresh-Token verlässt den Browser-Kontext und ist damit vor Abgreifen durch Malware oder bösartige Browser-Erweiterungen geschützt.

PKCE (Proof Key for Code Exchange, RFC 7636) schließt eine zusätzliche Lücke: Ohne PKCE könnte ein Angreifer, der den Autorisierungscode abfängt, ihn selbst gegen Tokens eintauschen. Der Client erzeugt vor dem Redirect einen zufälligen code_verifier und leitet daraus per SHA-256 einen code_challenge ab, der mit der Autorisierungsanfrage übertragen wird. Beim Token-Tausch muss der ursprüngliche code_verifier mitgeschickt werden, den nur der legitime Client kennt. Seit 2025 empfiehlt die IETF PKCE für alle Client-Typen, nicht nur für Mobile- und Single-Page-Apps.


<?php
declare(strict_types=1);

// Step 1: Generate PKCE code_verifier and code_challenge (RFC 7636)
function generatePkcePair(): array
{
    $verifier = rtrim(strtr(base64_encode(random_bytes(64)), '+/', '-_'), '=');
    $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');

    return ['verifier' => $verifier, 'challenge' => $challenge];
}

$pkce = generatePkcePair();
$state = bin2hex(random_bytes(16));

// Store verifier and state server-side (session), never in the URL
$session->setData('oauth_code_verifier', $pkce['verifier']);
$session->setData('oauth_state', $state);

$authorizeUrl = 'https://accounts.example.com/authorize?' . http_build_query([
    'response_type'        => 'code',
    'client_id'             => $clientId,
    'redirect_uri'          => 'https://mironsoft.de/customer/account/oauthCallback',
    'scope'                 => 'openid profile email',
    'state'                 => $state,
    'code_challenge'        => $pkce['challenge'],
    'code_challenge_method' => 'S256',
]);

// Step 2: Callback exchanges the authorization code for tokens
function exchangeCodeForTokens(string $code, string $verifier, string $redirectUri, string $clientId, string $clientSecret): array
{
    $response = (new \GuzzleHttp\Client())->post('https://accounts.example.com/token', [
        'form_params' => [
            'grant_type'    => 'authorization_code',
            'code'          => $code,
            'redirect_uri'  => $redirectUri,
            'client_id'     => $clientId,
            'client_secret' => $clientSecret,
            'code_verifier' => $verifier, // proves this client started the flow
        ],
    ]);

    return json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
}

4. OpenID Connect: Die Identitätsschicht über OAuth

OpenID Connect (OIDC) baut als dünne Identitätsschicht direkt auf OAuth 2.0 auf und liefert genau das, was OAuth bewusst offen lässt: einen standardisierten, verifizierbaren Identitätsnachweis. Zusätzlich zum Access Token stellt der Autorisierungsserver bei OIDC ein ID Token aus, ein signiertes JWT mit Claims wie sub (eindeutige Nutzer-ID), iss, aud, exp und optional email oder name. Der Client verifiziert Signatur und Claims kryptographisch, statt dem Autorisierungsserver blind zu vertrauen.

Ergänzend liefert der UserInfo-Endpunkt weitere Profildaten, wenn der Scope openid profile email angefragt wurde und ein gültiges Access Token vorliegt. Der wichtige Unterschied: Das ID Token ist für den Client selbst bestimmt und beweist die Anmeldung zu einem festen Zeitpunkt, während der Access Token für den Resource Server bestimmt ist und laufenden API-Zugriff autorisiert. Beide Tokens getrennt zu behandeln verhindert eine häufige Verwechslung in Implementierungen.


{
  "id_token_claims": {
    "iss": "https://accounts.example.com",
    "sub": "110169484474386276334",
    "aud": "594832-abc123.apps.example.com",
    "exp": 1752312345,
    "iat": 1752308745,
    "nonce": "f3a1c9e8b2d4",
    "email": "kunde@example.com",
    "email_verified": true,
    "name": "Max Mustermann"
  },
  "userinfo_endpoint_response": {
    "sub": "110169484474386276334",
    "given_name": "Max",
    "family_name": "Mustermann",
    "email": "kunde@example.com",
    "email_verified": true,
    "picture": "https://accounts.example.com/avatar/110169484474386276334"
  }
}

5. Redirect-URI-Validierung, State und Nonce

Die redirect_uri bestimmt, wohin der Autorisierungsserver Code oder Token nach der Anmeldung schickt, und ist damit ein primäres Angriffsziel. Sicher ist ausschließlich ein exakter String-Vergleich gegen eine beim Autorisierungsserver hinterlegte Allow-List, niemals ein Präfix- oder Wildcard-Abgleich. Offene Redirect-Validierung erlaubt es Angreifern, Autorisierungscodes über eine manipulierte, aber ähnlich aussehende URI an sich selbst umzuleiten, ein Klassiker unter den OAuth-Schwachstellen.

Der state-Parameter schützt vor Cross-Site-Request-Forgery: Der Client erzeugt vor dem Redirect einen kryptographisch zufälligen, einmaligen Wert, speichert ihn serverseitig in der Session und vergleicht ihn beim Callback mit dem zurückgegebenen Wert über einen zeitkonstanten Vergleich. Der nonce-Parameter erfüllt bei OIDC eine ähnliche Funktion auf Token-Ebene: Er wird als Claim im ID Token zurückgegeben und verhindert Replay-Angriffe mit einem zuvor abgefangenen, gültigen Token.


<?php
declare(strict_types=1);

// Allow-list of exact, pre-registered redirect URIs -- never pattern match
final class RedirectUriValidator
{
    private const ALLOWED_URIS = [
        'https://mironsoft.de/customer/account/oauthCallback',
    ];

    public function assertAllowed(string $redirectUri): void
    {
        if (!in_array($redirectUri, self::ALLOWED_URIS, true)) {
            throw new \RuntimeException('Redirect URI is not on the allow-list');
        }
    }
}

// Validate the state parameter on callback to prevent CSRF
function validateState(SessionManagerInterface $session, string $receivedState): void
{
    $expectedState = $session->getData('oauth_state');
    $session->unsetData('oauth_state'); // one-time use

    if ($expectedState === null || !hash_equals((string) $expectedState, $receivedState)) {
        throw new \RuntimeException('State mismatch, possible CSRF attempt');
    }
}

// Validate the nonce claim inside the decoded ID token to prevent replay
function validateNonce(SessionManagerInterface $session, array $idTokenClaims): void
{
    $expectedNonce = $session->getData('oauth_nonce');
    $session->unsetData('oauth_nonce');

    if (!hash_equals((string) $expectedNonce, (string) ($idTokenClaims['nonce'] ?? ''))) {
        throw new \RuntimeException('Nonce mismatch, possible token replay');
    }
}

6. Der Implicit Flow und weitere Implementierungsfehler

Der Implicit Flow lieferte Access- und ID-Token direkt im URL-Fragment an den Browser zurück, ganz ohne serverseitigen Tausch. Das machte ihn für Single-Page-Apps ohne Backend attraktiv, aber auch strukturell unsicher: Tokens landen in Browser-History, Server-Logs und Referrer-Headern, und es gibt keine Client-Authentifizierung beim Token-Erhalt. Die OAuth Security Best Current Practice (RFC 9700) rät seit 2025 explizit von diesem Flow ab, zugunsten von Authorization Code Flow mit PKCE, auch für reine Frontend-Anwendungen.

Weitere verbreitete Fehler betreffen die Token-Validierung selbst: JWTs ohne Signaturprüfung akzeptieren, den alg-Header des Tokens ungeprüft übernehmen, was einen Downgrade auf alg=none erlaubt, oder die aud-Claim nicht gegen die eigene Client-ID zu prüfen. Jeder dieser Fehler erlaubt es einem Angreifer, ein für einen anderen Client ausgestelltes, aber technisch gültiges Token gegen die eigene Anwendung wiederzuverwenden.

7. Social Login in einen Magento-Shop integrieren

Ein Google- oder Microsoft-Login in einem Magento-Storefront folgt demselben Muster wie jede andere OAuth/OIDC-Integration: Ein eigener Controller leitet zum Autorisierungsserver um, ein zweiter Controller verarbeitet den Callback, tauscht den Code serverseitig gegen Tokens und ordnet die verifizierte E-Mail-Adresse aus dem ID Token einem bestehenden oder neu anzulegenden Magento-Kundenkonto zu. Die Kundenkonto-Verknüpfung sollte über die stabile sub-Claim erfolgen, nicht über die E-Mail-Adresse, da diese beim Identity-Provider geändert werden kann.

Layout-seitig lässt sich der Login-Button sauber über einen eigenen Block in customer_account_login.xml einhängen, ohne den bestehenden Login-Container zu verändern. Wichtig für Hyvä-Themes: Der OAuth-Redirect selbst findet außerhalb von Alpine.js statt, ein einfacher Server-Redirect genügt, zusätzliches JavaScript ist für den Flow selbst nicht nötig. Die Session-Anlage nach erfolgreichem Callback nutzt weiterhin Magentos reguläre CustomerSession, sodass der restliche Checkout- und Kontobereich unverändert funktioniert.


<?xml version="1.0"?>
<!-- view/frontend/layout/customer_account_login.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="customer.login.additional.info">
            <block class="Mironsoft\SocialLogin\Block\GoogleLoginButton"
                   name="social.login.google"
                   template="Mironsoft_SocialLogin::google-login-button.phtml"
                   after="-">
                <arguments>
                    <argument name="authorize_url" xsi:type="string">
                        {{/customer/account/oauthRedirect}}
                    </argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>

8. Token-Speicherung und Refresh-Token-Handling

Access- und Refresh-Token gehören serverseitig gespeichert, verschlüsselt at rest, und niemals in localStorage oder sessionStorage des Browsers, da beide für jedes im Kontext der Seite laufende JavaScript lesbar sind und damit ein direktes XSS-Ziel darstellen. In Magento bietet sich die EncryptorInterface für die Verschlüsselung an, kombiniert mit einer eigenen Tabelle statt der Wiederverwendung ungeschützter Kernstrukturen. Der Access Token selbst sollte, wenn er überhaupt an den Browser muss, ausschließlich in einem httpOnly- und secure-Cookie liegen.

Refresh-Token-Rotation ist Pflicht: Bei jedem Refresh wird ein neuer Refresh-Token ausgestellt und der alte serverseitig invalidiert. Taucht ein bereits verwendeter, alter Refresh-Token erneut auf, deutet das auf einen gestohlenen Token hin, und der Autorisierungsserver sollte die gesamte Token-Familie sperren. Ein kurzes Access-Token-Ablaufdatum von wenigen Minuten in Kombination mit rotierenden Refresh-Token begrenzt den Schaden eines abgegriffenen Tokens erheblich, ohne den Nutzer ständig neu anmelden zu müssen.


<?php
declare(strict_types=1);

// Store tokens encrypted server-side, never in localStorage or a readable cookie
final class OAuthTokenStorage
{
    public function __construct(
        private readonly EncryptorInterface $encryptor,
        private readonly CustomerTokenRepositoryInterface $tokenRepository,
    ) {
    }

    /**
     * Persist access and refresh tokens encrypted at rest, tied to the customer ID.
     */
    public function store(int $customerId, string $accessToken, string $refreshToken, int $expiresIn): void
    {
        $this->tokenRepository->save(
            $customerId,
            $this->encryptor->encrypt($accessToken),
            $this->encryptor->encrypt($refreshToken),
            time() + $expiresIn
        );
    }

    /**
     * Refresh the access token shortly before expiry using the stored refresh token.
     */
    public function refreshIfNeeded(int $customerId, callable $refreshCall): ?string
    {
        $record = $this->tokenRepository->getByCustomerId($customerId);
        if ($record === null) {
            return null;
        }

        if ($record->getExpiresAt() - 60 > time()) {
            return $this->encryptor->decrypt($record->getAccessTokenCipher());
        }

        $refreshToken = $this->encryptor->decrypt($record->getRefreshTokenCipher());
        $tokens = $refreshCall($refreshToken); // rotates refresh token server-side

        $this->store($customerId, $tokens['access_token'], $tokens['refresh_token'], $tokens['expires_in']);

        return $tokens['access_token'];
    }
}

9. OAuth-Flows und Sicherheitsmechanismen im Vergleich

Die folgende Übersicht fasst die wichtigsten Entscheidungen zusammen, bei denen unsichere und sichere Implementierungen in der Praxis am häufigsten auseinanderlaufen.

Aspekt Unsicher / Veraltet Empfohlen Warum
Flow-Typ Implicit Flow (Token im URL-Fragment) Authorization Code Flow + PKCE Kein Token-Leak über Browser-History/Logs
Redirect-URI-Prüfung Präfix- oder Wildcard-Abgleich Exakter Allow-List-Vergleich Verhindert Code-Diebstahl über ähnliche URIs
State-Parameter Kein oder statischer state Zufälliger, einmaliger state pro Anfrage Schützt zuverlässig vor CSRF
PKCE-Methode code_challenge_method=plain code_challenge_method=S256 Challenge nicht aus abgefangenem Wert rekonstruierbar
Token-Speicherung localStorage im Browser httpOnly-Cookie / verschlüsselt serverseitig Kein direkter XSS-Zugriff auf Tokens

Alle fünf Zeilen der Tabelle haben eines gemeinsam: Die sichere Variante erfordert selten mehr Code, meist nur eine bewusste Konfigurationsentscheidung beim Aufsetzen der Integration. Wer diese fünf Punkte bei jeder neuen OAuth- oder OIDC-Integration standardmäßig korrekt umsetzt, schließt die überwiegende Mehrheit der in der Praxis beobachteten Schwachstellen von vornherein aus.

Mironsoft

Sichere Authentifizierung, OAuth-Integrationen und Identity-Architektur für Magento-Shops

OAuth- und OIDC-Integration sicher umsetzen?

Wir prüfen bestehende OAuth- und Social-Login-Integrationen auf PKCE, Redirect-URI-Validierung und Token-Handling, und implementieren neue Identity-Provider-Anbindungen nach aktuellem Sicherheitsstandard, inklusive Magento-spezifischer Kundenkonto-Zuordnung.

OAuth/OIDC-Audit

Prüfung von PKCE, State/Nonce-Handling und Redirect-URI-Konfiguration gegen RFC 9700

Social-Login-Integration

Google-, Microsoft- und weitere Identity-Provider sicher in Magento und Hyvä einbinden

Token-Architektur

Verschlüsselte Speicherung, Refresh-Token-Rotation und sichere Session-Anbindung

10. Zusammenfassung

OAuth 2.0 und OpenID Connect lösen zwei unterschiedliche, aber eng verwandte Probleme: OAuth delegiert begrenzten Ressourcenzugriff, OIDC beweist Identität über ein signiertes ID Token. Der Authorization Code Flow mit PKCE ist für praktisch jeden Client-Typ der richtige Flow, der Implicit Flow gilt seit RFC 9700 als veraltet. Redirect-URI-Allow-Listen, state und nonce verhindern die häufigsten OAuth-spezifischen Angriffe: Code-Abgreifen, CSRF und Token-Replay.

Für die Magento-Integration eines Social Logins gilt dasselbe Prinzip wie für jede andere sicherheitsrelevante Funktion: Der Token-Tausch läuft ausschließlich serverseitig, die Kundenzuordnung erfolgt über die stabile sub-Claim, und Tokens werden verschlüsselt und mit kurzer Lebensdauer gespeichert. Wer diese Grundlagen einmal sauber implementiert, kann sie als wiederverwendbares Muster für jeden weiteren Identity-Provider nutzen, ohne die Sicherheitsarchitektur jedes Mal neu zu durchdenken.

OAuth 2.0 und OpenID Connect: Das Wichtigste auf einen Blick

Authorization Code + PKCE

Der empfohlene Flow für alle Client-Typen. Der Code-Tausch gegen Tokens läuft serverseitig, PKCE bindet den Code an den anfragenden Client.

OIDC ID Token

Signiertes JWT mit sub, iss, aud und exp. Beweist Identität, ist von der Autorisierung im Access Token zu trennen.

State & Nonce

state schützt vor CSRF, nonce schützt vor Token-Replay. Beide zufällig, einmalig und serverseitig geprüft.

Token-Speicherung

Verschlüsselt serverseitig, nie in localStorage. Refresh-Token-Rotation begrenzt den Schaden gestohlener Token.

11. FAQ: OAuth 2.0 und OpenID Connect

1Was ist der Unterschied zwischen OAuth 2.0 und OpenID Connect?
OAuth 2.0 delegiert begrenzten Ressourcenzugriff. OpenID Connect fügt eine Identitätsschicht mit signiertem ID Token hinzu und beweist damit, wer sich tatsächlich angemeldet hat.
2Warum ist der Implicit Flow nicht mehr empfohlen?
Tokens landen ungeschützt im URL-Fragment, in History und Logs, ohne Client-Authentifizierung. RFC 9700 rät seit 2025 explizit davon ab.
3Was macht PKCE, und wird es nur für Mobile-Apps gebraucht?
PKCE bindet den Code über code_verifier/code_challenge an den anfragenden Client. Seit RFC 9700 für alle Client-Typen empfohlen, nicht nur Mobile/SPA.
4Wofür genau ist der state-Parameter da?
Schützt vor CSRF: zufälliger, einmaliger Wert vor dem Redirect erzeugt und beim Callback gegen den zurückgegebenen Wert geprüft.
5Wofür genau ist der nonce-Parameter da?
Schützt bei OIDC vor Replay abgefangener ID Tokens, indem er als Claim zurückgegeben und gegen den gesendeten Wert geprüft wird.
6Wie validiere ich eine redirect_uri sicher?
Exakter String-Vergleich gegen eine serverseitige Allow-List. Kein Präfix- oder Wildcard-Abgleich, das ermöglicht Code-Diebstahl.
7Wo sollte ich Access- und Refresh-Token speichern?
Serverseitig verschlüsselt. Wenn nötig im Browser, ausschließlich httpOnly/secure-Cookie, niemals localStorage.
8Was ist Refresh-Token-Rotation und warum ist sie wichtig?
Jeder Refresh stellt einen neuen Token aus und invalidiert den alten. Wiederverwendung eines alten Tokens signalisiert Diebstahl.
9Wie ordne ich einen Social-Login-Nutzer einem Magento-Kundenkonto zu?
Über die stabile sub-Claim aus dem ID Token, nicht über die E-Mail-Adresse, die sich beim Provider ändern kann.
10Reicht ein gültiges Access Token als Identitätsnachweis?
Nein, ein Access Token beweist nur Ressourcenzugriff. Für Identität ist das signierte ID Token von OpenID Connect nötig.