Entwickler-Leitfaden · OpenID Connect

CodeB-Anmeldung in Ihre App einbauen.

Diese Seite führt Sie durch alles, was Sie brauchen, um den OpenID-Connect-Identity-Provider von CodeB von einer Drittanbieter-Website aus zu konsumieren — PKCE-Setup, Auth-Code-Tausch, JWT-Validierung, Refresh-Handhabung und Logout. Kopierfertige Beispiele in Vanilla-JavaScript und Node, plus ein curl-Durchlauf für Backend-Bastler.

Der gesamte IdP ist standardkonform zu OIDC Core 1.0. Wenn Sie bereits eine Library wie oidc-client-ts, openid-client (Node), Authlib (Python) oder die eingebaute OpenIdConnectAuthentication aus .NET verwenden — richten Sie sie auf die Discovery-URL und konfigurieren Sie Ihre Client-ID. Die meisten Schritte unten kollabieren dann zu einem Config-Block.

Inhalt
  1. Voraussetzungen
  2. Anwendung registrieren
  3. Endpoints entdecken
  4. Der Anmelde-Flow
  5. Vollständiger Code — Vanilla-JS
  6. Vollständiger Code — Node / Express
  7. curl-Durchlauf
  8. ID-Token validieren
  9. Refresh + Logout
  10. Token-Widerruf
  11. Authentifizierungs-Faktor (amr / acr)
  12. Key-Rotation-Runbook
  13. Fehlersuche
  14. Schnellreferenz

1. Voraussetzungen

2. Anwendung registrieren

CodeB verlangt, dass jede Relying Party vorregistriert ist — die Standard-OIDC-Haltung (RFC 6749 §3.1.2.1). Die Vorregistrierung verriegelt, welche redirect_uri-Werte der IdP akzeptiert, und genau das verhindert, dass ein Angreifer die Auth-Codes Ihrer Nutzer abfängt.

Der Betreiber fügt Ihren Client in App_Data/<tenant>/oidc/clients.json ein. Die Datei ist pro Mandant und wird bei jeder Änderung heiß nachgeladen — kein IIS-Recycle nötig. Beispiel:

{ "clients": [ { "client_id": "nextcloud", "redirect_uris": ["https://cloud.example.com/index.php/apps/user_oidc/code"], "description": "Acme Nextcloud (user_oidc-App)" }, { "client_id": "acme-portal", "redirect_uris": [ "https://app.acme.com/oauth/callback", "https://staging.acme.com/oauth/callback" ], "description": "Acme Support-Portal (prod + staging)" } ] }

Wenn Sie den Host nicht selbst betreiben, schreiben Sie an info@codeb.io mit Ihrem Anwendungsnamen, der exakten Callback-URL (HTTPS, außer loopback) und ob Sie Test- vs. Produktions-Redirects benötigen. Alle CodeB-Clients sind öffentliche PKCE-only-Clients — kein Client-Secret zu verwalten.

Der eingebaute codeb-admin-Client ist immer verfügbar und an https://<tenant>/oidc-callback.html gebunden — das ist, was die Admin-UI antreibt. Sie können ihn nicht in clients.json überschreiben oder entfernen.

Für Experimente gegen Ihren eigenen Mandanten ist der eingebaute Client codeb-admin mit der Redirect-URI https://<ihr-host>/oidc-callback.html vorregistriert — das nutzen die CodeB-Admin-Seiten. Verwenden Sie ihn nicht für Drittanbieter-Produktions-Integrationen; fordern Sie Ihre eigene Client-ID an.

3. Endpoints entdecken

Der IdP veröffentlicht alles Nötige unter der Standard-Well-known-URL:

curl https://phone.codeb.io/.well-known/openid-configuration

Antwort (gekürzt):

