76 lines
2.9 KiB
Markdown
76 lines
2.9 KiB
Markdown
# 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
|