Personal Access Tokens (PATs)
Hegemony issues long-lived, opaque Personal Access Tokens (PATs) for authenticating scripts, Postman collections, and the bundled Swagger UI's Authorize dialog. PATs are an alternative to Keycloak-issued JWTs intended for non-interactive use; humans should still sign in through SSO.
PATs are admin-only to issue and revoke. This is enforced centrally by the RBAC middleware (the
api_token:manageaction inapps/api/auth/actions.yaml).
Token format
hgm_pat_<43 url-safe base64 chars>- Prefix:
hgm_pat_(constant, makes tokens easy to recognize in logs/secret scanners). - Body: 32 bytes of
secrets.token_urlsafeentropy. - The plaintext is shown exactly once when the token is created.
- Only an HMAC-SHA-256 digest is persisted (
personal_access_tokens.token_hash) along with the first 12 chars of the plaintext as a non-secretdisplay_prefix.
Hashing
apps/api/services/api_tokens.py computes:
token_hash = HMAC-SHA-256(key=HEGEMONY_PAT_PEPPER, msg=plaintext)If HEGEMONY_PAT_PEPPER is unset, the service falls back to plain SHA-256. Production deployments must set HEGEMONY_PAT_PEPPER — packages/core/ settings.py raises at boot if the variable is empty and HEGEMONY_ENVIRONMENT is prod/production.
Configuration
| Env var | Default | Meaning |
|---|---|---|
HEGEMONY_PAT_PEPPER | empty | HMAC key. Required in production. |
HEGEMONY_PAT_MAX_LIFETIME_DAYS | 365 | Maximum allowed expires_in_days; 0 disables the cap. When the caller omits expires_in_days and the cap is non-zero, tokens default to the cap. |
REST API
All endpoints are under /api-tokens (reachable through the UI proxy as /api/api-tokens). All routes require the admin realm role.
POST /api-tokens
Issue a new token. Request body:
{
"name": "Postman dev",
"scopes": ["viewer"],
"expires_in_days": 30
}Response (201):
{
"id": "…",
"user_username": "alice",
"name": "Postman dev",
"display_prefix": "hgm_pat_xY1",
"scopes": ["viewer"],
"expires_at": "2026-06-25T12:00:00Z",
"last_used_at": null,
"created_at": "2026-05-26T12:00:00Z",
"revoked_at": null,
"issued_by_username": "admin",
"token": "hgm_pat_xY1…"
}The plaintext is in token. Copy it immediately — the server cannot recover it after this response is sent.
GET /api-tokens?include_revoked=&user_username=
List tokens. Excludes revoked tokens by default. Filter by owner username.
DELETE /api-tokens/{token_id}
Revoke a single token. Returns {"revoked": 1} on success or 404 if unknown. Repeating the call against an already-revoked token returns {"revoked": 0}.
DELETE /api-tokens/by-user/{username}
Bulk-revoke all active tokens owned by username. Returns the count of rows affected.
Scopes
The allowed scope values mirror the Keycloak realm roles consumed by the RBAC policy:
adminoperatorapproverauditorviewer
A PAT's scopes become the bearer's realm_roles for the duration of the request. Issuing an admin-scoped PAT effectively grants full API access — use sparingly.
Defense in depth
- PAT cannot mint or revoke PATs. The RBAC middleware sets
request.state.auth_method = "pat"for PAT-authenticated requests; theapi_tokensrouter refuses to act on those calls (HTTP 403). Only JWT sessions can manage tokens. - Constant prefix makes leaked tokens easy to grep for in logs and to detect with secret-scanning tools.
- Last-used throttling.
last_used_atis updated at most every 60 seconds per token to avoid a DB write per request, with an in-process LRU cap.
Using a PAT
Swagger UI
- Open
/api/docs(link is on the Settings page). - Click Authorize.
- Paste the token value (just the
hgm_pat_…string, noBearerprefix). - Endpoints execute against
/api/<path>through the UI nginx proxy.
curl / scripts
curl -H "Authorization: Bearer hgm_pat_xxxx…" \
https://hegemony.example.com/api/runsPostman
Set the request Authorization type to Bearer Token and paste the token.
Lifecycle and rotation
- Tokens are revocable at any time from Settings → API Tokens.
- Rotate by issuing a new token first, switching consumers, then revoking the old token.
- For a compromised owner account, use
DELETE /api-tokens/by-user/{username}to revoke everything they hold.
Known limitations
- Owner usernames are free-text; they are not validated against Keycloak. This keeps the feature self-contained but means typos go unnoticed.
- PATs do not surface in Keycloak —
last_used_at, audit events, and revocation are tracked entirely in the Hegemony DB. - There is no per-token rate limiting.