> ## Documentation Index
> Fetch the complete documentation index at: https://woku.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SSO (Single Sign-On)

> Enterprise login via SAML 2.0 or OpenID Connect, brokered by Stytch B2B

<Info>
  **Available on the Corporate plan.** This functionality is part of woku's enterprise capabilities. [Talk to our sales team](https://woku.app/pricing).
</Info>

woku supports **enterprise SSO** (SAML 2.0 + OpenID Connect) so that
a company's users can log in with the identity provider (IdP) they
already use internally: Azure AD /
Microsoft Entra, Okta, OneLogin, Google Workspace, ADFS, etc.

The technical broker is **Stytch B2B**: Stytch hosts the
SAML/OIDC connections and hands us a verifiable token when the
IdP authentication finishes successfully.

<Note>
  **Reference case.** **RE/MAX Chile** uses SSO with woku, integrating its
  Active Directory from **Microsoft Azure AD (Entra ID)**.
</Note>

## Model

* **One woku company ↔ one Stytch `Organization`.** Every company that
  enables SSO is linked to an organization in Stytch.
* **One Stytch organization ↔ one or more SAML/OIDC connections.** The
  connections are configured in the Stytch dashboard (IdP XML metadata,
  attribute mapping, certificates, etc.).
* **Domain allowlist.** Every company that enables SSO
  declares which email domains may use SSO against that
  organization. This prevents cross-tenant impersonation attacks.
* **Identity scoped by organization + Stytch member.** Once
  a woku user logs in via SSO, we link them to the tuple
  `(stytchOrganizationId, stytchMemberId)`. Subsequent logins
  resolve by that tuple, not by email, which closes the impersonation
  window even if the email changes or is reused.
* **Per-company opt-in.** By default SSO is off and users
  log in with email + password + (optional) MFA TOTP.

## Enabling SSO in your company

Prerequisites:

1. Have a Stytch organization created (the first time we create it
   for you).
2. Have at least one SAML or OIDC connection configured in that
   organization against your IdP.

Then, from woku:

1. Go to **Company → Security → SSO**.
2. Check **Enabled** and enter what Stytch gives you:
   * **Stytch Organization ID** (format `organization-prod-...`).
   * **Stytch Connection ID** (format `saml-connection-prod-...`
     or `oidc-connection-prod-...`).
3. Add at least one **allowed email domain** (for example
   `acme.com`). Only users whose email ends in one of
   those domains will be able to complete SSO against this company.
4. Save.

> The backend refuses to enable SSO without an Organization ID, Connection ID,
> and at least one allowed domain (400 `BadRequest`). This prevents
> leaving the company with SSO enabled but with no destination or control
> over who can log in.

### Why the domain list

Without this list, a malicious (or misconfigured) IdP could assert
an email from another company (`someone@other-company.com`) and gain
access to the wrong tenant. With the list, the backend rejects with
`403` any SSO assertion whose email domain is not among
the company's authorized ones.

## What the login flow looks like (SP-initiated)

As seen by the user:

1. The user opens the admin login page and types their email.
   They click **Continue**.
2. The admin queries the **discovery service** to resolve
   which method to show:
   * If the email domain belongs to a company with SSO
     enabled, the **Continue with SSO** button appears.
   * Otherwise, the **Password** field appears.
3. If it was SSO: the browser is redirected to Stytch, Stytch to the IdP, the
   IdP back to Stytch, and Stytch to our callback. After
   exchanging the token, it redirects to the admin with the woku tokens in
   the URL fragment and the session is started.

As seen by the backend:

1. **Discovery** resolves whether the email domain belongs
   to a company with SSO active. The response indicates the mode
   (`sso` or `password`) and has a **stable shape regardless of whether the
   user exists in woku**, so as not to leak enumeration data.
2. When the user clicks **Continue with SSO**, the backend validates
   that the company has SSO enabled and responds with a **302
   redirect** to Stytch's public URL.
3. Stytch redirects to the IdP where the user authenticates.
4. The IdP redirects back to Stytch, which validates the assertion and
   redirects to the **woku callback**.
5. woku exchanges the token (`stytch.sso.authenticate`), resolves
   the company by `organization_id`, **verifies that the asserted
   email domain is in `ssoEmailDomains`**, looks up the user
   first by `(stytchOrganizationId, stytchMemberId)` and, if it is the
   first time, does JIT provisioning + records the identity.
   Finally it ensures the `UserCompany` membership, issues a pair of
   woku tokens (access + refresh) and redirects to the admin with the
   tokens in the URL fragment.
6. The admin's **callback page** reads the fragment, persists the
   token in the authentication store, opens the real time channel and
   clears the fragment from history before redirecting the user to the
   dashboard.

V1 supports only the **SP-initiated** flow (the user starts
in woku). IdP-initiated arrives in a later iteration.

## Just-In-Time provisioning

When a user logs in via SSO for the first time:

1. **Resolution by scoped identity.** First we look for a woku
   user that already has the identity
   `(stytchOrganizationId, stytchMemberId)` recorded. If it exists, we use it.
2. **Email fallback.** If there was no recorded identity, we look up
   by normalized email. If the user exists we **link** them to the
   new identity for subsequent logins.
3. **JIT.** If the email does not exist either, we create it automatically with
   a username derived from the email and a random password (that the
   user never uses). We mark the account as confirmed.
4. **Membership.** In all cases we ensure the user has
   `UserCompany` membership in the corresponding company. The default role
   is `member`. If you need SSO users to log in with
   the `admin` role, contact support (the role mapping UI is on the
   backlog).

Every SSO login is recorded in the [audit
log](/docs/en/seguridad/audit-log) as `auth.sso.login` with
`actorEmail`, `companyId`, `stytchOrganizationId`, `stytchMemberId`,
`newlyLinked` (`true` when the identity was just recorded) and IP.

## Guarantees

* **The Stytch secret never touches the client.** Only the
  `STYTCH_PUBLIC_TOKEN` (public by design) is included in the start
  URL; the `STYTCH_SECRET` lives only in the backend.
* **Server-side validation.** The woku session is issued only
  after Stytch confirms the authentication. The Stytch
  token is single-use.
* **Per-company isolation.** The callback rejects with `403` if the
  Stytch organization is not linked to any woku company.
* **Cross-tenant anti-impersonation.** The callback rejects with `403`
  if the email domain asserted by the IdP is not in
  `ssoEmailDomains`, and the woku identity is resolved by the tuple
  `(stytchOrganizationId, stytchMemberId)` before by email.
* **No enumeration.** The discovery response does not depend on
  whether the user exists in woku; only on the email domain. It is
  rate-limited to discourage sweeps.
* **Auditable.** Every step is recorded.

## Known limitations (V1)

* The detailed SAML/OIDC configuration (XML metadata, attribute
  mapping, certificates) is done in the **Stytch dashboard**, not
  in the woku panel. Only the IDs and the domains live in woku.
* SP-initiated only. IdP-initiated requires an additional endpoint;
  it arrives in the next iteration.
* No automatic role mapping: all SSO users log in as
  `member` by default. Upgrades to `admin`/`owner` are done
  manually from **Company → Members**.
* The SSO add-on is **opt-in and monetizable**: the commercial
  model (add-on price) is documented separately. In the meantime the flag
  `ssoEnabled` is turned on manually.

## Loss of access

If the IdP's SAML/OIDC connection breaks, if the user's email
domain changes, or if you need to add/remove an allowed
domain without access to the panel, contact support
(`team@woku.app`) to temporarily disable SSO or reassign
the membership manually.
