External Claim Providers
Pulling claim values from systems Orion does not own
ORION_EXTERNAL_PROVIDER__* variables.
A claim provider is an HTTPS endpoint of yours that Orion calls to obtain claim values it
cannot know itself — entitlements from a billing system, a tier from a CRM, seat counts
from a licence server. Once registered, a scope references it as
ext:<name>, and the value lands in the token.
Configuring one
Manage → OAuth → Providers (/oauth/manage/{org}/providers), then + New Provider.
| Field | Meaning | Default |
|---|---|---|
| Name | How scopes reference it: ext:<name>. Unique per organisation. | — |
| Mode | Synchronous — called during token issuance. Sync → metadata — a background job writes values into user metadata; scopes then read them as meta:<key>. | Synchronous |
| URL | Your endpoint. HTTPS only, and it must satisfy the SSRF guard below. | — |
| Claim allowlist | Space-separated claim names this provider may return. Anything else it sends is discarded. | — |
| Outbound auth | None, API key (sent as Authorization: Bearer), or HMAC-SHA256. | None |
| Secret | The API key or HMAC secret. Encrypted at rest under ORION_CLAIM_PROVIDER_KEK. Never returned by the API once saved. | — |
| Timeout | Milliseconds before the call is abandoned. Synchronous providers sit on the token path — keep it small. | 800 |
| Cache TTL | Seconds a value is reused before calling again. | 300 |
| On failure | Fail open — omit the claim, issue the token anyway. Fail closed — abort issuance. | Fail open |
| Active | Unchecking stops it being called without deleting it. | on |
Wiring it to a token
Registering a provider does nothing on its own. A scope must reference it:
- Register the provider, e.g. named
entitlements. - Edit a scope (Manage → OAuth → Scopes) and add
ext:entitlementsto its Claims field. - Allow that scope on the application.
A token issued for that scope then carries the claim, with the ext: prefix stripped:
{
"sub": "62B3…",
"scope": "openid @acme/billing",
"entitlements": ["reports", "exports"]
}
The contract your endpoint implements
Orion POSTs JSON identifying the user and expects a flat object of claim values back.
// request POST https://provider.example.com/claims Authorization: Bearer <your api key> // when Outbound auth = API key { "sub": "62B3…", "email": "user@acme.com", "org_id": "acme", "client_id": "reports-app" } // response — only allowlisted names are kept { "entitlements": ["reports", "exports"], "tier": "gold" }
With HMAC-SHA256, the request carries a signature over the body computed with the shared secret; verify it before trusting the request. Return promptly — a synchronous provider's latency is added to every token issuance.
Guards you will meet
- SSRF protection. Private and loopback ranges are always refused. Set
ORION_CLAIM_PROVIDER_ALLOWED_HOSTSto permit specific hosts (exact, or.suffix); when it is empty only public HTTPS hosts are allowed. - Reserved claims. A provider can never emit
sub,roles,org_idor any protocol claim — so a compromised provider cannot impersonate a user. Those names are filtered out even if allowlisted. - Secret storage. Saving a secret requires
ORION_CLAIM_PROVIDER_KEK. It is generated automatically if unset; providers can be created without auth, but storing a key needs it.
If a claim does not appear
- Is the name in the provider's allowlist? Values outside it are dropped silently.
- Is it a reserved name? Same outcome, deliberately.
- Does a scope reference
ext:<name>, and was that scope granted? - Is the provider Active, and is the endpoint answering within the timeout?
- Is a stale value cached? Lower the TTL while testing.
- For Sync → metadata providers, the claim is read as
meta:<key>, notext:, and only appears after the background sync has run (ORION_CLAIM_SYNC_INTERVAL_MINUTES, default 15).
https://login.shanecraven.com/oauth/{org}/userinfo resolves through the same pipeline as tokens, so it is the quickest way to test.