MCIAS now acts as an SSO provider for downstream services. Services
redirect users to /sso/authorize, MCIAS handles login (password, TOTP,
or passkey), then redirects back with an authorization code that the
service exchanges for a JWT via POST /v1/sso/token.
- Add SSO client registry to config (client_id, redirect_uri,
service_name, tags) with validation
- Add internal/sso package: authorization code and session stores
using sync.Map with TTL, single-use LoadAndDelete, cleanup goroutines
- Add GET /sso/authorize endpoint (validates client, creates session,
redirects to /login?sso=<nonce>)
- Add POST /v1/sso/token endpoint (exchanges code for JWT with policy
evaluation using client's service_name/tags from config)
- Thread SSO nonce through password→TOTP and WebAuthn login flows
- Update login.html, totp_step.html, and webauthn.js for SSO nonce
passthrough
Security:
- Authorization codes are 256-bit random, single-use, 60-second TTL
- redirect_uri validated as exact match against registered config
- Policy context comes from MCIAS config, not the calling service
- SSO sessions are server-side only; nonce is the sole client-visible value
- WebAuthn SSO returns redirect URL as JSON (not HTTP redirect) for JS compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ui/ui.go: add pendingLogin struct and pendingLogins sync.Map
to UIServer; add issueTOTPNonce (generates 128-bit random nonce,
stores accountID with 90s TTL) and consumeTOTPNonce (single-use,
expiry-checked LoadAndDelete); add dummyHash() method
- ui/handlers_auth.go: split handleLoginPost into step 1
(password verify → issue nonce) and step 2 (handleTOTPStep,
consume nonce → validate TOTP) via a new finishLogin helper;
password never transmitted or stored after step 1
- ui/ui_test.go: refactor newTestMux to reuse new
newTestUIServer; add TestTOTPNonceIssuedAndConsumed,
TestTOTPNonceUnknownRejected, TestTOTPNonceExpired, and
TestLoginPostPasswordNotInTOTPForm; 11/11 tests pass
- web/templates/fragments/totp_step.html: replace
'name=password' hidden field with 'name=totp_nonce'
- db/accounts.go: add GetAccountByID for TOTP step lookup
- AUDIT.md: mark F-02 as fixed
Security: the plaintext password previously survived two HTTP
round-trips and lived in the browser DOM during the TOTP step.
The nonce approach means the password is verified once and
immediately discarded; only an opaque random token tied to an
account ID (never a credential) crosses the wire on step 2.
Nonces are single-use and expire after 90 seconds to limit
the window if one is captured.
- Introduced `web/templates/` for HTMX-fragmented pages (`dashboard`, `accounts`, `account_detail`, `error_fragment`, etc.).
- Implemented UI routes for account CRUD, audit log display, and login/logout with CSRF protection.
- Added `internal/ui/` package for handlers, CSRF manager, session validation, and token issuance.
- Updated documentation to include new UI features and templates directory structure.
- Security: Double-submit CSRF cookies, constant-time HMAC validation, login password/Argon2id re-verification at all steps to prevent bypass.