docs: add Keycloak SSO integration design
This commit is contained in:
75
docs/plans/2026-03-01-keycloak-sso-design.md
Normal file
75
docs/plans/2026-03-01-keycloak-sso-design.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Keycloak SSO Integration Design
|
||||
|
||||
**Goal:** Replace local username/password auth with Keycloak OIDC using a backend callback flow.
|
||||
|
||||
**Architecture:** Backend acts as OIDC confidential client. Browser is redirected to Keycloak, which redirects back to a backend callback endpoint. Backend validates the token, checks group membership, provisions the user, and issues its own httpOnly JWT cookie. Frontend is unchanged except the login page.
|
||||
|
||||
**Tech Stack:** authlib (OIDC), FastAPI, Alembic, React
|
||||
|
||||
---
|
||||
|
||||
## Auth Flow
|
||||
|
||||
```
|
||||
Browser → GET /api/auth/oidc/login
|
||||
→ backend generates state, stores in short-lived cookie, redirects to Keycloak
|
||||
|
||||
Keycloak → user authenticates → redirects to:
|
||||
GET /api/auth/oidc/callback?code=...&state=...
|
||||
|
||||
Backend:
|
||||
1. Validates state cookie (CSRF protection)
|
||||
2. Exchanges code for tokens via Keycloak token endpoint
|
||||
3. Validates ID token signature via Keycloak JWKS (authlib handles this)
|
||||
4. Checks groups claim for "firewall admins" → 403 if absent
|
||||
5. Looks up user by keycloak_sub → auto-provisions row if first login
|
||||
6. Issues httpOnly JWT cookie (same mechanism as before)
|
||||
7. Redirects browser to /
|
||||
```
|
||||
|
||||
Removed endpoints: `POST /auth/login`, `POST /auth/register`
|
||||
Kept endpoints: `GET /auth/me`, `POST /auth/logout`
|
||||
|
||||
## Data Model
|
||||
|
||||
New Alembic migration:
|
||||
- Add `keycloak_sub VARCHAR(255) UNIQUE` to `users` table
|
||||
- Make `hashed_password` nullable (always NULL for SSO users; kept for schema stability)
|
||||
|
||||
## Configuration
|
||||
|
||||
**ConfigMap** (non-secret):
|
||||
- `KEYCLOAK_URL`: `https://sso.baumann.gr`
|
||||
- `KEYCLOAK_REALM`: `homelab`
|
||||
- `KEYCLOAK_CLIENT_ID`: `shorefront`
|
||||
|
||||
**Secret** (added to `scripts/create-secrets.sh`):
|
||||
- `KEYCLOAK_CLIENT_SECRET`
|
||||
|
||||
**Redirect URI** (backend callback, registered in Keycloak):
|
||||
- `https://shorefront.baumann.gr/api/auth/oidc/callback`
|
||||
|
||||
## Backend Changes
|
||||
|
||||
- Add `authlib` + `httpx` to `requirements.txt`
|
||||
- Add `keycloak_url`, `keycloak_realm`, `keycloak_client_id`, `keycloak_client_secret` to `Settings`
|
||||
- Add `keycloak_sub` column to `User` model
|
||||
- New migration: add `keycloak_sub`, make `hashed_password` nullable
|
||||
- Replace `backend/app/api/auth.py` with OIDC endpoints:
|
||||
- `GET /auth/oidc/login` — generate state, redirect to Keycloak
|
||||
- `GET /auth/oidc/callback` — exchange code, validate token, check group, provision user, set cookie, redirect
|
||||
- Keep `POST /auth/logout`, `GET /auth/me`
|
||||
- Remove `hash_password`, `verify_password` from `auth.py`
|
||||
|
||||
## Frontend Changes
|
||||
|
||||
- `Login.tsx`: replace username/password form with a single "Sign in with SSO" button (`window.location.href = '/api/auth/oidc/login'`)
|
||||
- All other components unchanged
|
||||
|
||||
## Keycloak Manual Setup (pre-deploy)
|
||||
|
||||
1. Create client `shorefront`, access type: confidential
|
||||
2. Set Valid Redirect URIs: `https://shorefront.baumann.gr/api/auth/oidc/callback`
|
||||
3. Set Web Origins: `https://shorefront.baumann.gr`
|
||||
4. Add Group Membership mapper on client: include groups in ID token, claim name `groups`
|
||||
5. Create group `firewall admins`, add users to it
|
||||
Reference in New Issue
Block a user