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

SP-initiated SAML sign-in: dashboard, OpsAPI, the identity provider and backDashboardSign in with SSOwork emailOpsAPI/auth/saml/:id/loginIdentity providerOkta, Entra ID, …1. discover by domain2. AuthnRequest3. user signs inSigned responseHTTP-POSTOpsAPI ACSverify signaturecheck audience, time,request, replay5. token4. SAMLResponseDashboard/auth/callback
Service-provider-initiated sign-in. The browser carries every message; OpsAPI and the IdP never talk directly.
  1. The user types a work email. GET /auth/saml/discover?email=… finds the workspace's provider from the domain.
  2. /auth/saml/:id/login sends the browser to the IdP with a deflated AuthnRequest (HTTP-Redirect binding), and keeps the request ID in a short-lived signed cookie.
  3. The user signs in at the IdP, with whatever MFA the company requires there.
  4. The IdP posts a signed SAMLResponse to OpsAPI's assertion consumer service, /auth/saml/:id/acs.
  5. 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 showsThe IdP calls it
Entity ID (Audience), also the metadata URLAudience URI, SP Entity ID, Identifier
ACS URLSingle sign-on URL, Reply URL, Assertion Consumer Service
NameID format: email addressName 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:

CheckStops
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 existsSignature-wrapping attacks
RSA-SHA256 or stronger; SHA-1 refusedWeak digests
Issuer is the IdP; Audience is this workspace's provider; Recipient and Destination are its ACS URLResponses meant for another service
NotBefore / NotOnOrAfter, with 3 minutes of clock skewOld responses
InResponseTo matches the request in the signed cookieLogin CSRF and unsolicited responses
Each assertion ID accepted onceReplays

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.