API authentication and JWT validation¶
Every FlexGalaxy.AI API call carries a JWT bearer token issued by DotID. This page explains how the tokens are obtained, which realms issue them, and how your service should validate them.
Realms¶
DotID uses three realm types. Each one issues its own JWTs from its own signing key.
Realm |
Who lives there |
Example issuer claim |
|---|---|---|
|
Platform admins (FlexGalaxy operators), account-root users, and platform-level workforce |
|
|
Per-account service users |
|
|
Per-organization Identity Center personas (SSO) |
|
Accounts get a fresh acc-* realm and a fresh idc-* realm at account provisioning time. The realms are isolated — a user in one acc-* realm cannot sign in to another.
Tenant binding: account_id and user_type claims¶
Access tokens issued by the flexgalaxy realm carry two claims that describe the
authenticated subject’s tenant binding: account_id and user_type.
{
"iss": "{{ dotid_auth_prod_flexgalaxy_realm }}",
"sub": "9e0f6f96-2f57-4f49-84ef-8d4d12d4f5e1",
"account_id": "11111111-1111-1111-1111-111111111111",
"user_type": "root"
}
account_id— the DotID account UUID the subject is bound to. It is emitted for both account-root users and workforce members of an account. It means “this user belongs to this tenant” — nothing more. It is not the account name and not anacc-*realm name.user_type— how the subject is bound to that account:root— the account’s root user.workforce— a workforce member of the account.
Rules:
Derive account-root status strictly from
user_type == "root". Never infer root from the mere presence ofaccount_id— a workforce user also carriesaccount_idbut is not a root. Treating “hasaccount_id” as “is root” will silently promote every workforce member to root-equivalent.Use
account_idto resolve the caller’s tenant (for per-request scoping and when building PDP principal context). Do not derive account identity from email, username, or realm name.Unbound
flexgalaxyusers (e.g. platform admins) receive neither claim. Ifaccount_idis absent, the caller is not bound to a tenant; ifuser_typeis absent or is notroot, do not treat the caller as an account root. Route the request through the appropriate platform-admin, workforce, service-user, or workload-identity path instead.
These claims are identity context, not an authorization grant. They tell a service which tenant the validated token belongs to and whether the subject is that account’s root; they do not say the caller may perform a particular service action. A first-party service must still call the DotID PDP for any account/resource operation that is policy controlled.
Validating JWTs your service receives¶
FlexGalaxy.AI’s platform convention is that every service accepts tokens from any of the three realm types. A service that rejects a valid token because it came from an idc-* realm rather than flexgalaxy breaks the cross-realm composition the platform depends on.
Concretely, your JWT validator must:
Extract the
issclaim from the token header / payload.Verify the issuer URL is one of:
{{ dotid_auth_prod_realm_base }}/flexgalaxyor{{ dotid_auth_prod_realm_base }}/acc-*or{{ dotid_auth_prod_realm_base }}/idc-*. Reject any other issuer.Fetch the realm’s JWKS from
<issuer>/protocol/openid-connect/certs. Cache the keys (DotID rotates them on a schedule; respectCache-Controlheaders).Verify the signature against the JWKS, matching on the token’s
kidheader.Verify the standard claims:
exp(not expired),nbf(if present, not before),iat(sanity-check against clock skew),aud(matches your service’s audience identifier).Verify any service-specific claims your authorization logic depends on (e.g.
realm_access.roles,groups).
DotID’s own services implement exactly this pattern, and two acceptable approaches exist:
Open acceptance — accept any realm whose JWKS endpoint resolves and serves valid keys.
Explicit allowlist — accept
flexgalaxyplus a prefix match foracc-*/idc-*, rejecting anything else. Slightly more secure (rejects rogue realms even if they reach the auth server) at the cost of a config update when a new realm category is added. Some services choose this stricter allowlist form.
Pick one approach per service and document it. Mixing the two within one service leads to subtle authorization gaps.
Common errors¶
Status |
Meaning |
What to check |
|---|---|---|
|
No bearer token, or the token signature did not validate. |
Verify |
|
The token was valid but the PDP returned |
Inspect the response body — it includes the |
|
Invalid principal object, action, or resource FRN. |
See FRN format for the canonical resource syntax and parsers. |
|
API versioning mismatch — the request was sent to a deprecated path. |
Public developer APIs use |
Federated workload tokens¶
DotID also issues access tokens through RFC 8693 token exchange for workload identities federated from external OIDC issuers (GitHub Actions, GitLab CI). Validation at your service is identical to validating any other DotID-issued JWT — the same realm JWKS, the same iss / aud / exp rules. The only difference is the token’s sub is a DotID workload identity ID rather than a human or service-user ID, and the token carries an extra federated_subject custom claim of shape {issuer, sub} recording the originating CI subject for audit correlation.
Treat the federated_subject claim as audit metadata only — never use it for authorization decisions. The aud, sub, and scope claims are the authorization surface, just like every other DotID-issued token.
For the exchange contract (the endpoint your CI calls to obtain this token, not the validation your service does to consume it), see workload-federation.md.