Anmelden mit CodeB - OIDC-Kochbuch (authorization_code + PKCE).
Acht Framework-Rezepte fuer die klassische OIDC-Anmeldung (authorization_code + PKCE, S256). Alle Snippets zielen auf das oeffentliche Discovery-Dokument unter /.well-known/openid-configuration. Wallet-basierte Ablaeufe finden Sie im Verifier-Kochbuch. Sitzungen der European Digital Identity Wallet landen im selben authorization_code-Austausch.
Umfang. Dokumentierte Rezepte, kein paketiertes Widget. Jedes Snippet arbeitet gegen einen echten CodeB-Mandanten mit einem in oidc-clients.html konfigurierten Client. PKCE (RFC 7636, S256) ist Pflicht.
Framework-Matrix
| Framework / Sprache | Bibliothek | Rezept | Snippet |
|---|---|---|---|
| Vanilla HTML/JS | no build; PKCE via subtle-crypto | Anzeigen | oidc-signin-vanilla.html |
| React | react-oidc-context (oidc-client-ts) | Anzeigen | oidc-signin-react.jsx |
| Angular | angular-oauth2-oidc | Anzeigen | oidc-signin-angular.ts |
| Vue 3 | custom composable using oidc-client-ts | Anzeigen | oidc-signin-vue.js |
| Node.js server-side | openid-client (Express) | Anzeigen | oidc-signin-nodejs.js |
| Python server-side | Authlib (Flask) | Anzeigen | oidc-signin-python.py |
| PHP server-side | jumbojett/openid-connect-php | Anzeigen | oidc-signin-php.php |
| ASP.NET Core | Microsoft.AspNetCore.Authentication.OpenIdConnect | Anzeigen | oidc-signin-aspnetcore.cs |
Vanilla HTML/JS Rezept
no build; PKCE via subtle-crypto. Rohdatei laden .html
<!--
Sign in with CodeB - Vanilla HTML + JS (no build step).
European Digital Identity Wallet users can also arrive via /logineu.html.
[oidc-signin-cookbook 2026-07-23]
Replace <CODEB_TENANT_HOST> with e.g. phone.codeb.io.
Replace <APP_CALLBACK> with the absolute URL of your callback page.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sign in with CodeB - vanilla</title>
</head>
<body>
<button id="signin">Sign in with CodeB</button>
<pre id="out"></pre>
<script>
const ISSUER = "https://<CODEB_TENANT_HOST>";
const CLIENT_ID = "<YOUR_CLIENT_ID>";
const REDIRECT = "<APP_CALLBACK>";
const SCOPE = "openid profile email";
// --- PKCE helpers (RFC 7636, S256) ------------------------------------
function b64url(bytes){
return btoa(String.fromCharCode.apply(null, bytes))
.replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
}
async function sha256(str){
const h = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
return b64url(new Uint8Array(h));
}
function randomVerifier(){
const a = new Uint8Array(64);
crypto.getRandomValues(a);
return b64url(a);
}
// --- Discovery cache (60s) --------------------------------------------
async function discover(){
const r = await fetch(ISSUER + '/.well-known/openid-configuration');
if(!r.ok) throw new Error('discovery failed: ' + r.status);
return r.json();
}
// --- Begin sign-in ----------------------------------------------------
document.getElementById('signin').onclick = async function(){
const d = await discover();
const verifier = randomVerifier();
const state = b64url(crypto.getRandomValues(new Uint8Array(16)));
const challenge = await sha256(verifier);
sessionStorage.setItem('codeb_pkce_v', verifier);
sessionStorage.setItem('codeb_state', state);
const u = new URL(d.authorization_endpoint);
u.searchParams.set('client_id', CLIENT_ID);
u.searchParams.set('redirect_uri', REDIRECT);
u.searchParams.set('response_type', 'code');
u.searchParams.set('scope', SCOPE);
u.searchParams.set('state', state);
u.searchParams.set('code_challenge', challenge);
u.searchParams.set('code_challenge_method', 'S256');
window.location = u.toString();
};
// --- Callback handling (run this on /oidc-callback.html) -------------
// If ?code=... present, exchange for tokens, then call userinfo.
(async function callback(){
const qs = new URLSearchParams(location.search);
const code = qs.get('code');
if(!code) return;
const state = qs.get('state');
if(state !== sessionStorage.getItem('codeb_state')){
document.getElementById('out').textContent = 'state mismatch';
return;
}
const d = await discover();
const body = new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: REDIRECT,
client_id: CLIENT_ID,
code_verifier: sessionStorage.getItem('codeb_pkce_v')
});
const t = await fetch(d.token_endpoint, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: body.toString()
});
const tok = await t.json();
// Optionally decode id_token; here we just call userinfo.
const ui = await fetch(d.userinfo_endpoint, {
headers: {'Authorization': 'Bearer ' + tok.access_token}
});
const user = await ui.json();
document.getElementById('out').textContent =
'Signed in as ' + user.preferred_username + ' (role=' + (user.role||'user') + ')';
})();
</script>
</body>
</html>
React Rezept
react-oidc-context (oidc-client-ts). Rohdatei laden .jsx
// Sign in with CodeB - React (react-oidc-context).
// European Digital Identity Wallet users can also sign in via the wallet
// button; see verifier-integration-cookbook.html for the widget recipe.
// [oidc-signin-cookbook 2026-07-23]
// npm install react-oidc-context oidc-client-ts
import React from "react";
import { AuthProvider, useAuth } from "react-oidc-context";
const oidcConfig = {
authority: "https://<CODEB_TENANT_HOST>",
client_id: "<YOUR_CLIENT_ID>",
redirect_uri: "<APP_CALLBACK>",
response_type: "code",
scope: "openid profile email",
post_logout_redirect_uri: window.location.origin,
// PKCE (S256) is on by default in oidc-client-ts.
// Discovery -> https://<host>/.well-known/openid-configuration
};
function Profile(){
const auth = useAuth();
if (auth.isLoading) return <p>Loading...</p>;
if (auth.error) return <p>Error: {auth.error.message}</p>;
if (!auth.isAuthenticated){
return <button onClick={() => auth.signinRedirect()}>Sign in with CodeB</button>;
}
const u = auth.user.profile;
return (
<div>
<p>Signed in as {u.preferred_username} (role={u.role || "user"})</p>
<button onClick={() => auth.signoutRedirect()}>Sign out</button>
</div>
);
}
export default function App(){
return (
<AuthProvider {...oidcConfig}>
<Profile />
</AuthProvider>
);
}
Angular Rezept
angular-oauth2-oidc. Rohdatei laden .ts
// Sign in with CodeB - Angular (angular-oauth2-oidc).
// European Digital Identity Wallet users can also arrive via /logineu.html.
// [oidc-signin-cookbook 2026-07-23]
// npm install angular-oauth2-oidc
import { Component, OnInit } from '@angular/core';
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
export const authConfig: AuthConfig = {
issuer: 'https://<CODEB_TENANT_HOST>',
clientId: '<YOUR_CLIENT_ID>',
redirectUri: '<APP_CALLBACK>',
responseType: 'code',
scope: 'openid profile email',
showDebugInformation: false,
// PKCE (S256) is on by default in angular-oauth2-oidc.
// Discovery -> https://<host>/.well-known/openid-configuration
};
@Component({
selector: 'app-signin',
template: `
<button *ngIf="!signedIn" (click)="login()">Sign in with CodeB</button>
<div *ngIf="signedIn">
Signed in as {{name}} (role={{role}})
<button (click)="logout()">Sign out</button>
</div>
`
})
export class SignInComponent implements OnInit {
signedIn = false;
name = '';
role = '';
constructor(private oauth: OAuthService){
this.oauth.configure(authConfig);
}
async ngOnInit(){
await this.oauth.loadDiscoveryDocumentAndTryLogin();
if (this.oauth.hasValidAccessToken()){
const c: any = this.oauth.getIdentityClaims();
this.signedIn = true;
this.name = c.preferred_username;
this.role = c.role || 'user';
}
}
login() { this.oauth.initCodeFlow(); }
logout() { this.oauth.logOut(); }
}
Vue 3 Rezept
custom composable using oidc-client-ts. Rohdatei laden .js
// Sign in with CodeB - Vue 3 composable (uses oidc-client-ts).
// European Digital Identity Wallet users can also arrive via /logineu.html.
// [oidc-signin-cookbook 2026-07-23]
// npm install oidc-client-ts
import { ref, onMounted } from 'vue';
import { UserManager } from 'oidc-client-ts';
const um = new UserManager({
authority: 'https://<CODEB_TENANT_HOST>',
client_id: '<YOUR_CLIENT_ID>',
redirect_uri: '<APP_CALLBACK>',
response_type: 'code',
scope: 'openid profile email',
post_logout_redirect_uri: window.location.origin,
// PKCE (S256) is on by default in oidc-client-ts.
});
export function useCodeBAuth(){
const user = ref(null);
const loading = ref(true);
const error = ref(null);
async function refresh(){
try {
// If we just returned from the OP, complete the exchange.
if (window.location.search.includes('code=')){
user.value = await um.signinRedirectCallback();
window.history.replaceState({}, '', window.location.pathname);
} else {
user.value = await um.getUser();
}
} catch (e) { error.value = e; }
finally { loading.value = false; }
}
onMounted(refresh);
return {
user, loading, error,
login: () => um.signinRedirect(),
logout: () => um.signoutRedirect(),
};
}
Node.js server-side Rezept
openid-client (Express). Rohdatei laden .js
// Sign in with CodeB - Node.js server-side (Express + openid-client).
// European Digital Identity Wallet users may also present via the
// /oauth2/v1/authorize flow; server-side treatment is identical.
// [oidc-signin-cookbook 2026-07-23]
// npm install express express-session openid-client
const express = require('express');
const session = require('express-session');
const { Issuer, generators } = require('openid-client');
const app = express();
app.use(session({ secret: 'change-me', resave:false, saveUninitialized:false }));
let client;
async function init(){
const issuer = await Issuer.discover('https://<CODEB_TENANT_HOST>');
client = new issuer.Client({
client_id: '<YOUR_CLIENT_ID>',
client_secret: '<YOUR_CLIENT_SECRET>', // omit for public clients
redirect_uris: ['<APP_CALLBACK>'],
response_types:['code'],
});
}
init();
app.get('/login', (req, res) => {
const code_verifier = generators.codeVerifier();
const code_challenge = generators.codeChallenge(code_verifier);
const state = generators.state();
req.session.pkce = code_verifier;
req.session.state = state;
res.redirect(client.authorizationUrl({
scope: 'openid profile email',
code_challenge, code_challenge_method: 'S256', state
}));
});
app.get('/oidc-callback', async (req, res) => {
const params = client.callbackParams(req);
const tok = await client.callback('<APP_CALLBACK>', params, {
code_verifier: req.session.pkce,
state: req.session.state
});
// tok.access_token, tok.id_token (already validated), tok.refresh_token
const claims = tok.claims();
const userinfo = await client.userinfo(tok.access_token);
req.session.user = { sub: claims.sub, role: userinfo.role || 'user',
name: userinfo.preferred_username };
res.json(req.session.user);
});
app.listen(3000, () => console.log('http://localhost:3000/login'));
Python server-side Rezept
Authlib (Flask). Rohdatei laden .python
# Sign in with CodeB - Python (Flask + Authlib).
# European Digital Identity Wallet users can also arrive via /logineu.html;
# server-side treatment is identical.
# [oidc-signin-cookbook 2026-07-23]
# pip install flask authlib requests
from flask import Flask, session, url_for, redirect, jsonify
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
app.secret_key = "change-me"
oauth = OAuth(app)
oauth.register(
name="codeb",
server_metadata_url="https://<CODEB_TENANT_HOST>/.well-known/openid-configuration",
client_id="<YOUR_CLIENT_ID>",
client_secret="<YOUR_CLIENT_SECRET>", # omit for public clients
client_kwargs={
"scope": "openid profile email",
"code_challenge_method": "S256", # PKCE
},
)
@app.get("/login")
def login():
return oauth.codeb.authorize_redirect(url_for("callback", _external=True))
@app.get("/oidc-callback")
def callback():
token = oauth.codeb.authorize_access_token()
# token["id_token"] is already validated by Authlib against JWKS.
claims = token.get("userinfo") or oauth.codeb.userinfo(token=token)
session["user"] = {
"sub": claims["sub"],
"name": claims.get("preferred_username"),
"role": claims.get("role", "user"),
}
return jsonify(session["user"])
if __name__ == "__main__":
app.run(port=5000)
PHP server-side Rezept
jumbojett/openid-connect-php. Rohdatei laden .php
<?php
// Sign in with CodeB - PHP (jumbojett/openid-connect-php).
// European Digital Identity Wallet users can also arrive via /logineu.html;
// server-side treatment is identical.
// [oidc-signin-cookbook 2026-07-23]
// composer require jumbojett/openid-connect-php
require __DIR__ . '/vendor/autoload.php';
session_start();
use Jumbojett\OpenIDConnectClient;
$oidc = new OpenIDConnectClient(
'https://<CODEB_TENANT_HOST>',
'<YOUR_CLIENT_ID>',
'<YOUR_CLIENT_SECRET>' // pass '' for public clients
);
$oidc->setRedirectURL('<APP_CALLBACK>');
$oidc->addScope(['openid', 'profile', 'email']);
$oidc->setCodeChallengeMethod('S256'); // PKCE
// The library fetches /.well-known/openid-configuration on first use
// and caches endpoints in the session.
$oidc->authenticate(); // redirects to /oauth2/v1/authorize on first hit,
// handles the callback exchange on second hit.
$_SESSION['user'] = [
'sub' => $oidc->getVerifiedClaims('sub'),
'name' => $oidc->requestUserInfo('preferred_username'),
'role' => $oidc->requestUserInfo('role') ?: 'user',
];
header('Content-Type: application/json');
echo json_encode($_SESSION['user']);
ASP.NET Core Rezept
Microsoft.AspNetCore.Authentication.OpenIdConnect. Rohdatei laden .csharp
// Sign in with CodeB - ASP.NET Core (Microsoft.AspNetCore.Authentication.OpenIdConnect).
// European Digital Identity Wallet users can also arrive via /logineu.html;
// server-side treatment is identical.
// [oidc-signin-cookbook 2026-07-23]
// dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(o =>
{
o.Authority = "https://<CODEB_TENANT_HOST>";
o.ClientId = "<YOUR_CLIENT_ID>";
o.ClientSecret = "<YOUR_CLIENT_SECRET>"; // omit for public clients
o.ResponseType = OpenIdConnectResponseType.Code;
o.UsePkce = true; // S256
o.CallbackPath = "/oidc-callback";
o.Scope.Add("openid");
o.Scope.Add("profile");
o.Scope.Add("email");
o.GetClaimsFromUserInfoEndpoint = true;
o.SaveTokens = true;
// Discovery -> /.well-known/openid-configuration; id_token
// signature verified against JWKS automatically.
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (HttpContext ctx) =>
ctx.User.Identity?.IsAuthenticated == true
? Results.Json(new {
sub = ctx.User.FindFirst("sub")?.Value,
name = ctx.User.FindFirst("preferred_username")?.Value,
role = ctx.User.FindFirst("role")?.Value ?? "user"
})
: Results.Challenge()
);
app.Run();
Selber testen
Nutzen Sie das Smoke-Skript testscripts/aloaha-smoke-test.ps1, um Discovery, JWKS und Refresh-Token-Austausch gegen Ihren Mandanten zu bestaetigen, bevor Sie diese Rezepte einbauen.
FAQ
Strukturierte Antworten im obigen Schema-Block.