Staff SSO (Google Workspace / Entra ID / SAML)

Register your identity provider so staff sign in to the company realm with SSO.

Company-staff accounts are invitation-only, with one exception: your organization can register its own identity provider (IdP) — Google Workspace or Microsoft Entra ID over OIDC, or any SAML 2.0 IdP by metadata paste — and staff whose verified work email is on the registered domain sign in with SSO. Their first sign-in provisions a member-role account automatically (JIT); admins are still appointed by invitation or an explicit role change. Personal social OAuth does not exist in the staff realm; SSO is a separate, tenant-controlled surface (ADR-0098).

Two properties to know before you start:

  • The email domain is the router. On the staff sign-in page, an email on your registered domain is routed to your IdP instead of the password prompt (exact match — subdomains do not inherit). Password, TOTP, and passkey sign-in remain available for everyone else.
  • MFA is delegated for SSO sessions. A session established through your IdP satisfies the tenant's two-factor requirement; enforce MFA in your IdP's own policy. Password sign-ins keep the TOTP requirement.

Register the IdP in matchwire

  1. Sign in as an organization admin and open Organization → SSO (/org/sso).

  2. Choose the IdP type, enter the allowed email domain and the values from the sections below (client credentials for OIDC, the metadata XML for SAML), and register.

  3. For Google Workspace / Entra ID, copy the callback URL shown after registration (also listed per provider) — you will paste it into your IdP's app as the redirect URI. It has the form:

    https://<your-matchwire-host>/api/org-auth/sso/callback/<provider-id>

    For SAML, the registration instead shows the SP metadata URL and the ACS URL to configure on the IdP side (see the SAML section).

The client secret is write-only: it is never displayed again after registration. One IdP per domain; delete and re-register to rotate credentials or certificates (re-registering issues new URLs — update the IdP app).

Google Workspace

Create an OAuth client in the workspace's Google Cloud project:

  1. In Google Cloud console, pick a project owned by your workspace, then APIs & Services → Credentials → Create credentials → OAuth client ID, application type Web application.
  2. Add the matchwire callback URL as an Authorized redirect URI. If you register the client before matchwire (no provider id yet), use a placeholder and replace it after step 3 below.
  3. Register in matchwire (/org/sso): type Google Workspace, your domain, and the client ID/secret. Then set the shown callback URL as the redirect URI in the Google client.

Sign-ins are accepted only when Google reports the email as verified and the account's hosted domain (hd claim) equals the registered domain — a consumer Gmail account with a custom address cannot pass.

Microsoft Entra ID

Registration happens in your own Entra tenant (the standard customer-side app registration — matchwire never asks for tenant-wide admin consent to a vendor app):

  1. In the Microsoft Entra admin center, open Identity → Applications → App registrations → New registration. Supported account types: Accounts in this organizational directory only. Platform Web, redirect URI = the matchwire callback URL (a placeholder first is fine, as with Google).
  2. Create a client secret under Certificates & secrets and note the secret value.
  3. Add the optional claim that proves email-domain ownership: Token configuration → Add optional claim → ID → xms_edov (consent to add email to the API permissions when prompted, and keep openid, email, profile granted). Without xms_edov in the ID token, matchwire refuses every sign-in from this app — the claim is the verification signal.
  4. Register in matchwire (/org/sso): type Microsoft Entra ID, your domain, the directory (tenant) ID as a GUID (domain forms like contoso.onmicrosoft.com are rejected — the issuer is pinned to the GUID), and the application (client) ID/secret. Then set the shown callback URL as the app's redirect URI.

SAML (Okta and other SAML 2.0 IdPs)

For IdPs that speak SAML rather than OIDC (Okta, PingFederate, ADFS, …). Entra ID also offers SAML apps, but for Entra the OIDC path above is the recommended, simpler setup. Registration is a metadata paste — matchwire never fetches metadata URLs server-side:

  1. In your IdP, create a SAML 2.0 application and download/copy its IdP metadata XML. It must contain the entityID, an HTTP-Redirect single sign-on endpoint, and the signing certificate.

  2. Register in matchwire (/org/sso): type SAML, your domain, and the pasted metadata XML. The confirmation shows two values (also listed per provider):

    SP metadata URL (doubles as the SP entityID):
    https://<your-matchwire-host>/api/org-auth/sso/saml2/sp/metadata?providerId=<provider-id>
    ACS URL (reply URL):
    https://<your-matchwire-host>/api/org-auth/sso/saml2/sp/acs/<provider-id>
  3. Configure the IdP-side application with those values — either import the SP metadata URL, or set the SP entityID (audience) and the ACS/reply URL by hand. Have the IdP send the user's work email as the NameID (emailAddress format, requested in the SP metadata) or as an attribute named email.

