Auth and API key management#
The service authenticates callers with simple bearer-token API keys, scoped to a tenant. This page covers operating that system.
For the design (TenantApiKey, validate_api_key, middleware), see Components and ADR-005.
How auth works at runtime#
Caller sends
Authorization: Bearer <key>.The middleware compares
<key>against every entry inAUTH_API_KEYSusingsecrets.compare_digest(constant-time).On match, the request is tagged with the matching
tenant_id(andis_superuserflag). On no match → 401.
GET /v1/health skips auth. Everything else requires it.
API key shape#
AUTH_API_KEYS is a JSON array of objects. Five fields:
Field |
Type |
Notes |
|---|---|---|
|
string |
Unique identifier for this key. Used for logging and rotation; not for auth. |
|
string |
Human-readable label (e.g. |
|
string |
The secret. Stored as |
|
string |
The tenant this key represents. |
|
bool, optional |
Default |
Example:
[
{
"key_id": "k-prd-0",
"name": "crm-production",
"key": "sk-prod-abc123def456",
"tenant_id": "tenant-redcross-nl",
"is_superuser": false
}
]
Production: rotating and adding keys#
In production the JSON is stored as the auth-api-keys secret in Key Vault and loaded via Key Vault references. Use the helper script — never edit the secret by hand:
az login
export AZURE_KEYVAULT="qfa-${ENV}-keyvault" # e.g. qfa-prd-keyvault
# Add a key for a tenant (keeps existing keys)
uv run python3 scripts/update_auth_api_keys.py --add <tenant>
# Replace all keys for a tenant with one new key
uv run python3 scripts/update_auth_api_keys.py --replace <tenant>
# Remove all keys for a tenant
uv run python3 scripts/update_auth_api_keys.py --remove <tenant>
The script prints the generated key to stdout. Copy it and share it with the tenant immediately — Key Vault stores it, but you cannot retrieve it again from the CLI in clear text.
Key rotation steps:
--add <tenant>— adds a new key for the tenant alongside the old one.Give the new key to the tenant.
Wait until you’ve verified the tenant is using the new key (look for the new
key_idin logs).--replace <tenant>with their now-confirmed new key, or--remove <tenant>plus--addif you want a clean cut.
App Service picks up Key Vault changes within a few minutes. To force an immediate refresh, restart the App Service from the Azure portal.
Local dev#
For local development, set AUTH_API_KEYS directly in .env or your shell:
export AUTH_API_KEYS='[{"key_id":"local-0","name":"local","key":"dev-key","tenant_id":"local","is_superuser":true}]'
is_superuser=true is convenient locally because /v1/usage/all then works.
Superuser scope#
is_superuser=true only grants access to the cross-tenant GET /v1/usage/all route. It does not elevate access to other tenants’ analyze/summarize/assign-codes endpoints — those are scoped purely by tenant_id derived from the matching key.
What’s not supported (yet)#
Per-key revocation by
key_idalone (rotation goes through the script’s--replace/--removeper-tenant flow).Per-key rate limits.
Non-bearer auth schemes (OAuth, mTLS).
These are deliberate omissions for v1, not bugs.