Add Keycloak realm, JWT auth, and role-based access control #4

Closed
opened 2026-03-16 01:40:43 +00:00 by forgejo_admin · 0 comments
Contributor

Lineage

plan-mcd-tracker → Phase 4: Keycloak Realm + Auth

Repo

forgejo_admin/mcd-tracker-api

User Story

As a user
I want to authenticate via Keycloak to access my coupon data
So that my data is private and only I can manage my locations and codes.

Architecture

  • arch-dataflow-mcd-tracker#auth-flow-keycloak-oidc — implements the OIDC auth flow. Two clients: confidential (SvelteKit) + public/PKCE (iOS).
  • arch-domain-mcd-tracker — User entity uses keycloak_sub from JWT, no local user table.

Context

Phases 1-3 complete. FastAPI app is live with SQLAlchemy models and Postgres. Keycloak is already running at https://keycloak.tail5b443a.ts.net in the keycloak namespace. Admin credentials are in ~/secrets/pal-e-services/secrets.env (KEYCLOAK_ADMIN_PASSWORD). The existing westside-basketball realm is the reference for how realms are configured.

Follow the basketball-api auth pattern at ~/basketball-api/src/basketball_api/auth.py.

File Targets

Files to create:

  • src/mcd_tracker_api/auth.py — JWKS fetch + cache, JWT decode (RS256), User dataclass (sub, email, username, roles), get_current_user() dependency, require_role() dependency factory

