Skip to content

Optionally require an OIDC sign-in in front of the app - #29

Merged
FWao merged 1 commit into
mainfrom
oidc-sign-in
Aug 19, 2026
Merged

Optionally require an OIDC sign-in in front of the app#29
FWao merged 1 commit into
mainfrom
oidc-sign-in

Conversation

@FWao

@FWao FWao commented Aug 19, 2026

Copy link
Copy Markdown
Member

Adds an optional OpenID Connect sign-in gate, so the app can be put behind an organisation's SSO where no authenticating proxy exists. Off by default (OIDC_ENABLED=false) — existing deployments are unchanged.

Scope

It is a gate, not an authorisation model. Everyone who can sign in gets the same, whole application: no accounts, no roles, no permissions, no user records. Restricting who may sign in stays at the provider.

llmaixweb's flow was the model, minus everything that needs a database (provider CRUD, JIT provisioning, identity linking, audit rows).

Backend

File
core/config.py 10 OIDC_* settings + APP_PUBLIC_URL; validate_auth_settings() refuses to start on a half-configured gate — in every environment, since a gate that silently doesn't gate is worse than one that doesn't come up
utils/auth.py Session + login-state tokens (HS256), PKCE. No session store: the signed cookie is the session, so restarts and multiple workers are fine
services/oidc_client.py Discovery, authorize URL, code exchange, id_token verification against the provider's JWKS, optional userinfo / end-session
middleware/auth_gate.py The gate — enforced by path in middleware, so a route added later is protected by default rather than by remembering a dependency. Exempt: /api/v1/auth/* and /health/*
routers/v1/endpoints/auth.py session / login / callback / logout

Security properties worth naming:

  • id_token verified for signature, iss, aud, exp and nonce; asymmetric algorithms only (HS256 and none are refused)
  • state token is signed and required in both the URL and a cookie, so a login started elsewhere cannot be completed here
  • absolute session expiry that does not renew on activity (the rule the result cache already follows)
  • HttpOnly + SameSite=Lax + Secure (over https), scoped to /api
  • provider error bodies are logged, never echoed to the browser; the subject is logged only as a log_reference hash

Frontend

stores/auth.ts + components/auth/SignInGate.vue, plus a header account chip with sign-out. Failed sign-ins come back as ?auth_error=<code> and render a sentence rather than JSON. A mid-session 401 — from axios or the NDJSON stream — brings the gate back with "session expired". All strings in de/en/fr/es.

Dependency

pyjwt[crypto]==2.13.0 (MIT; pulls cryptography, Apache-2.0 OR BSD-3-Clause — no AGPL conflict). uv.lock and THIRD_PARTY_NOTICES.md regenerated.

Tests

51 new:

  • token round-trips, forgery, expiry, cross-kind replay, startup validation
  • id_token verification against a real RSA keypair: wrong audience, wrong issuer, expired, foreign signer, replayed nonce, alg: none, JWKS caching and key rotation
  • the gate through the real app: every document route 401s without a session, health probes stay open, and the full login → callback → logout flow

Full local gate passes: ruff, 381 pytest, npm run check, npm run build, 12 Playwright e2e, mkdocs build.

Note: 12 pre-existing Vitest specs (useLocale, settings) fail locally because Node's localStorage needs --localstorage-file. Verified by stashing that they fail identically on main without these changes.

Docs

New docs/operations/sso.md (+ nav), plus configuration reference, deployment, installation, SECURITY.md, THREAT_MODEL.md, DATA_FLOW.md, RISK_REGISTER.md, architecture, .env.example and CHANGELOG.md. AGENTS.md's "no auth" claim was corrected rather than left stale.

@FWao
FWao merged commit 5fde50c into main Aug 19, 2026
14 of 15 checks passed
@FWao FWao mentioned this pull request Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant