Public API

/oidc.ashx · public API

Built-in OpenID Connect identity provider. The handler is fully spec-compliant for Authorization Code + PKCE, exposes discovery + JWKS at the conventional /.well-known paths, and signs id_token / access_token with RS256.

Two URL shapes, one handler. Every endpoint below is reachable in either form — the conventional /oauth2/v1/<name> path (used by most commercial OIDC providers and what discovery advertises), or the legacy /oidc.ashx?action=<name> form. RP libraries that follow discovery automatically use the /oauth2/v1/* shape and need no special configuration.

GET /.well-known/openid-configuration #

RFC 8414 / OpenID Connect Discovery 1.0 metadata document. Lists the issuer, endpoint URLs, supported response types, claims and scopes.

Request

No parameters.

Response

Standard JSON document (verified 2026-06-05):

{
  "issuer": "https://phone.codeb.io",
  "authorization_endpoint":  "https://phone.codeb.io/oauth2/v1/authorize",
  "token_endpoint":          "https://phone.codeb.io/oauth2/v1/token",
  "userinfo_endpoint":       "https://phone.codeb.io/oauth2/v1/userinfo",
  "revocation_endpoint":     "https://phone.codeb.io/oidc.ashx?action=revoke",
  "introspection_endpoint":  "https://phone.codeb.io/oauth2/v1/introspect",
  "jwks_uri":                "https://phone.codeb.io/.well-known/jwks.json",
  "end_session_endpoint":    "https://phone.codeb.io/oauth2/v1/logout",
  "response_types_supported": ["code"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "scopes_supported": ["openid","profile","email","groups","phone","address"],
  "token_endpoint_auth_methods_supported": ["none","client_secret_post"],
  "claims_supported": ["sub","iss","aud","exp","iat","nonce","name",
                       "preferred_username","email","email_verified",
                       "phone_number","address","locale","role","groups"],
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code","refresh_token","urn:ietf:params:oauth:grant-type:jwt-bearer"]
}

Example

curl https://phone.codeb.io/.well-known/openid-configuration
Also reachable as /oidc.ashx?action=discovery.

GET /.well-known/jwks.json #

RFC 7517 JSON Web Key Set. Contains the RSA public key used to verify RS256-signed id_token / access_token JWTs issued by this IdP.

Request

No parameters.

Response

{
  "keys": [
    { "kty":"RSA","use":"sig","alg":"RS256",
      "kid":"<current-kid>","n":"<modulus-b64u>","e":"AQAB" }
  ]
}

Example

curl https://phone.codeb.io/.well-known/jwks.json
Includes both the active key and any rotation-window predecessor so old tokens still verify until they expire. Cache for at most 24 h.

GET /oidc.ashx?action=authorize #

Authorization Code flow entry point with PKCE. Public clients (browsers, native apps) must use code_challenge + code_challenge_method=S256.

Request

Query parameters: response_type=code, client_id, redirect_uri (byte-for-byte registered), scope (default openid), state, nonce, code_challenge, code_challenge_method=S256. Optional fast-path: cp_v2_assertion=<jwt> issued by an already-signed-in same-origin session.

Response

302 redirect to redirect_uri?code=…&state=… on success, or to /login.html?return=… if the visitor isn't signed in yet.

Errors

400 with JSON {error, error_description} on unknown client, invalid redirect URI, missing PKCE, or unsupported challenge method.

Authorization codes are single-use and live 60 seconds.

POST /oidc.ashx?action=login #

Form POST that authenticates the visitor using the same HA1 (MD5(user:realm:password)) as the SIP credentials store. Computes HA1 client-side so plaintext passwords never reach the IdP. When the return URL points back to ?action=authorize, the code is minted directly — no cookie is set.

Request

Body fields: user, ha1 (32 hex), return (optional URL).

Response

Either a 302 redirect with ?code=… appended to return, or JSON { ok: true, code: "…" }.

Errors

400 / 401 { error: "invalid_credentials" }. 429 if IP exceeded 10 attempts in the last 60 s.

HA1 comparison is constant-time. The login is stateless: no session cookie is set on the IdP origin.

POST /oidc.ashx?action=token #

RFC 6749 token endpoint. Exchanges either an authorization code (with PKCE verifier) or a refresh token for a fresh id_token, access_token and rotated refresh_token.

Request

Form / JSON body: grant_type (authorization_code, refresh_token, or urn:ietf:params:oauth:grant-type:jwt-bearer), code, redirect_uri, code_verifier, client_id, client_secret (confidential clients only), refresh_token, assertion (JWT-bearer grant only).

The JWT-bearer grant (RFC 7523) lets a wallet integrator exchange an SSO assertion (typ=sso) from vp-response directly for an access_token, skipping the full Authorization Code + PKCE choreography. See the EU Wallet integration walkthrough →

Response

{
  "id_token": "<rs256-jwt>",
  "access_token": "<rs256-jwt>",
  "refresh_token": "<opaque>",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Errors

400 { error: "invalid_grant" } on code reuse, PKCE mismatch, expired code, unknown client. 401 on confidential-client secret mismatch.

Access tokens last 1 hour. Refresh tokens last 4 hours and rotate on every use.

GET /oidc.ashx?action=end_session #

OpenID Connect RP-Initiated Logout 1.0. Clears the IdP-side SSO assertion and bounces the browser back to post_logout_redirect_uri if it’s registered for the client.

Request

Optional query: id_token_hint, post_logout_redirect_uri, state.

Response

302 to either the registered post-logout URI or /login.html.

Errors

400 if post_logout_redirect_uri isn’t in the client’s registered allow-list.

GET /oauth2/v1/userinfo #

OIDC userinfo endpoint. Returns the canonical claims about the user whose access_token is presented. Requires a valid Bearer issued by this IdP.

Request

HTTP header Authorization: Bearer <access_token>. No body.

Response (verified 2026-06-05)

{
  "sub": "claude",
  "role": "admin",
  "groups": ["admin"],
  "preferred_username": "claude"
}

Additional claims (name, email, email_verified, phone_number, address) appear when the user record has them set and the token’s scope includes them.

Errors

401 invalid_token on missing / malformed / expired Bearer:

{"error":"invalid_token","error_description":"Bearer token required"}

Example

$ curl -H "Authorization: Bearer $ACCESS" https://phone.codeb.io/oauth2/v1/userinfo
{"sub":"claude","role":"admin","groups":["admin"],"preferred_username":"claude"}
Equivalent legacy form: /oidc.ashx?action=userinfo. Discovery advertises the /oauth2/v1/userinfo path so off-the-shelf RP libraries pick it up automatically.

POST /oidc.ashx?action=revoke #

RFC 7009 token revocation. Useful when a user logs out of a confidential RP and you want to invalidate the refresh token immediately.

Request

Body: token, optional token_type_hint=access_token|refresh_token, client_id, client_secret (confidential clients).

Response

200 { "ok": true } — per the RFC, succeeds even if the token is already invalid.

Errors

400 on unknown / mis-authenticated client.

POST /oauth2/v1/introspect #

RFC 7662 token introspection. Submit any token issued by this IdP — access_token, id_token, or refresh_token — and find out whether it’s still active, who it belongs to, and when it expires.

Request

Form / JSON body: token (required), token_type_hint (optional — access_token, id_token or refresh_token), client_id (required only when introspecting a token that was issued to a confidential client), client_secret (then required).

Response

For an active token (RFC 7662 §2.2):

{
  "active": true,
  "token_type": "Bearer",
  "client_id": "<client>",
  "sub": "<user>",
  "scope": "openid profile email",
  "iss": "https://phone.codeb.io",
  "aud": "<client>",
  "exp": 1717248000,
  "iat": 1717244400
}

For an inactive / unknown / expired token — per RFC 7662 §2.2 the only field returned is active:

{"active":false}

Verified 2026-06-05 — POST token=bogus200 {"active":false}.

Errors

400 if token is missing. 401 if the named confidential client’s secret fails to verify. Never a 4xx for “token unknown” — that returns 200 {active: false} by spec.

Useful for resource servers that want to defer token-validation logic to the IdP instead of verifying JWT signatures themselves. Note: for high-traffic resource servers, local JWT verification using the JWKS is usually faster.

GET /oidc.ashx?action=ping #

Build stamp, tenant identity, and live EU Wallet verifier counters. Handy as a liveness probe and a quick way to spot the OID4VP abandonment rate without parsing logs.

Request

No params.

Response

{
  "ok": true,
  "build": "2026-06-09-vp-traceall",
  "tenant": "phone.codeb.io",
  "now": 1780995196,
  "vp_started": 2,
  "vp_completed": 2,
  "vp_abandoned": 0,
  "vp_pending_or_inflight": 0
}

Fields

  • oktrue when the OIDC handler is initialised.
  • build — handler build version. Format YYYY-MM-DD-slug; bumped on every meaningful change.
  • tenant — tenant the request resolved to (multi‑tenancy by domain).
  • now — current server time, seconds since epoch.
  • vp_started / vp_completed — counters of OID4VP sessions opened and completed since service start.
  • vp_abandoned — counter of sessions that timed out without a wallet response.
  • vp_pending_or_inflight — gauge of sessions currently waiting on a wallet.

Verified 2026-06-09.

Example

curl https://phone.codeb.io/oidc.ashx?action=ping
Full EU Wallet verifier endpoint set (vp-start, vp-request, vp-response, verifier-metadata) is documented separately at eu-wallet-api.html.
Need an admin endpoint? Admin-only and OIDC Bearer-gated routes are documented inside the admin UI itself (visible only to signed-in admins on this host). The public API set on this page is the surface you can integrate against without provisioning a CodeB user.

Questions? Ask us · Index: All public APIs