Skip to content

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.

Give these to the customer’s identity provider administrator:

FieldValue
Entity IDhttps://acme.oauth.work/saml/metadata
ACS URLhttps://acme.oauth.work/saml/acs
BindingHTTP-POST
NameID formatemailAddress
Assertions signedRequired
AuthnRequests signedNot required

The same values are served as metadata XML:

GET https://acme.oauth.work/saml/metadata

An Admin Portal session returns sp_entity_id and acs_url directly, so the customer’s admin can copy them without you relaying anything.

Requires sso:write. The easiest path is to import the IdP’s metadata rather than transcribing fields:

POST /v1/organizations/org_01J…/sso/connections
Authorization: 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.

/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.

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:

CheckRequirement
Signing certificateMatches the certificate pinned on the connection
DestinationMatches the ACS URL
AudienceMatches the SP entity id
RecipientMatches the ACS URL
ConditionsWithin NotBefore / NotOnOrAfter
InResponseToMatches 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.

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.
  • RelayState supplied 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.

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.

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.