Sign-in, end to end, no cookies.

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.

CodeB IdP /oidc.ashx + /login.html RS256 · PER-TENANT KEY U Browser USER · PKCE HA1 HASH ONLY RP Your app RELYING PARTY VERIFIES JWT 1. /authorize 2. login + HA1 3. ?code=...&state=... 4. POST /token (PKCE) 5. access + id + refresh PASSWORD NEVER LEAVES BROWSER · NO COOKIE SET ANYWHERE
Front-channel (browser redirects) Back-channel (server-to-server)

Five hops · password never leaves the browser · no cookie set anywhere

Browser → IdP
01 / Authorize

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.

Browser → IdP
02 / Login

HA1 only, never plaintext

/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.

IdP → Browser → RP
03 / Code

One-time auth code

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.

RP → IdP
04 / Token

Code + verifier exchange

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.

IdP → RP
05 / Tokens

Access + ID + refresh

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.

RP → user
06 / Signed in

You decide what to do next

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.

What the IdP sees vs. what it stores

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.

Where the trust boundaries are

An OIDC flow has three principals (user, IdP, relying party). Trust is bidirectional and bounded:

Multi-tenant isolation in the flow

Tenant identity is the request host. The diagram above runs once per tenant, with no shared state between tenants:

Compared to the media flow

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