The PKCE challenge
The user clicks Sign in. The browser mints a random code verifier, hashes it to a code challenge, stashes both in sessionStorage, and redirects to /oidc.ashx?action=authorize with the challenge and a CSRF state token.
Five steps from "click sign in" to a signed JWT in the relying party. The IdP brokers identity; the password never leaves the browser; the only state in transit is short-lived, signed tokens.
Five hops · password never leaves the browser · no cookie set anywhere
The user clicks Sign in. The browser mints a random code verifier, hashes it to a code challenge, stashes both in sessionStorage, and redirects to /oidc.ashx?action=authorize with the challenge and a CSRF state token.
/login.html renders a form. The browser computes MD5(user:realm:password) and POSTs only that hash. The server constant-time compares against the stored HA1. The plaintext password never leaves the browser; the server can’t see it even with full request logging.
On HA1 match, the IdP mints a 60-second single-use auth code and redirects the browser to your redirect_uri with ?code=...&state=.... Cookie-free: the IdP doesn’t set or read any session cookie — the code travels in the URL only.
Your app validates state, then POSTs the code plus the PKCE code verifier to /oidc.ashx?action=token. The IdP confirms the verifier hashes to the original challenge, burns the code, and returns three JWTs.
Three RS256-signed JWTs. access_token (1 hour) for API calls. id_token (1 hour) carries identity claims (sub, role, email). refresh_token (4 hours, single-use rotating) buys the next access token. Your app verifies signatures against the JWKS at /.well-known/jwks.json.
From the verified id_token you have sub (the username) and role (admin / user / siponly / guest). Drop the user into your authenticated session. If you want extra profile fields, hit /userinfo with the access token.
The IdP’s job is identity verification, not surveillance. Each request gets the minimum data needed to do that one job, and nothing is persisted beyond what audit + token validation demand.
client_id and redirect_uri, the source IP for rate limiting.App_Data/<tenant>/logs/codeb-oidc-YYYY-MM-DD.log. Significant events also land in the Windows Event Log under source CodeBOIDC.An OIDC flow has three principals (user, IdP, relying party). Trust is bidirectional and bounded:
iss matches the IdP, aud matches the RP, and exp hasn’t passed.sessionStorage; that’s why refresh tokens last 4 hours, rotate on every use, and are revoked the moment a reused token is detected.Tenant identity is the request host. The diagram above runs once per tenant, with no shared state between tenants:
App_Data/<tenant>/oidc/private-key.xml. A token minted on tenant A fails signature verification at tenant B.App_Data/<tenant>/sip-credentials/<tenant-slug>.json. Usernames across tenants don’t collide.App_Data/<tenant>/logs/. Cross-tenant log access is impossible from the IdP itself.iss claim in every token is https://<that-tenant-host>. Servers verifying the token MUST check iss matches what they expect.The media data flow is peer-to-peer: video and audio never touch the server, only signaling does. OIDC is the opposite shape — everything goes through the IdP, because identity is centralised by definition. The shared trait is what’s NOT stored: the IdP never keeps tokens after issuance, just like the signaling server never keeps media frames after forwarding the SDP.
Building an integration? → OIDC integration how-to · OIDC overview