Adds OIDC login support to the harmony-fleet-operator web dashboard using Zitadel SSO.
pkce was the recommended option for this since we don't need to hold on to any secret. We compute a value on server before sending the data to Zitadel who validates authenticity by recomputing the hash and comparing the two values.
pkce Auth flow
1. User visits a protected dashboard route, like /devices.
2. If no valid harmony_fleet_session cookie exists, the app redirects to /login.
3. /login creates:
- random state
- random pkce_code_verifier
- derived code_challenge = base64url(sha256(pkce_code_verifier))
4. The app stores state and pkce_code_verifier in a temporary HTTP-only login-attempt cookie.
5. The browser is redirected to Zitadel’s authorize endpoint with:
- client_id
- redirect_uri
- scope
- state
- code_challenge
- code_challenge_method=S256
6. After SSO login, Zitadel redirects back to /auth/callback?code=...&state=....
7. The callback handler:
- parses the raw query into a strict success/failure enum
- reads the temporary login-attempt cookie
- validates returned state
- exchanges code + pkce_code_verifier for tokens
- validates the returned ID token using OIDC discovery/JWKS
- creates a local harmony_fleet_session cookie
- redirects to /
8. Protected routes validate the local dashboard session cookie on each request.
9. /logout clears the dashboard session cookie and redirects to /login.
---
Auth middleware responses depending on request type:
- normal browser request: redirect to /login
- SSE request: 401 authentication required
- HTMX request: 401 with HX-Redirect: /login (HTMX redirect is more idiomatic than through Axum for this)
Reviewed-on: #284
Reviewed-by: johnride <jg@nationtech.io>
Co-authored-by: Reda Tarzalt <tarzaltreda@gmail.com>
Co-committed-by: Reda Tarzalt <tarzaltreda@gmail.com>