Repository navigation
Add optional OIDC sign-in (Entra ID / Authentik) with admin, developer and read-only roles - #320
zwanski-start wants to merge 2 commits into
Conversation
Opt-in via AUTOFORGE_AUTH_MODE=oidc. Works with any OpenID Connect
provider (Entra ID, Authentik, Keycloak) using the authorization code
flow with PKCE; ID tokens are verified against the provider's JWKS.
- server/auth/: settings, OIDC client, in-memory sessions, roles file
(~/.autoforge/auth.yaml, hot-reloaded, fail closed), central route
policy table, and a pure-ASGI middleware enforcing it on HTTP and
WebSocket routes plus same-origin checks for state-changing requests
- Roles: admin (global), developer / readonly (per project, globs)
- /api/auth/{login,callback,logout,me}; project list filtered per user
- No credentialed wildcard CORS when auth is enabled
- Docs, example roles file, .env.example, tests (test_auth.py) in CI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- AuthGate shows a "Sign in with <provider>" screen when server auth is enabled and there is no session, and a "No access yet" screen (with the user's identity and groups) for users without any role - API 401s re-check the session so expiry returns to the sign-in screen - Sidebar gets a sign-out entry; read-only projects show a Read-only badge instead of agent/dev-server/reset controls; New Project and delete are hidden for non-admins - No change when auth is disabled Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🔍 QA report: ✅ PassedTester: Guido (cubefarm agent, in a fresh session) · Round: 1 · Author: Ada The opt-in OIDC sign-in with admin / per-project developer / read-only roles works end to end against a local fake OIDC provider (RS256, PKCE, end-session): sign-in, project filtering, Read-only badge, admin-only actions, sign-out, the 'No access yet' screen and roles-file hot reload all behave as described, and the server enforces every role on HTTP and WebSocket, including cross-origin rejection. With auth off the app behaves exactly as before, and an invalid auth config stops the server from starting. The 40 new auth tests, both security suites, ruff, the UI build and lint all pass; the remaining pytest and mypy failures are identical on master. Minor notes: a denied WebSocket gets a plain HTTP 403 on the handshake rather than the 4401/4403 close codes the description claims, and some write buttons deeper in the UI are still visible to read-only users (documented as a follow-up; the server rejects those actions).
🧪 Commands run
No browser screenshots were taken in this round. Posted by cubefarm · merges automatically once GitHub's checks pass |
Closes #32
What this adds
An opt-in sign-in for the web UI that works with any OpenID Connect provider (Microsoft Entra ID, Authentik, Keycloak, …), plus a small role system that can be set per project. Nothing changes unless
AUTOFORGE_AUTH_MODE=oidcis set.admindeveloperreadonlyRoles are assigned in
~/.autoforge/auth.yamlto users (email / preferred_username / sub) or groups (thegroups/rolesclaims, e.g. Entra app roles or Authentik groups). Project keys accept glob patterns, and the file reloads automatically when it changes. Seeexamples/auth.yamland the new README section.Backend (
server/auth/)statebound to the browser by a cookie (prevents login CSRF), nonce check, ID token signature checked against the provider's JWKS (and HS* with the client secret, for Authentik without a signing key), plusaud/iss/expchecks. If the ID token has no group claims, groups are read from userinfo. Logout also signs out at the provider when it supports that.policy.py) maps method + path to the access each route needs. Any/apior/wsroute not in the table needs admin, so it is denied by default. A pure-ASGIAuthMiddlewareenforces this for HTTP and WebSocket handshakes (close codes 4401/4403).GET /api/projectsonly lists the projects the user can see./api/auth/login,/api/auth/callback,/api/auth/logout,/api/auth/me.httpxandpyjwt[crypto]were already installed throughmcp. They are now listed explicitly in both requirements files.UI
How I verified it
test_auth.pyadds 40 tests. They cover the settings loader, the policy table, role resolution (globs, admin, default role, reload, invalid file fails closed), the middleware over HTTP and WS (401/403, cross-origin rejection, trusted origins), and a full login flow against a mocked provider (state cookie binding, single-use state, nonce mismatch, wrong audience, open redirect). All pass. I also added this file to CI.ruff check .is clean, mypy is clean on the new modules, andpython test_security.pypasses.test_ws_security.py/test_devserver_security.py: 21 tests fail, and exactly the same 21 fail on unmodifiedmaster. They were already failing and have nothing to do with this change.npm run lintshows no new warnings, andnpm run buildsucceeds./api/auth/mereportsenabled:falseand everything works as before.Assumptions / decisions
adminrole on top of the requested developer/read-only, because creating/deleting projects, global settings and the server filesystem browser aren't tied to one project.developer: they run a shell or Claude, and the assistant can create features.:5173proxying to:8888), setAUTOFORGE_OIDC_REDIRECT_URI=http://localhost:5173/api/auth/callbackandAUTOFORGE_AUTH_TRUSTED_ORIGINS=http://localhost:5173. Both are documented.Possible follow-ups (not in this PR)
🤖 Generated with Claude Code