Sign-in is SP-initiated only: every response must answer an AuthnRequest matchwire issued moments before, assertions are accepted once (replays are refused), and the response must be signed with a certificate from the pasted metadata. SAML has no email_verified equivalent — the signed assertion plus the exact registered-domain match is the verification. To rotate the IdP certificate, delete and re-register (the URLs change — update the IdP app).

What JIT provisioning does — and refuses

On a verified SSO callback, matchwire finds or creates the person, grants member through the standard membership path, and links the staff account. An existing invited account with the same email is linked instead — its role is never downgraded. The callback is refused, creating nothing, when the email is unverified (email_verified / xms_edov for OIDC), the domain does not match the registration exactly, or Google's hd claim is missing or different. Revoked workspace membership stops the next sign-in: OIDC tokens are re-verified on every SSO sign-in, and every SAML sign-in requires a fresh signed assertion — not only the first.

SSO enforcement — "IdP only" (Enterprise)

With the Enterprise Edition module loaded, an organization admin can require staff to sign in only through the registered IdP: on the same Organization → SSO page, turn on Require single sign-on (IdP only).

What the policy does, precisely:

  • Members and evaluators cannot obtain a non-SSO session. Password, passkey, and TOTP/backup-code completion are all refused at session issuance — after the credential verifies, before any session or cookie exists. The sign-in page states that single sign-on is required.
  • Admins are exempt, structurally. Administrator password/passkey sign-in keeps working (their TOTP requirement still applies), so a misconfigured or unreachable IdP never locks the organization out — an admin signs in and fixes the registration or turns the policy off. The sign-in page keeps a quiet "Administrators: sign in with a password instead" path for exactly this.
  • Preconditions guard the lockout. Enabling requires at least one registered IdP, and the last IdP cannot be deleted while the policy is on (turn it off first).
  • Existing sessions are not revoked by turning the policy on; staff sessions expire absolutely within 12 hours. The mail-based password reset stays available (it issues no session), so credentials recover normally if the policy is later turned off.

Without the Enterprise module (the open-core build), this section does not appear and the policy has no effect; SAML sign-in itself is part of the open core.

SCIM provisioning (Enterprise)

With the Enterprise Edition module loaded, your IdP can manage staff membership directly over SCIM 2.0 — assignment in the IdP creates the member, unassignment removes them and signs them out immediately (within the sync run, not the session's 12-hour horizon).

Setup, on the same Organization → SSO page (SCIM section):

  1. Copy the SCIM base URL (https://<your-host>/scim/v2).
  2. Issue a bearer credential and copy it — it is shown once. Rotation is revoke + issue; the credential list shows created/last-used times.
  3. Configure the IdP:
    • Okta (SCIM 2.0 app integration): SCIM connector base URL = the base URL, unique identifier field = userName, authentication mode = HTTP header (Bearer), and enable Push New Users / Push Profile Updates / Deactivate Users. Okta updates existing users mainly with PUT.
    • Microsoft Entra ID (custom app provisioning): Tenant URL = the base URL, secret token = the credential, then start provisioning. Entra patches with PATCH (string booleans are accepted). Keep only the supported attribute mappings — userName, active, displayName, name.givenName, name.familyName, externalId, and the work email — and remove the rest (an update patching an unsupported attribute such as name.formatted is refused with 400).

What provisioning does — and refuses:

  • Create finds or creates the person and grants plain member through the standard membership path — an existing member or admin is adopted with their role untouched. The userName must be an email whose domain matches a registered IdP of the organization (register the IdP first); anything else is refused.
  • Deactivate / delete removes the membership and the staff login, and clears any cached sessions in the same transaction — the person, their candidate account, and their memberships in other organizations are untouched. Deactivation is not erasure: personal-data deletion stays the person's own /profile/delete right.
  • The last admin is refused (HTTP 409) — transfer the admin role in the IdP-independent members page first; SCIM gets no exception to the invariant.
  • userName is immutable — to change a person's email, deprovision and re-provision. active is derived from the membership itself, so a manual removal in matchwire reads back as active: false on the next sync.

SCIM covers Users only: /scim/v2/Groups is not a resource (every request is 404), so leave group push/provisioning off in the IdP. Without the Enterprise module, /scim/v2/* does not exist at all (every request is 404).

On this page