{ "issuer": "https://phone.codeb.io", "authorization_endpoint": "https://phone.codeb.io/oidc.ashx?action=authorize", "token_endpoint": "https://phone.codeb.io/oidc.ashx?action=token", "userinfo_endpoint": "https://phone.codeb.io/oidc.ashx?action=userinfo", "jwks_uri": "https://phone.codeb.io/.well-known/jwks.json", "end_session_endpoint": "https://phone.codeb.io/oidc.ashx?action=end_session", "response_types_supported": ["code"], "id_token_signing_alg_values_supported": ["RS256"], "code_challenge_methods_supported": ["S256"], "grant_types_supported": ["authorization_code", "refresh_token"], "scopes_supported": ["openid", "profile", "email", "groups", "phone", "address"], "token_endpoint_auth_methods_supported": ["none"] }

Die meisten Libraries holen sich das einmal beim Start und cachen es. Holen Sie es neu, wenn Sie bei der Token-Validierung kid-Mismatches zu sehen beginnen (Betreiber hat den Key rotiert).

4. Der Anmelde-Flow

Standard-Authorization-Code mit PKCE. Vier bewegliche Teile: Ihr Frontend, Ihr Backend, der Browser des Nutzers und der CodeB-IdP.

1

PKCE-Paar im Frontend erzeugen

Zufälliger 32-Byte-code_verifier, base64url-codiert. code_challenge ist der SHA-256 des Verifiers, ebenfalls base64url. Verifier in sessionStorage ablegen, damit die Callback-Seite ihn wiederfindet.

2

Nutzer zu /authorize umleiten

Mit Ihrer client_id, Ihrer redirect_uri, angefragten Scopes (immer openid einschließen), einem zufälligen state-Token, dem code_challenge und code_challenge_method=S256.

3

CodeB fordert den Nutzer zur Anmeldung auf

Der Nutzer landet auf /login.html, tippt seinen CodeB-Benutzernamen + Passwort (clientseitig zu HA1 gehasht; das Klartext-Passwort verlässt nie den Browser). Bei Erfolg leitet CodeB zurück auf Ihre redirect_uri mit ?code=…&state=….

4

State validieren, Code tauschen

Frontend prüft, dass state mit dem gesendeten Wert übereinstimmt. Dann postet entweder das Frontend (Public Client) oder das Backend (Confidential Client) den Code + PKCE-Verifier an /token und erhält access_token, id_token, refresh_token.

5

ID-Token verifizieren, Nutzer einloggen

JWT-Signatur gegen die JWKS des IdP validieren, iss, aud, exp, nonce prüfen. Der sub-Claim ist die Nutzer-ID; role ist admin/user/siponly/guest.

5. Vollständiger Code — Vanilla-JS (Public SPA Client)

Für eine Single-Page-App, die den gesamten Flow clientseitig erledigt. Zwei Dateien: ein Launcher und ein Callback-Handler.

Launcher (der Button, der die Anmeldung startet)

// Auf der Seite mit Ihrem "Mit CodeB anmelden"-Button: async function signInWithCodeB() { // 1. PKCE-Verifier + Challenge erzeugen const verifier = b64url(crypto.getRandomValues(new Uint8Array(48))); const challenge = b64url(new Uint8Array( await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)))); const state = b64url(crypto.getRandomValues(new Uint8Array(16))); // 2. Beide ablegen, damit die Callback-Seite sie wiederfindet sessionStorage.setItem('oidc.verifier', verifier); sessionStorage.setItem('oidc.state', state); // 3. Redirect zu /authorize const params = new URLSearchParams({ response_type: 'code', client_id: 'YOUR_CLIENT_ID', redirect_uri: 'https://your-app.com/oidc/callback', scope: 'openid profile email', state, code_challenge: challenge, code_challenge_method: 'S256' }); location.assign('https://phone.codeb.io/oidc.ashx?action=authorize&' + params); } function b64url(bytes) { return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); }

Callback-Handler (die Seite, auf die CodeB zurückleitet)

