---
title: "Deferred: Keycloak/JWT extension of access control"
description: "Deferred Keycloak/JWT hybrid extension of vxData access control — design, parked PRs, and the order to reopen them."
image: "https://docs.virdx.dev/img/virdx-social-card.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.virdx.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Deferred: Keycloak/JWT extension of access control

v1 runs on static tokens only (`API_PRINCIPALS`). The Keycloak path was built,
tested, and deliberately parked (2026-07-14) as closed draft PRs with branches
kept — extending is "reopen + rebase", not redesign.

## Design (already validated)

The resolver in `api/auth.py` accepts one credential kind today. The hybrid was:
same `Authorization: Bearer` header; a credential with two dots is verified as a
Keycloak JWT (PyJWT + cached JWKS, RS256, `iss`/`aud=vxdata-api`/`exp`), then
claims map to the same `Policy`: `groups` claim via `group_grants`, `azp` via
`client_grants`. Unknown claims = empty policy (authenticated, reads nothing).
Config lives in an optional `oidc` block inside `API_PRINCIPALS`:

```json
{"tokens": {...}, "oidc": {"issuer": ".../realms/virdx", "audience": "vxdata-api",
 "group_grants": {"vxdata-admins": [...], "vxdata-users": [...]},
 "client_grants": {"some-bot": [...]}}}
```

Keycloak stays pure authentication; permissions never leave vxData config.

## The parked pieces (reopen in this order)

1. **API resolver** — mono PR #236 (branch `feat/vxdata-oidc-resolver`): JWT
   verification + claims mapping + per-request principal logging; fake-JWKS
   test matrix (expired/wrong-aud/wrong-iss/tampered). Dormant without the
   `oidc` block, so it can merge any time.
2. **Realm clients** — infra_ansible PR #75 (`felix/vxdata-keycloak-clients`):
   `vxdata-cli` public client with device grant, `vxdata-api` audience-only
   client, audience mappers on `dashboard`/`vxdata-cli`, template `attributes`
   passthrough. Applies on Keycloak restart; coordinate with anything touching
   the `dashboard` client (e.g. old #59).
3. **Groups** — infra_iam PR #28: `vxdata-admins` (felix, moritz) /
   `vxdata-users` (eng roster). Propagates via auth-sync -> FreeIPA -> LDAP
   mapper (~5 min; tokens pick groups up on next login).
4. **Human CLI login** — mono #237's dropped commit (`vxdata login`, device
   grant, token cache/refresh in `~/.config/vxdata/`, transport fallback):
   recover from the #237 branch history.
5. **Dashboard PKCE** — mono PR #238 (`feat/dashboard-keycloak-login`):
   oidc-spa + fetch-wrapper bearer + dashboard-api user-token forwarding.
   Known issue to fix on reopen: `VITE_OIDC_ISSUER` is baked at build time but
   one image serves staging+prod — needs runtime injection (the
   `window.__APP_BASE__` pattern). Also add the staging redirect URI in #75.

## Until then

Humans and bots use static tokens (`VXDATA_TOKEN`); audit granularity is the
token subject. When enforcement reaches the dashboard: reopen #238, or interim
edge-injected static token (identity collapses to "dashboard").

Source: https://docs.virdx.dev/knowledge/wiki/workstreams/infrastructure/vxdata-keycloak-extension/index.mdx
