PH0.4 — Keycloak auth: JWKS bearer validation, require_role, GET /me, LDAP smoke test #7

Closed
opened 2026-09-11 09:02:08 +00:00 by bart · 2 comments
Collaborator

Parent

Row PH0.4 in docs/12-implementation-plan.md (branch, owned paths and doc section are defined there).

What to build

API requests carry a Keycloak bearer token that is validated against the realm's JWKS with an audience check; endpoints declare the roles they need; GET /me returns the caller's subject, username, roles and locale. A compose-level smoke test proves the LDAP group → realm role mapping for all five users.

Acceptance criteria

  • A locally signed token with the right audience passes; wrong audience, expired or bad signature yield a 401 problem-details response
  • require_role returns 403 for a caller lacking the role
  • GET /me returns sub, preferred_username, roles and locale
  • Smoke test logs in as each LDAP user via Keycloak and asserts /me shows exactly that user's role

Blocked by

  • #3 — Compose base: db, LDAP, Keycloak realm, nginx, OSRM
  • #4 — API skeleton: core, router discovery, healthz, db + audit hook, alembic 0001, CLI
## Parent Row **PH0.4** in `docs/12-implementation-plan.md` (branch, owned paths and doc section are defined there). ## What to build API requests carry a Keycloak bearer token that is validated against the realm's JWKS with an audience check; endpoints declare the roles they need; `GET /me` returns the caller's subject, username, roles and locale. A compose-level smoke test proves the LDAP group → realm role mapping for all five users. ## Acceptance criteria - [x] A locally signed token with the right audience passes; wrong audience, expired or bad signature yield a 401 problem-details response - [x] `require_role` returns 403 for a caller lacking the role - [x] `GET /me` returns `sub`, `preferred_username`, roles and `locale` - [x] Smoke test logs in as each LDAP user via Keycloak and asserts `/me` shows exactly that user's role ## Blocked by - #3 — Compose base: db, LDAP, Keycloak realm, nginx, OSRM - #4 — API skeleton: core, router discovery, healthz, db + audit hook, alembic 0001, CLI
Owner

Starting PH0.4 on branch feature/auth-keycloak.

Owns (per docs/12-implementation-plan.md row PH0.4): backend/src/polaris/api/auth.py, backend/src/polaris/api/routers/me/**, backend/tests/**/auth*.

Note: main did not yet have the PH0.2 API skeleton merged -- PR #51 merged feature/api-skeleton into develop, not main, so backend/ was missing from main. Branched from origin/main and merged origin/develop in (disjoint paths, no conflicts) to get backend/polaris.api/polaris.db to build on. Flagging this in case develop -> main needs a separate sync; my PR still targets main per the ticket instructions.

Delivering: JWKS-validated bearer auth (polaris.api.auth) against the realm from infra/keycloak/realm-polaris.json, aud check, require_role(...) dependency, GET /me (sub, preferred_username, roles, locale), a unit test with a locally-signed token, and a compose-level smoke test logging in as each of the 5 LDAP users and asserting /me's role.

Starting PH0.4 on branch `feature/auth-keycloak`. Owns (per docs/12-implementation-plan.md row PH0.4): `backend/src/polaris/api/auth.py`, `backend/src/polaris/api/routers/me/**`, `backend/tests/**/auth*`. Note: `main` did not yet have the PH0.2 API skeleton merged -- PR #51 merged `feature/api-skeleton` into `develop`, not `main`, so `backend/` was missing from `main`. Branched from `origin/main` and merged `origin/develop` in (disjoint paths, no conflicts) to get `backend/polaris.api`/`polaris.db` to build on. Flagging this in case `develop` -> `main` needs a separate sync; my PR still targets `main` per the ticket instructions. Delivering: JWKS-validated bearer auth (`polaris.api.auth`) against the realm from `infra/keycloak/realm-polaris.json`, `aud` check, `require_role(...)` dependency, `GET /me` (`sub`, `preferred_username`, roles, `locale`), a unit test with a locally-signed token, and a compose-level smoke test logging in as each of the 5 LDAP users and asserting `/me`'s role.
Owner

PR opened: #56 (feature/auth-keycloak -> main). All four acceptance criteria checked above.

Note on the earlier main/develop split I flagged when starting: while this was in flight, main picked up a merge of develop and retired the two-branch gitflow in favour of a single trunk (see updated CLAUDE.md/docs/10-dev-workflow.md) -- so that concern is resolved upstream now. I merged the fresh main into this branch (clean, doc-only conflict-free merge) so the PR is mergeable.

ruff, ruff format --check, mypy --strict and the full pytest suite (46 tests) are green. The LDAP smoke test (backend/tests/integration/api/auth_smoke_test.py, marker smoke) was run against a real docker compose -f infra/compose/docker-compose.yml up db openldap ldap-init keycloak in this session -- all five bootstrap users (admin, planner, technician, viewer, ingest) log in via polaris-web's PKCE flow and GET /me returns exactly their own realm role. Stack torn down afterwards.

Not merging myself per the workflow -- ready for review.

PR opened: https://git.xor.nu/patrick/Polaris/pulls/56 (`feature/auth-keycloak` -> `main`). All four acceptance criteria checked above. Note on the earlier `main`/`develop` split I flagged when starting: while this was in flight, `main` picked up a merge of `develop` and retired the two-branch gitflow in favour of a single trunk (see updated `CLAUDE.md`/`docs/10-dev-workflow.md`) -- so that concern is resolved upstream now. I merged the fresh `main` into this branch (clean, doc-only conflict-free merge) so the PR is mergeable. `ruff`, `ruff format --check`, `mypy --strict` and the full `pytest` suite (46 tests) are green. The LDAP smoke test (`backend/tests/integration/api/auth_smoke_test.py`, marker `smoke`) was run against a real `docker compose -f infra/compose/docker-compose.yml up db openldap ldap-init keycloak` in this session -- all five bootstrap users (`admin`, `planner`, `technician`, `viewer`, `ingest`) log in via `polaris-web`'s PKCE flow and `GET /me` returns exactly their own realm role. Stack torn down afterwards. Not merging myself per the workflow -- ready for review.
patrick self-assigned this 2026-09-11 11:44:44 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
patrick/Polaris#7
No description provided.