OIDC federation
Federation lets an enterprise customer sign in with their own identity provider. Your application still talks to one authorization server; the platform brokers the login to the customer’s IdP behind it, verifies what comes back, and provisions the user into the tenant.
Your integration does not change. You call /authorize exactly as in the
standard flow — the brokering happens on the other side.
Create a connection
Section titled “Create a connection”Requires sso:write. With a discovery URL, the endpoints are fetched for you:
POST /v1/organizations/org_01J…/sso/connectionsAuthorization: Bearer sk_live_…
{ "type": "oidc", "name": "Acme corporate IdP", "oidc_discovery_url": "https://idp.acme.com/.well-known/openid-configuration", "oidc_client_id": "…", "oidc_client_secret": "…", "domains": ["acme.com"]}{ "id": "conn_…", "organization_id": "org_01J…", "type": "oidc", "name": "Acme corporate IdP", "status": "active", "oidc_issuer": "https://idp.acme.com", "domains": ["acme.com"]}Without discovery, supply oidc_authorization_endpoint, oidc_token_endpoint, and oidc_jwks_uri
explicitly. The client secret is stored encrypted and never returned.
The redirect URI to register at the customer’s IdP is the tenant’s SSO callback:
https://acme.oauth.work/sso/callbackCustomers can create their own connection through an Admin Portal link rather than sending you their client secret — which is usually what their security team prefers.
Routing a login to it
Section titled “Routing a login to it”Two ways to reach the connection:
Explicitly, by org:
GET /authorize?…&organization=org_01J…Implicitly, by email domain:
GET /authorize?…&login_hint=ada@acme.comThe domain resolves to the org through a verified domain, and that org’s active connection takes over. This is what makes a single sign-in box work for many enterprise customers: the user types their email, and they land at their own IdP.
If the client is itself org-bound, its org is used and organization must match it — otherwise the
request fails with invalid_request.
What happens behind the scenes
Section titled “What happens behind the scenes”- The platform redirects to the IdP’s authorization endpoint with
scope=openid email profile, anonce, and PKCE on the brokered leg. IdPs that do not support PKCE ignore the extra parameters harmlessly. - The IdP returns to
/sso/callback. - The state is consumed — one callback per brokered flow, so a replayed callback fails.
- The code is exchanged and the returned
id_tokenis verified against the IdP’s JWKS. - The user is provisioned just-in-time into the tenant, or matched to an existing record.
- The original
/authorizeflow resumes, and your application receives its code.
The result reaches you as an ordinary authorization code. The ID token you verify is signed by the tenant’s key, not the customer’s IdP — you verify one issuer regardless of how many customers federate.
Attribute mapping
Section titled “Attribute mapping”Claim names differ between providers. Override the defaults per connection:
{ "attribute_mapping": { "email": "mail", "name": "displayName", "given_name": "givenName", "family_name": "surname" }}Unmapped attributes fall back to the standard OIDC claim names. sub from the IdP is always stored
as the user’s external id, so a user whose email address changes at the IdP still matches the same
record.
JIT provisioning
Section titled “JIT provisioning”A user arriving through federation is created in the tenant on first login, with the member
role. The same user records back
SCIM provisioning — both converge on (external_id, connection_id), so a directory that
syncs users and an IdP that federates logins do not produce two accounts for one person.
Writes an sso_login audit event; connection creation writes sso_connection_created.
Listing connections
Section titled “Listing connections”GET /v1/organizations/org_01J…/sso/connections # sso:readReturns id, type, name, status, issuer or SAML entity id, and domains. Secrets and certificates are never included.
SAML instead
Section titled “SAML instead”For customers whose identity provider speaks SAML rather than OIDC, the same connection resource
takes type: "saml". See SAML 2.0.