// Unter https://your-app.com/oidc/callback (async () => { const url = new URL(location.href); const code = url.searchParams.get('code'); const state = url.searchParams.get('state'); const saved = sessionStorage.getItem('oidc.state'); const verif = sessionStorage.getItem('oidc.verifier'); // KRITISCH: state erzwingen. Fehlt ODER passt nicht -> abbrechen. if (!code || !saved || state !== saved || !verif) { document.body.textContent = 'Anmeldung fehlgeschlagen (bad state).'; return; } sessionStorage.removeItem('oidc.state'); sessionStorage.removeItem('oidc.verifier'); // Code gegen Tokens tauschen const body = new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: 'https://your-app.com/oidc/callback', client_id: 'YOUR_CLIENT_ID', code_verifier: verif }); const r = await fetch('https://phone.codeb.io/oidc.ashx?action=token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body }); const tok = await r.json(); if (!r.ok || !tok.access_token) { document.body.textContent = 'Token-Tausch fehlgeschlagen: ' + (tok.error_description || tok.error); return; } // Sie haben jetzt: // tok.access_token -- als Authorization: Bearer an Ihre API senden // tok.id_token -- JWT, das den Nutzer identifiziert (sub, role, Profil-Claims) // tok.refresh_token -- später gegen frisches access_token tauschen // tok.expires_in -- Sekunden bis access_token abläuft (typ. 3600) sessionStorage.setItem('app.access_token', tok.access_token); sessionStorage.setItem('app.refresh_token', tok.refresh_token); sessionStorage.setItem('app.expires_at', Math.floor(Date.now()/1000) + tok.expires_in); location.replace('/'); })();

Public Clients müssen PKCE verwenden. Ohne PKCE kann jeder, der die Redirect-URL abfängt, den Code gegen Tokens tauschen. Wir akzeptieren den authorization_code-Grant nicht ohne code_verifier.

6. Vollständiger Code — Node / Express (Confidential Client)

Für ein Backend, das den Token-Tausch serverseitig erledigt — das empfohlene Muster, wenn Sie Ihren eigenen Server kontrollieren. Nutzt die Library openid-client, die Discovery, PKCE, Token-Validierung und Refresh handhabt.

// npm install openid-client express express-session import express from 'express'; import session from 'express-session'; import { Issuer, generators } from 'openid-client'; const app = express(); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true })); const codeb = await Issuer.discover('https://phone.codeb.io'); const client = new codeb.Client({ client_id: process.env.CODEB_CLIENT_ID, client_secret: process.env.CODEB_CLIENT_SECRET, // nur für Confidential Clients redirect_uris: ['https://your-app.com/oidc/callback'], response_types: ['code'] }); app.get('/login', (req, res) => { const verifier = generators.codeVerifier(); const state = generators.state(); req.session.pkce = verifier; req.session.state = state; res.redirect(client.authorizationUrl({ scope: 'openid profile email', code_challenge: generators.codeChallenge(verifier), code_challenge_method: 'S256', state })); }); app.get('/oidc/callback', async (req, res) => { const params = client.callbackParams(req); const tokenSet = await client.callback( 'https://your-app.com/oidc/callback', params, { code_verifier: req.session.pkce, state: req.session.state } ); req.session.user = tokenSet.claims(); // sub, role, email, ... req.session.tokens = { access_token: tokenSet.access_token, refresh_token: tokenSet.refresh_token, expires_at: tokenSet.expires_at }; res.redirect('/'); }); app.get('/me', (req, res) => { if (!req.session.user) return res.status(401).end(); res.json(req.session.user); });

7. curl-Durchlauf

Nützlich, um den IdP aus einem Skript oder einer Postman-Collection heraus anzutesten. Ersetzen Sie YOUR_CLIENT_ID, YOUR_REDIRECT_URI und die PKCE-Werte durch Ihre eigenen.

Schritt 1 — die Authorize-URL im Browser öffnen

https://phone.codeb.io/oidc.ashx?action=authorize\ &response_type=code\ &client_id=YOUR_CLIENT_ID\ &redirect_uri=https%3A%2F%2Fyour-app.com%2Foidc%2Fcallback\ &scope=openid+profile+email\ &state=abc123\ &code_challenge=YOUR_S256_CHALLENGE\ &code_challenge_method=S256

