SSO and SAML
Chapter 3 · about 20 minutes to read
Password logins work for small teams. The moment you cross a few dozen users — or an auditor asks "how do you offboard?" — you want SSO. Zyra supports SAML 2.0 and OIDC out of the box, plus SCIM 2.0 for auto-provisioning.
Time: about 20 minutes (plus IdP-side configuration). Prerequisites: Org Admin in Zyra. Admin access to your IdP (Okta, Entra ID, Google Workspace, OneLogin, etc.).
What's implemented today
Confirmed in backend/app/routers/auth/sso_saml.py, sso_oidc.py, and scim_users.py:
- SAML 2.0 — IdP-initiated and SP-initiated login, SP metadata endpoint, attribute mapping, auto-provisioning, email-domain matching.
- OIDC — discovery document, PKCE flow, nonce verification, code exchange, ID-token validation.
- SCIM 2.0 — full user CRUD (
GET / POST / PUT / PATCH / DELETE /scim/v2/Users) using Bearer tokens.
[VERIFY: confirm Okta + Entra ID as production-tested; Google Workspace and OneLogin as community-reported]
Step 1 — Pick your protocol
Use SAML when your IdP is Okta or Entra ID and your security team prefers SAML, or when compliance mandates SAML assertions. Use OIDC when you already use Google Workspace or any OAuth2/OIDC IdP, or when you want simpler XML-free config. Both flows land in the same place: a Zyra JWT for the browser. IdP tokens stay server-side.
Step 2 (SAML) — Exchange metadata
Download Zyra's SP metadata from GET /api/v1/sso/saml/metadata, upload to your IdP, then copy back the IdP entity ID, SSO URL, and X.509 signing certificate.
curl -X POST https://app.getzyra.io/api/v1/sso/saml/config \
-H "Authorization: Bearer $ZYRA_API_KEY" \
-d '{
"idp_entity_id": "https://idp.example.com/saml",
"idp_sso_url": "https://idp.example.com/sso",
"idp_certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"attribute_mapping": {"email":"...","role":"..."},
"default_role": "org_member",
"auto_provision_users": true,
"email_domains": ["acme.com"]
}'
email_domains wires the auto-detect on the Zyra login page (POST /api/v1/sso/check-email).
Step 2 (OIDC) — Point to the discovery URL
OIDC is configured via POST /api/v1/sso/config with protocol=oidc and the IdP's issuer URL. Zyra fetches /.well-known/openid-configuration, performs the PKCE flow, and verifies the ID token's signature, audience, issuer, and nonce. [VERIFY: full OIDC config-create payload — see backend/app/routers/auth/sso_config.py for the canonical schema]
Step 3 — Map attributes to Zyra roles
The attribute_mapping.role claim from your IdP normalises to one of super_admin, org_admin, or org_member. Unknown values fall through to default_role. See Stage 3, Chapter 5 for the full permission matrix.
Step 4 — Fallback to password auth
Password login stays enabled for Org Admins even after SSO is active. This is the "break-glass" path if your IdP is down. Members on SSO-restricted domains are forced through the IdP — they cannot fall back unless an admin temporarily disables SSO.
Step 5 (Optional) — SCIM provisioning
SCIM auto-syncs users from your IdP into Zyra: hire someone, they land in Zyra; remove them, they're deactivated.
- Generate a SCIM token.
POST /api/v1/scim/tokens(Org Admin). Copy the token once. - Configure your IdP's SCIM connector. Base URL
https://app.getzyra.io/api/v1/scim/v2. Bearer token from step 1. Content-Typeapplication/scim+json. - Push users. Your IdP starts calling
POST /scim/v2/Usersfor new hires andDELETE /scim/v2/Users/{id}(soft-delete) for offboards.
Rate limits: 100/min for reads, 50/min for writes.
What just happened
You picked SAML or OIDC, exchanged metadata, mapped attributes to roles, and optionally turned on SCIM. New hires sign in with their corporate account; leavers are deactivated automatically.
Troubleshooting
- SAML callback returns
400 Invalid SAMLResponse. Your IdP cert doesn't match the one you uploaded. Re-download and update viaPUT /sso/saml/config. - OIDC fails with
IdP did not return id_token. Your IdP'sresponse_typeis misconfigured. - SCIM returns 401. The Bearer token doesn't match any active row, or it's been revoked. Generate a new one.
Last reviewed: 2026-05-21