External Claim Providers

Pulling claim values from systems Orion does not own

Back to Documentation
What this is not. These providers supply claim values, not identity. For signing users in with Microsoft (Entra ID), see External Authentication — a separate feature configured with 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.

FieldMeaningDefault
NameHow scopes reference it: ext:<name>. Unique per organisation.
ModeSynchronous — called during token issuance.
Sync → metadata — a background job writes values into user metadata; scopes then read them as meta:<key>.
Synchronous
URLYour endpoint. HTTPS only, and it must satisfy the SSRF guard below.
Claim allowlistSpace-separated claim names this provider may return. Anything else it sends is discarded.
Outbound authNone, API key (sent as Authorization: Bearer), or HMAC-SHA256.None
SecretThe API key or HMAC secret. Encrypted at rest under ORION_CLAIM_PROVIDER_KEK. Never returned by the API once saved.
TimeoutMilliseconds before the call is abandoned. Synchronous providers sit on the token path — keep it small.800
Cache TTLSeconds a value is reused before calling again.300
On failureFail open — omit the claim, issue the token anyway.
Fail closed — abort issuance.
Fail open
ActiveUnchecking stops it being called without deleting it.on
Fail open vs fail closed is a real decision. Fail open means a provider outage silently issues tokens missing that claim — if a resource server treats a missing entitlement as "no access", users lose functionality with no error anywhere. Fail closed means the same outage stops login entirely. Choose per provider, deliberately.

Wiring it to a token

Registering a provider does nothing on its own. A scope must reference it:

  1. Register the provider, e.g. named entitlements.
  2. Edit a scope (Manage → OAuth → Scopes) and add ext:entitlements to its Claims field.
  3. 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_HOSTS to 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_id or 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>, not ext:, 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.

Scopes & Claims All Documentation