SAML 2.0 Service Provider

Tetrapus acts as a SAML 2.0 SP. Users hit the login page, are redirected to the configured enterprise Identity Provider (IdP), authenticate there, and the IdP returns a signed SAMLResponse to the Tetrapus ACS endpoint that mints a session.

Why SAML

Most regulated enterprises and government tenants standardise on SAML 2.0 for browser SSO. It pre-dates OIDC, has deeper attribute-mapping conventions, and is the only protocol some compliance auditors accept for sensitive workloads. Tetrapus supports it for parity with the rest of the federation surface.

SP-initiated flow

The user always starts at Tetrapus. They pick their IdP, get a 302 to the IdP's SSO endpoint with a SAMLRequest in the query string, authenticate (and complete MFA) at the IdP, and are bounced back via POST to the Tetrapus Assertion Consumer Service (ACS) URL with a signed SAMLResponse.

graph TD USER["User"] --> LOGIN["Tetrapus /login"] LOGIN -->|click 'Sign in with Acme IdP'| START["GET /api/v1/saml/{id}/sso-start"] START -->|302 Location: IdP SSO + SAMLRequest| IDP["Acme IdP\n(Okta / Azure AD / ADFS)"] IDP -->|user authenticates + MFA| SIGN["IdP signs assertion\n(RSA-SHA256)"] SIGN -->|HTTP-POST SAMLResponse| ACS["POST /api/v1/saml/{id}/acs"] ACS --> VERIFY["Verify signature, audience, expiry"] VERIFY --> MAP["Map NameID + attributes via AttributeMapping"] MAP --> LOOKUP["Find pre-provisioned user (saml_subject)"] LOOKUP --> SESSION["Mint Tetrapus session cookie"] SESSION -->|302 to RelayState| APP["Tetrapus UI"]

IdP setup walkthrough

Steps are generic across IdPs (Okta, Azure AD, ADFS, Keycloak). Substitute terminology as needed.

  1. Register Tetrapus as an SP. In the IdP admin console, create a new SAML 2.0 application. Set the SP entity ID to the value Tetrapus exposes at /api/v1/saml/{id}/metadata.
  2. Configure the ACS URL. Point the IdP at https://<your-tetrapus>/api/v1/saml/{id}/acs. Binding: HTTP-POST.
  3. Upload the IdP signing certificate into the Tetrapus provider record (x509_cert_pem field). Tetrapus verifies every assertion against this exact key.
  4. Map attributes. Configure the IdP to release at minimum an email attribute. Optionally release given_name, family_name, groups.
  5. Pre-provision users. Either via SCIM (auto) or by setting saml_subject on each user record manually. JIT user creation is not enabled by default.
  6. Test SP-initiated start by visiting /api/v1/saml/{id}/sso-start directly in a browser.

saml_providers schema

SQL
CREATE TABLE saml_providers (
    id              TEXT PRIMARY KEY,            -- ULID
    org_id          TEXT NOT NULL REFERENCES orgs(id),
    name            TEXT NOT NULL,               -- "Acme Okta"
    entity_id       TEXT NOT NULL,               -- IdP entity ID
    sso_url         TEXT NOT NULL,               -- IdP SingleSignOnService
    slo_url         TEXT,                        -- Optional SingleLogoutService
    x509_cert_pem   TEXT NOT NULL,               -- IdP signing cert
    name_id_format  TEXT NOT NULL DEFAULT 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
    attr_mapping    TEXT NOT NULL,               -- JSON AttributeMapping
    enabled         INTEGER NOT NULL DEFAULT 1,
    created_at      TEXT NOT NULL,
    updated_at      TEXT NOT NULL,
    UNIQUE (org_id, name)
);

AttributeMapping

Tells Tetrapus which SAML attribute to read for each user field. JSON-serialised in the attr_mapping column. The defaults below work for Okta and Azure AD's standard claim sets.

JSON
{
  "email":      "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
  "given_name": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
  "family_name":"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
  "groups":     "http://schemas.xmlsoap.org/claims/Group",
  "name_id_as_subject": true
}

NameID & Single Logout

The NameID from the assertion is stored on the user record as saml_subject and is the primary lookup key on subsequent logins — the email attribute is only used at provisioning time. Single Logout (SLO) is best-effort: if the IdP defines a slo_url, Tetrapus will POST a LogoutRequest there when the user signs out, then revoke the local session regardless of the IdP response.

Status — Hardening notes

Tetrapus verifies: RSA-SHA256 signature on the SAMLResponse (or on the inner Assertion), audience restriction matches our entity ID, and NotOnOrAfter / NotBefore bounds. What's not implemented yet: full xml-exc-c14n canonicalisation per the spec — we do a constrained text-normalisation that handles the IdPs in our test matrix. ECDSA-signed responses, encrypted assertions, and IdP-initiated unsolicited responses are also deferred. Pre-provisioning is required: there is no JIT user creation in the SAML path today.

Related

Questions?

Reach out for help with integration, deployment, or custom domain codecs.