Anmelden. Der Browser landet auf Ihrer Callback-URL mit ?code=<langer String>&state=abc123.

Schritt 2 — Code gegen Tokens tauschen

curl -X POST https://phone.codeb.io/oidc.ashx?action=token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=<Code aus Schritt 1>" \ -d "redirect_uri=https://your-app.com/oidc/callback" \ -d "client_id=YOUR_CLIENT_ID" \ -d "code_verifier=YOUR_PKCE_VERIFIER"

Antwort:

{ "access_token": "eyJhbGc...", "token_type": "Bearer", "expires_in": 3600, "id_token": "eyJhbGc...", "refresh_token": "7m...", "scope": "openid profile email" }

Schritt 3 — Nutzerprofil abrufen

curl https://phone.codeb.io/oidc.ashx?action=userinfo \ -H "Authorization: Bearer eyJhbGc..."

8. ID-Token validieren

Wenn Sie ID-Tokens serverseitig akzeptieren (zum Beispiel, um den Nutzer ohne Back-Channel-Call zu identifizieren), MÜSSEN Sie sie verifizieren. Validierung überspringen heißt: jeder kann Ihnen ein gefälschtes Token in die Hand drücken.

Jede OIDC/JOSE-Library erledigt das für Sie. Die nötigen Prüfungen:

  1. Signatur — gegen den öffentlichen Schlüssel aus jwks_uri verifizieren, passend zur kid im JWT-Header.
  2. iss — muss dem Issuer aus dem Discovery-Dokument entsprechen (https://phone.codeb.io).
  3. aud — muss Ihrer client_id entsprechen.
  4. exp — muss in der Zukunft liegen (ein paar Sekunden Clock-Skew zulassen).
  5. iat — Sanity-Check: nicht weit in der Zukunft.
  6. nonce — falls Sie eine an /authorize gesendet haben, muss sie im Token zurückkommen.

Decodierte ID-Token-Payload:

{ "iss": "https://phone.codeb.io", "sub": "alice", "aud": "YOUR_CLIENT_ID", "exp": 1748467200, "iat": 1748463600, "auth_time": 1748463600, "name": "Alice Walker", "preferred_username": "alice", "email": "alice@example.com", "email_verified": true, "role": "admin", "groups": ["admin"] }

Der role-Claim ist die CodeB-spezifische Rollen-Zuweisung (admin, user, siponly oder guest). Der Standard-groups-Claim ist per Default ein einelementiges Array mit der Rolle — das erwarten die meisten RPs. Admins können pro Nutzer überschreiben via Groups-Feld im Nutzerprofil-Editor (register.html) — wenn gesetzt, ersetzen diese Gruppen das Rolle-als-Gruppe-Default, was der empfohlene Weg ist, RP-spezifische Mitgliedschaften (Nextcloud-Gruppen, App-Rollen, Abteilungen) ins Token zu speisen.

9. Refresh + Logout

Access-Token erneuern

Access-Tokens laufen 1 Stunde. Refresh-Tokens 4 Stunden und werden bei jeder Nutzung rotiert (entsprechend OAuth-2.1-Best-Practice): jeder Aufruf von /token mit grant_type=refresh_token liefert ein frisches refresh_token, das alte wird sofort invalidiert. Speichern Sie das neue, werfen Sie das alte weg.

curl -X POST https://phone.codeb.io/oidc.ashx?action=token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "refresh_token=YOUR_REFRESH_TOKEN" \ -d "client_id=YOUR_CLIENT_ID"

Wenn Sie jemals ein bereits eingelöstes refresh_token vorlegen, antwortet der IdP mit invalid_grant. Das ist das Sicherheits-Feature der Rotation: es zeigt Ihnen, dass das Token anderswo benutzt wurde (mögliche Exfiltration). Erzwingen Sie eine erneute Anmeldung.

Nutzer abmelden

Löschen Sie Ihre eigene Session, dann optional auf den End-Session-Endpoint des IdP umleiten:

https://phone.codeb.io/oidc.ashx?action=end_session\ &post_logout_redirect_uri=https%3A%2F%2Fyour-app.com%2Floggedout

Weil CodeB cookie-frei ist, ist der IdP-seitige Logout meist nur ein Redirect — es gibt kein IdP-Session-Cookie zu löschen. Die eigentliche Logout-Arbeit passiert bei Ihnen: Tokens wegwerfen.

10. Token-Widerruf (RFC 7009)

Sie müssen die Session eines Nutzers sofort IdP-seitig beenden (Admin-Aktion, Passwort-Änderung, Mitarbeiter-Abgang)? POSTen Sie das refresh_token an /oidc.ashx?action=revoke. Beim nächsten Refresh-Versuch des RP gibt es invalid_grant und der Nutzer muss sich erneut anmelden.

curl -X POST https://phone.codeb.io/oidc.ashx?action=revoke \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "token=<das_refresh_token>" \ -d "token_type_hint=refresh_token" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET"

Liefert immer 200 {}, unabhängig davon, ob das Token existierte — das ist RFC 7009 §2.2 mit Absicht, um Token-Enumeration zu verhindern. Confidential Clients müssen client_secret mitschicken; Public Clients dürfen ohne Auth widerrufen.

Access-Tokens sind zustandslose JWTs mit 1h-TTL, deshalb macht RFC 7009 §2.2 serverseitigen Widerruf optional — CodeB macht Access-Token-Widerruf zum No-Op (liefert trotzdem 200). Um eine Session schneller zu beenden, widerrufen Sie das refresh_token UND lassen Sie den RP sein In-Memory-access_token verwerfen.

11. Authentifizierungs-Faktor (amr / acr)

Jedes ausgestellte id_token und access_token trägt zwei Claims, die beschreiben, wie sich der Nutzer tatsächlich authentifiziert hat:

RPs, die Step-up-Auth brauchen (z. B. Smartcard-Login für sensitive Operation erzwingen), können auf acr verzweigen:

if (idToken.acr !== 'urn:codeb:acr:hwk-mfa') { // Re-Auth mit stärkerem Faktor erzwingen: // /authorize?acr_values=urn:codeb:acr:hwk-mfa&... redirectToAuthorizeWithAcrValues(); }

Die Claims überleben Refresh-Token-Rotation, also bleibt der Faktor für die gesamte Session-Lebensdauer korrekt. Sie werden auch durch die cookielose SSO-Assertion getragen: meldet sich der Nutzer innerhalb des 30-min-Fensters bei einem zweiten RP an, sieht der neue RP denselben Faktor.

12. Key-Rotation-Runbook (Betreiber)

Der IdP signiert jedes JWT mit einem pro-Mandant 2048-Bit-RSA-Key. Für Rotation ohne Downtime unterstützt CodeB ein Überlappungsfenster: zwei Keys können gleichzeitig aktiv sein. Tokens, die vor der Rotation signiert wurden, verifizieren weiter via den vorigen Key, solange er auf der Platte liegt; neue Tokens werden mit dem frisch erzeugten Key signiert.

  1. SSH / RDP auf den IIS-Host.
  2. Aktuellen Key beiseite schieben:
    cd D:\aloaha\phone\App_Data\phone.codeb.io\oidc move private-key.xml private-key-previous.xml
  3. Der nächste Request an /oidc.ashx auf diesem Mandanten erkennt die fehlende private-key.xml und erzeugt eine frische. Der vorige Key bleibt als verify-only-Fallback geladen.
  4. JWKS publiziert nun beide öffentlichen Schlüssel (den neuen zuerst):
    curl https://phone.codeb.io/.well-known/jwks.json | jq .keys

    RPs, die JWKS regelmäßig holen, picken beide automatisch auf. Vor der Rotation signierte Tokens verifizieren weiter; neue Tokens verwenden die neue kid.

  5. Warten, bis das Überlappungsfenster leerläuft. Sobald jedes unter dem alten Key ausgegebene refresh_token rotiert wurde (max. 4 h bei Default-TTL), darf der vorige Key gelöscht werden:
    del D:\aloaha\phone\App_Data\phone.codeb.io\oidc\private-key-previous.xml

    JWKS fällt beim nächsten Request auf einen einzigen Key zurück; etwaige Nachzügler mit Token unter altem Key scheitern bei der Verifikation und müssen sich neu anmelden.

Der IdP mtime-überwacht beide Dateien, sodass die Rotation heiß ist — kein IIS-Recycle, keine abgebrochenen WebSocket-Sessions, keine kaputten laufenden Anmeldungen. Die kid in jedem JWT-Header pinnt es zur Verifikationszeit an den richtigen Key.

13. Fehlersuche

SymptomWahrscheinliche Ursache
invalid_client bei /authorize Ihre client_id ist nicht für diesen Mandanten registriert, oder Ihre redirect_uri entspricht nicht byte-genau dem registrierten Wert (Case, Trailing-Slash, Query-String).
invalid_request-Redirect Fehlende code_challenge oder code_challenge_method != S256. PKCE ist Pflicht.
invalid_grant: PKCE verifier invalid Der gesendete code_verifier ergibt nicht den vorher gesendeten code_challenge-SHA-256. Meist ein Tippfehler oder versehentlich den Challenge statt des Verifiers gesendet.
invalid_grant: code invalid or expired Auth-Codes sind single-use und leben 60 Sekunden. Nicht cachen; sofort beim Callback tauschen.
invalid_grant: refresh_token invalid Refresh-Tokens sind single-use (Rotation). Sie haben eines vorgelegt, das schon eingelöst war — entweder von Ihnen bei einem früheren Aufruf oder von einem Angreifer, falls es geleakt ist. Re-Login erzwingen.
401 invalid_token bei /userinfo Access-Token abgelaufen (1h) oder der falsche Mandanten-Key hat es signiert. Refresh + retry.
429 rate_limited bei /login Pro-IP-Rate-Limit (10 Fehlversuche pro Minute). Retry-After-Header beachten.
State-Mismatch beim Callback Ihr Frontend hat den gespeicherten state verloren (z. B. Nutzer hat die Auth-URL in einem anderen Tab geöffnet). Flow neu starten.

14. Schnellreferenz

WasWert
Discovery/.well-known/openid-configuration
JWKS/.well-known/jwks.json
Authorize/oidc.ashx?action=authorize
Token/oidc.ashx?action=token
UserInfo/oidc.ashx?action=userinfo
End-Session/oidc.ashx?action=end_session
Signatur-Alg.RS256 (2048-Bit RSA, pro-Mandant-Key)
PKCEfür alle Flows verpflichtend, nur S256
Grant-Typenauthorization_code, refresh_token
Access-Token-TTL3600s (1h)
ID-Token-TTL3600s (1h)
Refresh-Token-TTL14400s (4h), bei jeder Nutzung rotiert
Auth-Code-TTL60s, single-use
Scopesopenid, profile, email, groups, phone, address
Revocation-Endpoint/oidc.ashx?action=revoke
amr-Wertepwd, hwk, mfa, swk, otp
acr-Werteurn:codeb:acr:pwd, urn:codeb:acr:hwk, urn:codeb:acr:hwk-mfa, urn:codeb:acr:mfa
Key-RotationÜberlappungsfenster: private-key.xml → private-key-previous.xml umbenennen; neuer Key wird automatisch erzeugt; beide in JWKS publiziert
Rollen (im role-Claim)admin, user, siponly, guest

Hilfe beim Einbau gefällig? Schreiben Sie an info@codeb.io · Zurück zur OIDC-Übersicht →

Siehe auch · Plattform-Integrations-Anleitungen