Skip to content

feat(auth): OIDC sign-in against a deployment-controlled IdP (#95) - #105

Merged
amar-python merged 1 commit into
mainfrom
feat/m2d-oidc
Oct 26, 2026
Merged

amar-python merged 1 commit into
mainfrom
feat/m2d-oidc

Conversation

@amar-python

Copy link
Copy Markdown
Owner

Fixes #95. Part of ART Milestone 2 (BR-36). ADR-0003 approved OIDC with a local-account fallback.

Flow

  1. The frontend completes the IdP's authorization-code flow (with PKCE), which is M2e: frontend sign-in and tenant-scoped session (BR-36) #96.
  2. It posts {tenant, idToken} to POST /api/auth/oidc.
  3. The API verifies the token and returns the same session as a local sign-in.

Defaults I chose (please confirm)

Decision Default Why
Who may sign in Only existing, active local users who are members of the requested tenant The IdP proves identity, but authorisation stays local. Nobody is auto-created from claims, so an IdP misconfiguration can't create accounts or grant roles.
Which claim names the user preferred_username (OIDC_USERNAME_CLAIM) This is Keycloak's and Entra ID's usual choice. email or upn also work.
Signing algorithms RS256, ES256 only none and HMAC algorithms are refused.
Where keys come from https:// (fetched, and refetched on an unknown key ID, which handles IdP key rotation) or file:// (a key file published inside an enclave) http:// is refused, because keys fetched over plain HTTP could be swapped.
Required token fields exp, iat, iss, aud
When OIDC isn't configured The route returns 404 Air-gapped installs keep using local accounts.
Misconfiguration A missing audience or an http:// key URL stops the API at startup Fail closed.

New dependency: PyJWT[crypto] (cryptography). Wheels exist for the offline bundle's target (CPython 3.12, manylinux x86_64), so build_bundle.sh picks it up automatically.

Evidence

All runs are against local PostgreSQL 16.

Check Result
Local replica of the CI integration job (scripts/test_report.py --strict) 297 passed, 0 failed, 0 skipped (288 on main + 9 new)
Evals p,i,s,e 26/26
tests/test_oidc.py 9 passed; see below
Same tests with the issuer check removed the wrong-issuer case fails (as expected)
DB-free pytest 132 passed
scripts/lint.sh clean

What tests/test_oidc.py covers:

  • Valid tokens: a valid token yields its username.
  • Rejected tokens: expired, wrong issuer, wrong audience, signed by another key, unknown key ID, no exp, no username claim, alg: none, and malformed input.
  • Sign-in through the API:
    • a member gets a session with their role;
    • a non-member, unknown, inactive or invalid sign-in gets 401;
    • the route returns 404 when OIDC isn't configured.
  • Startup checks: both misconfigurations stop the API.

Not exercised by these tests: the https:// key fetch. The workspace has no network, so the tests use a file:// key file with a locally generated RSA key. The https:// path is PyJWT's PyJWKClient, unchanged. A check against your real IdP (Keycloak or Entra ID) would cover it.


Generated by Claude Code

POST /api/auth/oidc exchanges an IdP ID token for a normal session. The
token's signature (RS256/ES256 from the IdP's JWKS), issuer, audience and
expiry are required and checked; the username claim must name an existing,
active local user who is a member of the requested tenant. Nobody is
created from claims. JWKS can be https:// or a file:// key file for an
enclave; http:// is refused. Off unless OIDC_ISSUER is set, and a
half-configured IdP stops startup.

Adds PyJWT[crypto] to api/requirements.txt (wheels exist for the offline
bundle's CPython 3.12 manylinux target).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YPoXzBSj89ZHgw7nfTmbB
@amar-python amar-python added this to the ART extension milestone Oct 26, 2026 — with Claude
@amar-python amar-python added enhancement New feature or request art ART framework extension work labels Oct 26, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

OIDC defaults approved by the product owner on 26 Oct.


Generated by Claude Code

@amar-python
amar-python merged commit 9f789cf into main Oct 26, 2026
3 checks passed
@amar-python
amar-python deleted the feat/m2d-oidc branch October 27, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

art ART framework extension work enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M2d: OIDC sign-in against a deployment-controlled IdP (BR-36)

2 participants