Developer cookbook

Sign in with CodeB - OIDC cookbook (authorization_code + PKCE).

Eight framework recipes for adding classic OIDC sign-in (authorization_code + PKCE, S256) to your app. All snippets target the public discovery document at /.well-known/openid-configuration. If you want a wallet-based flow instead, see the verifier integration cookbook. European Digital Identity Wallet sessions produced by that flow land in the same authorization_code exchange.

Scope. These are documented recipes, not a packaged widget. Every snippet works against a real CodeB tenant with a client configured through oidc-clients.html. PKCE (RFC 7636, S256) is mandatory.

Framework matrix

Framework / languageLibraryRecipeSnippet
Vanilla HTML/JSno build; PKCE via subtle-cryptoOpenoidc-signin-vanilla.html
Reactreact-oidc-context (oidc-client-ts)Openoidc-signin-react.jsx
Angularangular-oauth2-oidcOpenoidc-signin-angular.ts
Vue 3custom composable using oidc-client-tsOpenoidc-signin-vue.js
Node.js server-sideopenid-client (Express)Openoidc-signin-nodejs.js
Python server-sideAuthlib (Flask)Openoidc-signin-python.py
PHP server-sidejumbojett/openid-connect-phpOpenoidc-signin-php.php
ASP.NET CoreMicrosoft.AspNetCore.Authentication.OpenIdConnectOpenoidc-signin-aspnetcore.cs

Vanilla HTML/JS Recipe

no build; PKCE via subtle-crypto. Download raw .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 Recipe

react-oidc-context (oidc-client-ts). Download raw .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 Recipe

angular-oauth2-oidc. Download raw .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 Recipe

custom composable using oidc-client-ts. Download raw .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 Recipe

openid-client (Express). Download raw .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 Recipe

Authlib (Flask). Download raw .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 Recipe

jumbojett/openid-connect-php. Download raw .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 Recipe

Microsoft.AspNetCore.Authentication.OpenIdConnect. Download raw .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();

Test it

Use the smoke script testscripts/aloaha-smoke-test.ps1 to confirm discovery, JWKS, and refresh-token exchange against your tenant before wiring these recipes.

FAQ

See structured answers in the schema block above.