SAML 2.0 Sign-In Flow

GET  /api/v1/auth/saml/{providerID}/start
POST /api/v1/auth/saml/{providerID}/acs
GET  /api/v1/auth/saml/{providerID}/metadata

Rate limit: 30 requests per minute per IP (burst 10). SP-initiated only — IdP-initiated responses (no InResponseTo) are refused.

start builds a signed AuthnRequest, stores its ID in a signed relay cookie (osprey_saml_state, typ-bound, HttpOnly, SameSite=None; Secure — the ACS is a cross-site POST), and redirects to the IdP. acs verifies the assertion signature, audience, expiry (±3 min clock skew) and that InResponseTo matches our request ID; then it JIT-provisions/links the user (NameID as the immutable subject, group attribute → role mapping) and issues the session, redirecting to /. metadata returns the SP metadata XML to register at the IdP; the SP signing keypair is generated and persisted on first use (no openssl needed). Failures redirect to /?auth_error=<code> (invalid_state, sso_failed, provider_error), never rendering assertion detail.

The ACS URL and metadata URL both derive from auth.external_url.