Companies want their people to sign in with the account they already have: Okta, Microsoft Entra ID, Google Workspace, OneLogin, JumpCloud, Keycloak. All of them speak SAML 2.0, so OpsAPI does too. Each workspace connects its own identity provider (IdP) from Workspace → Single sign-on, and its people choose Sign in with SSO on the login page.
The round trip
- The user types a work email.
GET /auth/saml/discover?email=…finds the workspace's provider from the domain. /auth/saml/:id/loginsends the browser to the IdP with a deflated AuthnRequest (HTTP-Redirect binding), and keeps the request ID in a short-lived signed cookie.- The user signs in at the IdP, with whatever MFA the company requires there.
- The IdP posts a signed SAMLResponse to OpsAPI's assertion consumer service,
/auth/saml/:id/acs. - OpsAPI checks it, finds or creates the account, and sends the browser to the dashboard with a token, the same way Google sign-in does.
Setting it up
Paste the IdP's metadata XML and press Fill in from metadata: OpsAPI reads the entity ID, the sign-in URL and the signing certificate. Then give the IdP the three values on the provider's card:
| OpsAPI shows | The IdP calls it |
|---|---|
| Entity ID (Audience), also the metadata URL | Audience URI, SP Entity ID, Identifier |
| ACS URL | Single sign-on URL, Reply URL, Assertion Consumer Service |
| NameID format: email address | Name ID format; send email, firstName, lastName attributes |
What every response must pass
Signature checking is where SAML implementations go wrong, so OpsAPI does it narrowly. It parses with libxml2 (already in the OpenResty image), refuses any document with a DTD, and never fetches anything. A response is accepted only when all of these hold:
| Check | Stops |
|---|---|
| Signature by the certificate saved for this provider (any certificate inside the message is ignored) | Forged responses |
| The signature sits on the element it signs, references that element's unique ID, and exactly one Assertion exists | Signature-wrapping attacks |
| RSA-SHA256 or stronger; SHA-1 refused | Weak digests |
| Issuer is the IdP; Audience is this workspace's provider; Recipient and Destination are its ACS URL | Responses meant for another service |
| NotBefore / NotOnOrAfter, with 3 minutes of clock skew | Old responses |
| InResponseTo matches the request in the signed cookie | Login CSRF and unsolicited responses |
| Each assertion ID accepted once | Replays |
An IdP vouches for its own workspace only
Accounts in OpsAPI are global and found by email, but an IdP is configured by a workspace owner. Without a limit, an owner who runs their own IdP could assert someone else's address and sign in as them. So:
- Only addresses at the provider's email domains are accepted.
- An existing account that also belongs to another workspace, or is a platform admin, can't sign in through SAML; it uses its password.
- New people get an account and join the workspace with the provider's default role (or must be invited first, if auto-provisioning is off).
- IdP-initiated sign-in (from the IdP's app tile) is off unless the owner turns it on.
Try it
The live demo is connected to a demo IdP: choose Sign in with SSO, type any address at example.com, and you are back in the showcase workspace as a read-only viewer through a real signed SAML response.