Files
harmony/docs/guides/web-auth-security.md
Jean-Gabriel Gill-Couture 75aac243c7
All checks were successful
Run Check Script / check (pull_request) Successful in 2m18s
docs: operator dashboard SSO (Zitadel) setup guide
Step-by-step for wiring the operator dashboard's browser SSO: the Zitadel
app settings the code requires (Web app, PKCE/no-secret, redirect +
post-logout URIs), each config value mapped to its source
(ZitadelAuthConfig + cookie key), how to provide them (staging via
FleetDeployConfig/Secrets with hosts derived from base_domain; local via
HARMONY_CONFIG_* env), the derived endpoints, and the common-failure
gotchas (iss/aud/redirect mismatch, no client secret, localhost dev mode,
≥64-byte cookie key). Grounded in harmony_zitadel_auth's login/jwks code.

Registered in SUMMARY and cross-linked from web-auth-security.
2026-06-01 23:06:45 -04:00

220 lines
8.3 KiB
Markdown

# Web Authentication and CSRF Security Guidelines
These guidelines define the baseline for Harmony web frontends and future operator dashboards that use browser-based authentication, cookie sessions, Axum, HTMX, or OIDC providers such as Zitadel.
> Setting up the fleet operator dashboard's SSO concretely (Zitadel app + the exact config values)? See [Operator Dashboard SSO (Zitadel) — setup](./operator-dashboard-sso.md).
## Goals
- Prevent unauthenticated access.
- Prevent authenticated users from performing actions they are not authorized to perform.
- Prevent CSRF on state-changing endpoints.
- Reduce XSS impact with CSP and safe rendering practices.
- Keep authentication code understandable and reusable across projects.
## Required Baseline
Every browser-facing authenticated application must implement the following controls before production use:
1. **OIDC Authorization Code + PKCE** for login.
2. **OIDC nonce validation** on login callback.
3. **Explicit authorization checks** using roles, groups, claims, or permissions.
4. **CSRF protection** on all mutating routes.
5. **Secure cookie settings**: `HttpOnly`, `Secure` in production, constrained `SameSite`, and appropriate path/domain scoping.
6. **Strict security headers**, especially Content Security Policy.
7. **No permissive credentialed CORS** for operator dashboards.
8. **Generic client-facing errors** with detailed errors logged server-side only.
## OIDC Login Requirements
Use Authorization Code flow with PKCE. On login start, generate and persist a short-lived login attempt containing:
- `state`
- `pkce_code_verifier`
- `nonce`
- creation timestamp or cookie expiration
Send `state`, PKCE challenge, and `nonce` to the authorization endpoint.
On callback:
1. Require a valid login-attempt cookie.
2. Validate returned `state` against the stored state.
3. Exchange the authorization code using the stored PKCE verifier.
4. Validate the returned ID token as an OIDC ID token, including:
- signature
- issuer
- audience/client ID
- expiration/not-before
- nonce
- authorized party (`azp`) when applicable
5. Create the application session only after all checks pass.
6. Delete the login-attempt cookie.
`state` and `nonce` are not interchangeable:
- `state` binds the callback redirect to the browser login attempt.
- `nonce` binds the returned ID token to the browser login attempt.
- PKCE binds the code exchange to the client that started the flow.
## Session Requirements
For small internal dashboards, a verified short-lived ID token in an `HttpOnly` cookie may be acceptable. For higher-risk systems, prefer server-side sessions:
- Store a random session ID in the browser cookie.
- Store tokens and session metadata server-side.
- Support revocation, rotation, idle timeout, and absolute timeout.
Session cookies must use:
- `HttpOnly`
- `Secure` outside local development
- `SameSite=Lax` or `SameSite=Strict`
- `Path=/` unless a narrower path is possible
- No broad `Domain` attribute unless explicitly required
Production services should fail closed if HTTPS/secure-cookie configuration is inconsistent.
## Authorization Requirements
Authentication is not authorization. A valid identity provider token only proves who the user is.
Every protected application must define required permissions for each state-changing or sensitive route. Examples:
- `fleet:viewer` for read-only dashboard access
- `fleet:operator` for alert acknowledgement and operational actions
- `fleet:admin` for settings, user management, or destructive actions
Authorization must be enforced server-side. UI hiding is not sufficient.
## CSRF Protection Standard
For Axum + HTMX dashboards, the recommended baseline is:
1. Require a custom header on all mutating requests.
2. Validate `Origin` or `Referer` against the configured application origin.
3. Keep cookies `SameSite=Lax` or stricter.
4. Do not enable permissive credentialed CORS.
Mutating methods are:
- `POST`
- `PUT`
- `PATCH`
- `DELETE`
Recommended behavior:
- Reject mutating requests without `x-csrf-token`.
- Reject mutating requests whose `Origin` is present and does not match the configured base URL origin.
- If `Origin` is absent, require `Referer` to match the configured base URL origin.
- Reject when neither `Origin` nor `Referer` is available, unless the route is explicitly exempted and documented.
The CSRF header value may be static for HTMX dashboards, for example `x-csrf-token: 1`. The protection comes from the fact that cross-origin HTML forms cannot set custom headers, and cross-origin JavaScript cannot send custom headers with credentials unless CORS allows it.
Do not rely on header presence alone if adding Origin/Referer validation is practical.
## HTMX Integration
Add the CSRF header globally from a static JavaScript file:
```js
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['x-csrf-token'] = '1';
});
```
Serve this as a static asset, for example `/static/app.js`. Avoid inline scripts so that the application can use a strict CSP without `unsafe-inline`.
## Content Security Policy
Every browser-facing dashboard should set a restrictive CSP. A good starting point is:
```http
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
```
Meaning:
- Only load scripts, styles, and API/SSE/HTMX connections from the same origin.
- Prevent clickjacking with `frame-ancestors 'none'`.
- Prevent plugin/object execution with `object-src 'none'`.
- Prevent injected `<base>` tags from rewriting relative URLs.
- Prevent forms from submitting to external origins.
If inline scripts or styles are unavoidable, prefer per-response nonces over `unsafe-inline`.
## Other Security Headers
Set these headers on all HTML responses, or globally when safe:
```http
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
```
When the service is HTTPS-only, also set HSTS:
```http
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
Only enable HSTS when the domain and subdomains are intended to be HTTPS-only.
## CORS Policy
Operator dashboards should normally not enable CORS.
Never combine all of the following unless there is a reviewed, explicit integration need:
- credentialed requests
- arbitrary or reflected origins
- custom request headers such as `x-csrf-token`
A permissive credentialed CORS policy can bypass custom-header CSRF protection.
## Error Handling
Client-facing auth errors should be generic, for example:
```text
Authentication failed. Please start login again.
```
Detailed causes, provider responses, token validation failures, and stack traces should be logged server-side only.
Avoid returning raw OIDC provider error bodies or JWT validation details to the browser.
## Implementation Checklist
Before shipping a Harmony web frontend:
- [ ] Login uses Authorization Code + PKCE.
- [ ] Login attempt stores `state`, PKCE verifier, `nonce`, and expires quickly.
- [ ] Callback validates `state`.
- [ ] Callback validates ID token nonce.
- [ ] JWT validation checks issuer and exact intended audience/client.
- [ ] Authorization roles/permissions are enforced server-side.
- [ ] Mutating routes are protected by CSRF middleware.
- [ ] CSRF middleware requires custom header and same-origin `Origin`/`Referer`.
- [ ] Session cookies are `HttpOnly`, `Secure` in production, and `SameSite=Lax` or stricter.
- [ ] No permissive credentialed CORS is enabled.
- [ ] CSP is configured without `unsafe-inline` where practical.
- [ ] Security headers are configured.
- [ ] Auth errors shown to users are generic.
- [ ] Detailed auth failures are logged server-side.
## Recommended Default for Harmony Dashboards
For current and future Axum + HTMX dashboards, use this default design:
- Zitadel/OIDC Authorization Code + PKCE + nonce.
- Short-lived encrypted login-attempt cookie.
- Server-side authorization middleware based on roles/claims.
- `HttpOnly`, `Secure`, `SameSite=Lax` or `Strict` session cookie.
- CSRF middleware requiring `x-csrf-token` and same-origin `Origin`/`Referer`.
- Static `/static/app.js` that adds the HTMX CSRF header.
- Strict CSP that allows scripts only from `self`.
- No CORS unless explicitly reviewed.