SAML 2.0
SAML federation works the same way as OIDC federation — a connection on the
organization, brokered at /authorize — with the mechanics of SAML underneath.
Service provider details
Section titled “Service provider details”Give these to the customer’s identity provider administrator:
| Field | Value |
|---|---|
| Entity ID | https://acme.oauth.work/saml/metadata |
| ACS URL | https://acme.oauth.work/saml/acs |
| Binding | HTTP-POST |
| NameID format | emailAddress |
| Assertions signed | Required |
| AuthnRequests signed | Not required |
The same values are served as metadata XML:
GET https://acme.oauth.work/saml/metadataAn Admin Portal session returns sp_entity_id and acs_url directly, so the
customer’s admin can copy them without you relaying anything.
Create a connection
Section titled “Create a connection”Requires sso:write. The easiest path is to import the IdP’s metadata rather than transcribing
fields:
POST /v1/organizations/org_01J…/sso/connectionsAuthorization: Bearer sk_live_…
{ "type": "saml", "name": "Acme SAML", "saml_idp_metadata_url": "https://idp.acme.com/app/metadata", "domains": ["acme.com"]}The entity id, SSO URL, and signing certificate are extracted from the EntityDescriptor. You can
paste the XML directly as saml_idp_metadata_xml instead — useful when the metadata URL is inside
the customer’s network.
Manual configuration also works, and any field you supply explicitly wins over the imported value:
{ "type": "saml", "name": "Acme SAML", "saml_idp_entity_id": "https://idp.acme.com/…", "saml_idp_sso_url": "https://idp.acme.com/sso/saml", "saml_idp_cert": "-----BEGIN CERTIFICATE-----\nMIID…"}All three are required for a SAML connection; a connection missing any of them is rejected at creation rather than failing at first login.
The flow
Section titled “The flow”/authorize with organization or a login_hint on a
verified domain redirects the browser to the IdP with an
AuthnRequest over the HTTP-Redirect binding. The IdP posts the signed response back to /saml/acs,
the assertion is verified, the user is provisioned, and the original OAuth flow resumes.
Assertion verification
Section titled “Assertion verification”Signature checking is implemented from scratch on WebCrypto — exclusive canonicalization, RSA-SHA256
verification, and the enveloped-signature reference digest — with no node:crypto on the path. It
is cross-checked in tests against an independent XML-DSig implementation: assertions that library
signs must verify, and tampered assertions and bad signatures must be rejected.
Beyond the signature, the assertion must satisfy:
| Check | Requirement |
|---|---|
| Signing certificate | Matches the certificate pinned on the connection |
Destination | Matches the ACS URL |
Audience | Matches the SP entity id |
Recipient | Matches the ACS URL |
| Conditions | Within NotBefore / NotOnOrAfter |
InResponseTo | Matches the pending request, for SP-initiated flows |
A response failing any of these returns saml_failed without detail. The specifics go to the
platform log rather than to the caller — a verification endpoint that explains exactly which check
failed is a tool for crafting an assertion that passes.
IdP-initiated SSO
Section titled “IdP-initiated SSO”Unsolicited responses — the user clicking the app tile in their IdP’s dashboard — are supported, and carry their own rules:
- The connection is resolved from the assertion’s
Issuer, then verified against that connection’s pinned certificate. - The assertion must not carry
InResponseTo. A solicited assertion only counts against the pending flow that requested it, so it cannot be replayed as an unsolicited one. - If two organizations have pinned the same IdP entity id, the request fails closed rather than guessing which tenant it belongs to.
RelayStatesupplied by the IdP is deliberately not followed. An IdP-supplied redirect URL is an open redirect; the user lands signed in at the console instead.
Certificate renewal
Section titled “Certificate renewal”IdP signing certificates expire, typically on a yearly cadence, and an expired certificate is the most common way an established SAML integration breaks.
The replacement is validated before it displaces the pinned one: it must parse as a certificate and be within its validity window. A malformed or already-expired certificate is rejected rather than stored, so renewal cannot itself break the connection.
The customer’s admin can do this themselves through an Admin Portal link with
intent: "sso". Renewal writes a saml_cert_renewed audit event.
Provisioning
Section titled “Provisioning”Users arriving by SAML are provisioned just-in-time and converge on the same records as
SCIM and OIDC federation, keyed by external id and connection. Successful logins write
sso_login.