User-plane tokens
Exchange a login session for a short-lived token scoped to this service, and how roles map.
The user plane accepts exactly one kind of token: a short-lived exchange token minted by the identity provider (auth.console) with the audience provider-credentials-service. A login/management token — even a valid one — is rejected: the audience discipline is one JWT, one target service.
The flow
- Log in at the identity provider:
POST /v1/auth/login. Accounts belonging to several organizations getstatus: org_selection_requiredand finish withPOST /v1/auth/select-org— the chosen organization becomes the token'sorg_id. - Exchange the session for a service-scoped token:
POST /v1/auth/token/exchangewith{"audience": "provider-credentials-service"}. The result is an access-only JWT (ES256, minutes-lived, no refresh) carryingsub(user UUID),org_id, and the raw organizationroles. - Call this service with
Authorization: Bearer <token>. When the token expires, exchange again — the login session, not this service, is your refresh anchor.
import httpx
auth = httpx.post(f"{AUTH_BASE}/v1/auth/login", json={"email": EMAIL, "password": PASSWORD}).json()
# ... handle org selection / MFA states per the auth service's own docs ...
exchange = httpx.post(
f"{AUTH_BASE}/v1/auth/token/exchange",
headers={"Authorization": f"Bearer {auth['access_token']}"},
json={"audience": "provider-credentials-service"},
).json()
TOKEN = exchange["access_token"]
The identity provider publishes its own developer wiki with the full login state machine; this page covers only what this service needs from it.
What the token must carry
iss/aud— the configured issuer andprovider-credentials-service; anything else is401.sub— the user, a canonical UUID (non-canonical forms are rejected).org_id— optional; when present, a canonical UUID. Absent means no organization context: Organization Credentials answers403, and Credential Status reportsnullfor the org level.roles— the raw organization roles (owner,admin,member,viewer).
How roles map
Two independent mappings read the same roles claim:
- Service roles (
JWT_ROLE_MAP, defaultowner→user, admin→user, member→user): produces the §5.1 role that gates endpoints.viewermaps to nothing — such tokens are401. Elevated roles (provider_admin,auditor) are never derived from org roles by default; granting them is a deliberate deployment decision. - Organization managers (
JWT_ORG_MANAGER_ROLES, defaultowner,admin): checked against the raw roles to gate Organization Credentials — deliberately surviving the flattening above.
So a default deployment reads: every org member is a user; only owners and admins manage the org's credentials.
Dev auth mode
Local development can skip the identity provider entirely: DEV_AUTH_ENABLED=true accepts X-Dev-User-Id, X-Dev-Roles, X-Dev-Org-Id, X-Dev-Org-Roles headers, applying the same UUID and role rules. Requests carrying an Authorization header are always validated as JWTs regardless. Refused in production builds.