Files to modify:

  • src/mcd_tracker_api/config.py — add keycloak_realm_url: str field (default to placeholder, overridden by MCD_TRACKER_KEYCLOAK_REALM_URL env var)
  • pyproject.toml — add dependencies: python-jose[cryptography], httpx
  • tests/test_auth.py — auth tests (mock JWT for CI — Keycloak isn't available in CI)

Keycloak setup (via admin API, not manual UI):

  • Create realm mcd-tracker via POST /admin/realms using admin credentials from ~/secrets/pal-e-services/secrets.env
  • Keycloak admin URL: https://keycloak.tail5b443a.ts.net
  • Create realm roles: user, admin
  • Create OIDC client mcd-tracker-app: confidential, redirect URIs https://mcd-tracker-app.tail5b443a.ts.net/*
  • Create OIDC client mcd-tracker-ios: public, PKCE enabled, redirect URIs mcd-tracker://callback
  • Create test user: testuser with role user
  • Create admin user: testadmin with role admin

Kustomize update:

  • Add MCD_TRACKER_KEYCLOAK_REALM_URL env var to ~/pal-e-deployments/overlays/mcd-tracker/prod/deployment-patch.yaml
  • Value: https://keycloak.tail5b443a.ts.net/realms/mcd-tracker

Files NOT to touch:

  • src/mcd_tracker_api/models.py — no changes needed
  • src/mcd_tracker_api/routes/health.py — health doesn't need auth

Acceptance Criteria

  • Keycloak realm mcd-tracker exists with roles user and admin
  • Two OIDC clients created: mcd-tracker-app (confidential) and mcd-tracker-ios (public + PKCE)
  • auth.py validates JWT from Keycloak JWKS endpoint
  • get_current_user() extracts sub, email, username, roles from JWT
  • require_role("admin") returns 403 for non-admin users
  • No token → 401 Unauthorized
  • Invalid token → 401 Unauthorized
  • Valid token, wrong role → 403 Forbidden
  • MCD_TRACKER_KEYCLOAK_REALM_URL wired in deployment patch
  • Tests pass in CI (mock JWT — no live Keycloak in CI)

Test Expectations

  • test_auth.py — mock JWT tests: valid → extracts user, missing → 401, bad sig → 401, wrong role → 403
  • Run command: pytest tests/ -v

Constraints

  • Copy basketball-api auth.py pattern closely — proven in production
  • Use python-jose[cryptography] for JWT (same as basketball-api)
  • Use httpx for JWKS fetch (same as basketball-api)
  • Auth tests MUST mock JWT — Keycloak is not available in Woodpecker CI
  • Keycloak internal URL for JWT validation: http://keycloak.keycloak.svc.cluster.local:80/realms/mcd-tracker BUT use external URL https://keycloak.tail5b443a.ts.net/realms/mcd-tracker in deployment patch (issuer in JWT matches external URL). See basketball-api lesson: issuer mismatch between internal/external URLs.
  • Do NOT add API routes that use auth yet — that's Phase 5. Just wire the auth dependencies so Phase 5 can Depends(get_current_user).

Checklist

  • PR opened with Closes #3
  • Keycloak realm created
  • Tests pass
  • Ruff clean
  • Deployment patch updated (separate PR on pal-e-deployments if needed)
  • No unrelated changes
  • project-mcd-tracker — project page
  • phase-mcd-tracker-4-keycloak-auth — phase note
  • arch-dataflow-mcd-tracker — auth flow diagram
  • plan-mcd-tracker — parent plan
### Lineage `plan-mcd-tracker` → Phase 4: Keycloak Realm + Auth ### Repo `forgejo_admin/mcd-tracker-api` ### User Story As a user I want to authenticate via Keycloak to access my coupon data So that my data is private and only I can manage my locations and codes. ### Architecture - `arch-dataflow-mcd-tracker#auth-flow-keycloak-oidc` — implements the OIDC auth flow. Two clients: confidential (SvelteKit) + public/PKCE (iOS). - `arch-domain-mcd-tracker` — User entity uses `keycloak_sub` from JWT, no local user table. ### Context Phases 1-3 complete. FastAPI app is live with SQLAlchemy models and Postgres. Keycloak is already running at `https://keycloak.tail5b443a.ts.net` in the `keycloak` namespace. Admin credentials are in `~/secrets/pal-e-services/secrets.env` (`KEYCLOAK_ADMIN_PASSWORD`). The existing `westside-basketball` realm is the reference for how realms are configured. Follow the basketball-api auth pattern at `~/basketball-api/src/basketball_api/auth.py`. ### File Targets Files to create: - `src/mcd_tracker_api/auth.py` — JWKS fetch + cache, JWT decode (RS256), User dataclass (sub, email, username, roles), `get_current_user()` dependency, `require_role()` dependency factory Files to modify: - `src/mcd_tracker_api/config.py` — add `keycloak_realm_url: str` field (default to placeholder, overridden by `MCD_TRACKER_KEYCLOAK_REALM_URL` env var) - `pyproject.toml` — add dependencies: `python-jose[cryptography]`, `httpx` - `tests/test_auth.py` — auth tests (mock JWT for CI — Keycloak isn't available in CI) Keycloak setup (via admin API, not manual UI): - Create realm `mcd-tracker` via `POST /admin/realms` using admin credentials from `~/secrets/pal-e-services/secrets.env` - Keycloak admin URL: `https://keycloak.tail5b443a.ts.net` - Create realm roles: `user`, `admin` - Create OIDC client `mcd-tracker-app`: confidential, redirect URIs `https://mcd-tracker-app.tail5b443a.ts.net/*` - Create OIDC client `mcd-tracker-ios`: public, PKCE enabled, redirect URIs `mcd-tracker://callback` - Create test user: `testuser` with role `user` - Create admin user: `testadmin` with role `admin` Kustomize update: - Add `MCD_TRACKER_KEYCLOAK_REALM_URL` env var to `~/pal-e-deployments/overlays/mcd-tracker/prod/deployment-patch.yaml` - Value: `https://keycloak.tail5b443a.ts.net/realms/mcd-tracker` Files NOT to touch: - `src/mcd_tracker_api/models.py` — no changes needed - `src/mcd_tracker_api/routes/health.py` — health doesn't need auth ### Acceptance Criteria - [ ] Keycloak realm `mcd-tracker` exists with roles `user` and `admin` - [ ] Two OIDC clients created: `mcd-tracker-app` (confidential) and `mcd-tracker-ios` (public + PKCE) - [ ] `auth.py` validates JWT from Keycloak JWKS endpoint - [ ] `get_current_user()` extracts sub, email, username, roles from JWT - [ ] `require_role("admin")` returns 403 for non-admin users - [ ] No token → 401 Unauthorized - [ ] Invalid token → 401 Unauthorized - [ ] Valid token, wrong role → 403 Forbidden - [ ] `MCD_TRACKER_KEYCLOAK_REALM_URL` wired in deployment patch - [ ] Tests pass in CI (mock JWT — no live Keycloak in CI) ### Test Expectations - [ ] `test_auth.py` — mock JWT tests: valid → extracts user, missing → 401, bad sig → 401, wrong role → 403 - Run command: `pytest tests/ -v` ### Constraints - Copy basketball-api `auth.py` pattern closely — proven in production - Use `python-jose[cryptography]` for JWT (same as basketball-api) - Use `httpx` for JWKS fetch (same as basketball-api) - Auth tests MUST mock JWT — Keycloak is not available in Woodpecker CI - Keycloak internal URL for JWT validation: `http://keycloak.keycloak.svc.cluster.local:80/realms/mcd-tracker` BUT use external URL `https://keycloak.tail5b443a.ts.net/realms/mcd-tracker` in deployment patch (issuer in JWT matches external URL). See basketball-api lesson: issuer mismatch between internal/external URLs. - Do NOT add API routes that use auth yet — that's Phase 5. Just wire the auth dependencies so Phase 5 can `Depends(get_current_user)`. ### Checklist - [ ] PR opened with `Closes #3` - [ ] Keycloak realm created - [ ] Tests pass - [ ] Ruff clean - [ ] Deployment patch updated (separate PR on pal-e-deployments if needed) - [ ] No unrelated changes ### Related - `project-mcd-tracker` — project page - `phase-mcd-tracker-4-keycloak-auth` — phase note - `arch-dataflow-mcd-tracker` — auth flow diagram - `plan-mcd-tracker` — parent plan
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ldraney/mcd-tracker-api#4
No description provided.