Skip to content

SSO β€” Status & Path to Offering It to Customers

Where per-tenant SSO stands today, and exactly what has to happen before we can sell and onboard a customer. Companion to:

  • sso-uth-runbook.md β€” turnkey per-customer onboarding checklist
  • sso-implementation.md β€” architecture + original design
  • Epic #2589 and phase issues #2586 (Phase 1) / #2587 (Phase 2) / #2588 (Phase 3)

TL;DR

The SAML 2.0 Single Sign-On feature is built, tested, and merged to main (commit 18c2d0d76, security-hardened in a follow-up). The production database tables are already applied. This is not a development project β€” it is a small platform fix plus configuration, onboarding tooling, and documentation.

You could onboard a friendly SAML customer this week. The only gating code change is pinning one environment variable (PR #2585); the only gating operational change is setting an absolute session timeout in Supabase.


What already exists (feature-complete)

PieceLocationState
SAML crypto (verify, metadata, attribute mapping)workers/api/src/services/saml.tsBuilt (@node-saml/node-saml)
SP endpoints β€” metadata / login / ACSworkers/api/src/routes/sso.tsBuilt, Lambda-only mount
JIT provisioning + cross-tenant / replay guardsworkers/api/src/services/sso-provisioning.tsBuilt
Tenant-admin config APIworkers/api/src/routes/tenant-sso-admin.tsBuilt
Tenant-admin config UIapps/web/src/app/tenant-admin/sso/Built
User SSO start + session callback pagesapps/web/src/app/auth/sso/Built
DB tables tenant_sso_config, tenant_sso_loginsmigration 254_tenant_sso.sqlApplied in prod
Testsworkers/api/src/__tests__/**/*sso*, *saml*5 suites, comprehensive

Architecture in one line: we are our own per-tenant SAML SP (not Supabase native SSO). The customer’s IdP signs an assertion β†’ our ACS endpoint verifies the signature against their stored X.509 cert, checks the audience, enforces a verified-email-domain allowlist, JIT-provisions the user into the right tenant, guards against replay, mints a Supabase session, and hands tokens to the frontend in a URL fragment. Runs on the Lambda API (api-pdf.gevell.com) and serves pdf.gevell.com.


What still has to happen

Phase 1 β€” Platform readiness (do once) β€” issue #2586

  1. Pin PUBLIC_BASE_URL on the API Lambda β€” PR #2585 (this branch). The SP derives its entity ID + ACS URL from this var and falls back to the request origin when unset, which silently breaks the audience check. Interim manual fix:
    Terminal window
    aws lambda update-function-configuration \
    --function-name accessible-pdf-prod-api \
    --environment "Variables={PUBLIC_BASE_URL=https://api-pdf.gevell.com}"
  2. Set an absolute session timeout (Supabase Dashboard β†’ Authentication β†’ Sessions). Today every session has not_after = NULL β€” sessions never expire, one was observed refreshing for 56 days. Combined with no SLO and no SCIM, an IdP-revoked user stays signed in indefinitely. This is the #1 thing an enterprise/university security review will flag.

Phase 2 β€” Customer onboarding flow (repeatable) β€” issue #2587

  • Generalize the UTHealth runbook to any customer.
  • Build the intake web page (see below) to collect IdP details.
  • Write customer-facing docs (see below).

Phase 3 β€” Enterprise-readiness backlog (as deals require) β€” issue #2588

SLO + session revocation Β· SCIM deprovisioning Β· SSO enforcement (sso_enforce) Β· OIDC Β· cross-app SSO (audit/acr/orgchart) Β· SP request signing / encrypted assertions. All documented as v1 gaps in the runbook Β§9.


What information we need from the customer

This is the intake checklist β€” everything required to configure a tenant. Their IAM/IT team supplies most of it.

#FieldWhy we need itExample
1Organization name + desired slugSlug is permanent; appears in every SSO URLUTHealth Houston / uth
2IdP stackAttribute names differ substantiallyMicrosoft Entra ID, Shibboleth/InCommon, Okta, Google
3IdP metadata URL or XMLSource of entity ID, SSO URL, signing certhttps://idp.uth.edu/.../metadata
4Released email attribute nameExact attribute the assertion carries the email inurn:oid:0.9.2342.19200300.100.1.3 (Shib) or .../emailaddress (Entra)
5Actual released mail domain(s)Feeds the anti-takeover allowlist β€” must match realityuth.edu, uth.tmc.edu
6Display-name attribute (recommended)Populates the user profileurn:oid:2.16.840.1.113730.3.1.241
7Department/affiliation attribute + group→department map (optional)Only if they want users foldered by uniteduPersonAffiliation → dept slug
8NameID formatUsually emailAddressnameid-format:emailAddress
9Technical contact (IAM)Metadata exchange + cert-rotation notificationsname/email
10Signing-cert expiryCert rotation is the #1 cause of dead SSO laternotAfter date
11Confirmation they can accept our v1 limitationsNo SLO/SCIM/enforcement in v1signed-off by their security review

What we give them in exchange (the SP coordinates):

  • SP Entity ID / metadata URL: https://api-pdf.gevell.com/api/sso/<slug>/metadata
  • ACS / Reply URL: https://api-pdf.gevell.com/api/sso/<slug>/acs
  • Binding: HTTP-POST Β· NameID: emailAddress Β· Signed AuthnRequests: no Β· Encrypted assertions: no (v1)

User documentation we still need to write

Two distinct audiences:

  1. IdP-administrator setup guide (their IT team) β€” the most important new doc.
    • The SP coordinates to register (entity ID, ACS URL, metadata URL).
    • Which attributes to release (email mandatory, name recommended, dept optional).
    • Step-by-step screenshots for the two common stacks: Microsoft Entra ID (Enterprise Application β†’ SAML) and Shibboleth/InCommon.
    • Our v1 limitations, stated plainly (no SLO, no SCIM, no encrypted assertions).
    • Cert-rotation expectations (β€œtell us 60 days before you roll the signing cert”).
  2. End-user β€œSign in with SSO” help page β€” short. β€œGo to pdf.gevell.com/auth/sso/start, type your organization, sign in with your normal institutional login.” Where to go if it fails (contact your IT).
  3. Tenant-admin self-service guide β€” how an org admin uses /tenant-admin/sso to paste metadata, set the domain allowlist, test, and enable. (The UI exists; it needs a short walkthrough doc.)

These live under docs/user/ (Docusaurus) for the customer-facing ones; the tenant-admin guide can start in docs/admin/.


Can we add a web page that gathers the setup info?

Yes β€” recommended, and it’s Phase 2 work (#2587). Two options:

  • Option A β€” extend the existing tenant-admin UI (/tenant-admin/sso). It already has a β€œpaste IdP metadata XML β†’ parse” flow that auto-extracts the entity ID, SSO URL, and signing cert (POST /api/tenant-admin/sso/parse-metadata). The gap is the human fields (contacts, cert expiry, stack type, confirmed mail domains) and a guided first-run wizard. This is the lowest-effort path because the parsing + persistence backend already exists.
  • Option B β€” a pre-auth public intake form. A short unauthenticated page (e.g. pdf.gevell.com/sso-intake or a Typeform-style form) their IAM team fills before they have an account, writing to a lightweight sso_intake table that we review and turn into a tenant. Better for the sales/first-contact stage, before the customer has any logins.

Recommendation: do both, in order β€” Option B (intake form at first contact) to capture the details, then Option A (the wizard already half-built) for the admin to finish configuration and self-test. The metadata-parse endpoint means we should lean on paste-the-XML wherever possible rather than asking humans to transcribe entity IDs and certs by hand.


Bottom line

  • Merged & in prod: the entire SAML feature + DB schema.
  • Blocking to ship: PR #2585 (this branch) + set a session timeout.
  • To make it a repeatable product: intake form + customer/IdP-admin docs (Phase 2).
  • To pass demanding security reviews: SLO, SCIM, enforcement (Phase 3, per deal).