Aller au contenu principal

SAML 2.0 Single Sign-On

SyAuth runs as a SAML 2.0 Identity Provider. Customer apps (Service Providers — Salesforce, Slack, Jira, a homegrown internal tool…) can delegate sign-in to SyAuth, receive a signed assertion back, and trust it.

Paired with our SCIM 2.0 server — which handles user and group provisioning — SAML handles authentication. Together they cover the full "SSO + Directory Sync" checklist every enterprise procurement spreadsheet contains.

The V1 implementation is deliberately small and auditable (~250 lines of SAML logic). It covers:

  • SP-initiated SSO via HTTP-Redirect binding on the request and HTTP-POST on the response.
  • RSA-SHA256 signed assertions (enveloped XML-DSig).
  • IdP metadata endpoint for easy integration with admin consoles.
  • Configurable attribute mapping per SP.

Out of V1: encrypted assertions, IdP-initiated deeplinks, Single LogOut, signed AuthnRequest verification. See the bottom of this page for the roadmap.


Configuration (one-time, per deployment)

Generate a long-lived signing keypair for the IdP:

openssl req -x509 -newkey rsa:2048 \
-keyout saml_key.pem -out saml_cert.pem \
-sha256 -days 3650 -nodes \
-subj "/CN=SyAuth IdP"

Expose the PEM strings plus entity and SSO URLs as environment variables:

Env varExampleRequired
SAML_IDP_ENTITY_IDhttps://api.syauth.com/saml/metadata/yes
SAML_IDP_SSO_URLhttps://api.syauth.com/saml/sso/yes
SAML_IDP_CERT_PEMcontents of saml_cert.pemyes
SAML_IDP_KEY_PEMcontents of saml_key.pemyes
SAML_IDP_LOGIN_URL/auth/login/ (default)no

SyAuth will not serve SAML responses until all four are set. The metadata endpoint returns 503 with a clear message so misconfiguration is obvious.


Register a Service Provider

Open SAML SSO in the SyAuth dashboard (/security/saml) and click Register a Service Provider. Fill in:

FieldWhat to put
LabelHuman name like "Salesforce production"
SP Entity IDThe entityID from the customer app's SAML config
ACS URLWhere the app wants us to POST the SAML response
NameID FormatUsually emailAddress
NameID attributeExtsUser field — usually email
Attribute mappingJSON dict: {"email": "email", "firstName": "first_name", "lastName": "last_name"}
Default RelayStateOptional URL to land on after sign-in

The app's admin console will give you the first three. The attribute mapping is the most important knob — it decides what claims your customer's app sees about the user.

Supported ExtsUser field paths in mappings: email, first_name, last_name, display_name (concatenation), id.


How the SSO flow works

┌────────┐   1. unauthenticated   ┌───────────┐
│ End │ ─────────────────────▶ │ Customer │
│ User │ │ App (SP) │
└────────┘ └─────┬─────┘
▲ │
│ 6. redirect to app │ 2. build AuthnRequest, redirect
│ ▼
┌────────┐ ┌───────────────┐
│ App │ ◀─── 5. POST ACS ───── │ Browser │
│ (SP) │ │ │
└────────┘ └─────┬─────────┘
│ 3. GET /saml/sso/?SAMLRequest=…

┌─────────────┐
│ SyAuth IdP │ ── 4. if not logged in,
│ │ stash + redirect to login
└─────────────┘
  1. User visits the customer app without a session.
  2. App builds a SAML AuthnRequest, base64+deflates it, redirects the browser to SyAuth.
  3. SyAuth parses the request, finds the SAMLServiceProvider by Issuer, verifies the AssertionConsumerServiceURL matches what we have registered.
  4. If the user is not authenticated, SyAuth stashes the request in the session and redirects to the hosted login. After a successful login the flow resumes at /saml/sso/finish/.
  5. SyAuth builds a signed <Response> containing a signed <Assertion> about the user, renders an auto-submitting HTML form, and returns it to the browser. The browser POSTs to the app's ACS URL.
  6. The app verifies the signature with the IdP cert (downloaded from /saml/metadata/), and logs the user in.

End users see one brief redirect through SyAuth; in most cases they already have an active SyAuth session and it's invisible.


IdP metadata

GET /saml/metadata/

Returns an EntityDescriptor XML document with:

  • Our entity ID.
  • The signing certificate (base64 body, no PEM headers).
  • Both HTTP-Redirect and HTTP-POST SingleSignOnService endpoints.
  • Supported NameIDFormats.
  • WantAuthnRequestsSigned="false" — we currently accept unsigned AuthnRequests (see roadmap).

Download it and feed it into the customer app's SSO admin page in one click. This is what every modern SAML admin UI supports.


IdP-specific notes

Salesforce

  1. Setup → Identity → Single Sign-On Settings → New from Metadata URL.
  2. Paste https://api.syauth.com/saml/metadata/.
  3. For the SP side, Salesforce will give you an Entity ID and ACS URL — register those in the SyAuth dashboard.

Custom in-house app using python-saml / pysaml2

Point idp.metadata_url at /saml/metadata/ and accept NameID as email. Minimal SP config:

saml_settings = {
"sp": {
"entityId": "https://acme.com/saml",
"assertionConsumerService": {
"url": "https://acme.com/sso/acs",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
},
"idp": {
"entityId": "https://api.syauth.com/saml/metadata/",
"singleSignOnService": {
"url": "https://api.syauth.com/saml/sso/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
},
"x509cert": "<contents of our signing cert>",
},
}

AWS IAM Identity Center

Import the SyAuth metadata as an external IdP; AWS generates the SP entity ID and ACS URL which you paste back into SyAuth's dashboard.


Audit events

Every SAML write or SSO attempt lands in the workspace Audit Log:

EventSeverityWhen
saml_sp_createinfoDeveloper registered a new SP
saml_sp_updateinfoDeveloper changed SP config
saml_sp_deletewarnDeveloper deleted an SP
saml_sso_successinfoSyAuth issued a signed assertion
saml_sso_failurewarnMalformed request, unknown SP, ACS URL mismatch, build error

Every saml_sso_success includes metadata.assertion_id so you can correlate assertions with any incident reported downstream.


Security notes

  • Signed assertions on by default. RSA-SHA256, exclusive canonicalisation. Downstream SPs must verify using the cert from /saml/metadata/.
  • Assertion lifetime is 5 minutes with a 30-second clock-skew tolerance on NotBefore. Short enough to resist replay, wide enough to survive small clock drift.
  • ACS URL is validated against the registered one. Protects against assertion-redirect open-redirect attacks where a malicious AuthnRequest asks us to POST to an attacker-controlled URL.
  • Audience restriction is set to the SP's entityId, so a captured assertion can't be replayed against a different SP.
  • InResponseTo is always set to the AuthnRequest's ID. Downstream SPs that enforce it will reject assertions they didn't request.
  • Issuer lookup is exact-match on SAMLServiceProvider.sp_entity_id. Unknown issuers get an explicit 400.

Developer CRUD API

All require a developer session + IsDeveloper permission. Explicit per-call workspace-membership check — no cross-tenant reads.

  • GET /developer/saml/sps/?workspace_id=<uuid> — list
  • POST /developer/saml/sps/ — create (body fields: workspace_id, name, sp_entity_id, acs_url, name_id_format, name_id_attribute, sign_assertions, attribute_mapping, sp_cert_pem, default_relay_state, is_active)
  • GET /developer/saml/sps/{id}/ — read
  • PUT /developer/saml/sps/{id}/ — partial update, same fields as create
  • DELETE /developer/saml/sps/{id}/ — delete

Roadmap

Already reserved; shipping in later releases:

  • Signed AuthnRequest verification. Today we ignore the signature on inbound requests. When the SP stores its signing cert in sp_cert_pem, we'll verify. Protects against parameter tampering.
  • Encrypted assertions. For SPs that require <EncryptedAssertion> instead of <Assertion>.
  • IdP-initiated SSO. Deep link /saml/idp-init/?sp={id} issues an unsolicited assertion.
  • Single LogOut (SLO). Propagate SyAuth sign-out to every registered SP.
  • Per-SP signing certs. Rotate keys per customer rather than one cert for the whole IdP.
  • Conditional attribute mapping. Map different attributes depending on which group the user belongs to.