Menu

Developer documentation

Microsoft Entra ID workforce SSO

/docs/microsoft-entra-id

Current status: operator-drive guide, not a completed support claim. The SAML path and Entra claim mapping are implemented, but a real workforce-tenant interoperability drive and a safe multi-certificate rollover path remain release gates. Do not describe this as “Signet supports Entra ID” until the receipt in Release gate is green.

Signet does not replace or synchronize Microsoft Entra ID. Entra remains the workforce identity provider; Signet consumes a signed SAML assertion from one tenant and creates its ordinary local session. This first profile is single-tenant, SP-initiated SAML for a customer-created non-gallery Enterprise Application.

It does not use Microsoft Graph, import groups, evaluate Conditional Access, or manage Windows sign-in. IdP-initiated launch, SCIM, SAML logout, assertion encryption, and Entra External ID are outside this profile. Organization membership is provisioned at the callback: a successful sign-in through an org-linked provider adds a missing membership as a member only after the provider has proved control of its domain. Existing memberships keep their roles.

OIDC with Entra (2026-08-08): Signet now ships enterprise SSO over OIDC; see the "OIDC providers" section of the organization SSO guide. An Entra App registration (OIDC app, client id + secret, redirect URI {base_url}{base_path}/sso/callback/{providerId}, issuer https://login.microsoftonline.com/{tenant-guid}/v2.0) is often the faster path than the non-gallery SAML Enterprise Application this guide drives. The OIDC transport was driven against a live Entra workforce tenant on 2026-08-20 — full round trip (PKCE authorize, MFA login, client_secret_basic token exchange, ID-token validation, domain-proof-gated member enrolment) plus four live refusal receipts. One OIDC prerequisite that drive surfaced: the Entra user must carry an email (mail attribute or an email claim in the ID token) — a cloud-only user with an empty mail attribute is refused with missing_user_info. The SAML transport is not yet driven — this guide remains the SAML operator drive, and the overall release gate stays open until SAML passes on a live tenant too.

Before configuring Entra

  • Use an enterprise binary built with the enterprise-sso feature. The stock binary does not mount SAML registration, sign-in, or callback routes.
  • Serve Signet at its final public HTTPS base_url; the Reply URL is byte-sensitive.
  • Create the Signet organization first and add the operator who will register SSO as an owner or admin.
  • Choose a URL-safe provider ID, for example entra-acme. It becomes part of the Reply URL and cannot collide with another provider.
  • Record the Entra tenant ID. Use a tenant GUID, never common, organizations, or a mutable tenant domain.

For an instance at https://auth.example.com with the default /api/auth base path and provider ID entra-acme, the values are:

Entra fieldExact Signet value
Identifier (Entity ID)https://auth.example.com/api/auth/sso/saml2/sp/metadata/entra-acme
Reply URL (ACS)https://auth.example.com/api/auth/sso/saml2/callback/entra-acme
Sign on URLThe application’s own page that starts Signet SSO; leave blank if there is no browser GET page.
Relay StateLeave blank. Signet creates a single-use RelayState for SP-initiated login.

The Entity ID is an identifier, not a promise that its URL serves a metadata document. The current slice does not publish SP metadata; Entra’s Basic SAML Configuration must be entered explicitly. Replace both the origin and base path when the deployment does not use these example values.

Create the Entra Enterprise Application

  1. In the Microsoft Entra admin center, open Enterprise applications, add a Non-gallery application, then choose Single sign-on → SAML.
  2. Enter the Identifier and Reply URL above exactly. Do not enable an IdP-initiated application launch for this profile.
  3. Under Attributes & Claims, ensure these four claims are emitted. The claim name and namespace together must produce the exact URI in the right column.
PurposeSuggested Entra sourceAssertion attribute name Signet maps
Immutable account keyuser.objectidhttp://schemas.microsoft.com/identity/claims/objectidentifier
EmailA populated, organization-approved mail attributehttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
Given nameuser.givennamehttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
Surnameuser.surnamehttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname

NameID may remain Entra’s default UPN. Signet does not use it as the account key when the mapping below is present. ObjectIdentifier is scoped by the Signet provider, which in turn is pinned to one tenant issuer.

  1. Decide whether Assignment required should be enabled and assign the test users. That is an Entra-side access decision; Signet does not reproduce it.
  2. In SAML Certificates, download the active signing certificate and record its expiry. Signet accepts a PEM certificate or its base64 DER body in samlConfig.cert.
  3. Record the tenant-specific Login URL and Microsoft Entra Identifier. For the public Entra cloud they normally have these exact tenant-GUID forms:
https://login.microsoftonline.com/TENANT_ID/saml2
https://sts.windows.net/TENANT_ID/

Do not substitute endpoints from a different tenant or cloud.

Register the provider in Signet

With the Signet organization owner/admin session, send this body to POST /api/auth/sso/register. Replace every uppercase placeholder and keep the trailing slash on the Entra issuer.

{
  "providerId": "entra-acme",
  "issuer": "https://sts.windows.net/TENANT_ID/",
  "domain": "acme.example",
  "organizationId": "ORGANIZATION_ID",
  "samlConfig": {
    "cert": "BASE64_DER_CERTIFICATE_BODY",
    "callbackUrl": "https://app.example.com/after-sso",
    "audience": "https://auth.example.com/api/auth/sso/saml2/sp/metadata/entra-acme",
    "idpMetadata": {
      "entityID": "https://sts.windows.net/TENANT_ID/",
      "singleSignOnService": [
        {
          "Binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
          "Location": "https://login.microsoftonline.com/TENANT_ID/saml2"
        }
      ]
    },
    "spMetadata": {
      "entityID": "https://auth.example.com/api/auth/sso/saml2/sp/metadata/entra-acme"
    },
    "mapping": {
      "id": "http://schemas.microsoft.com/identity/claims/objectidentifier",
      "email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
      "firstName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
      "lastName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
    },
    "wantAssertionsSigned": true,
    "allowIdpInitiated": false,
    "rejectExpiredCert": true
  }
}

mapping.id is strict. If the assertion omits ObjectIdentifier, Signet refuses it instead of falling back to NameID/UPN. The same is true of the explicitly mapped email claim. The account key is (providerId, ObjectIdentifier), so the same object ID in another tenant/provider is a different external identity.

callbackUrl is the safe post-login fallback, not the Reply URL. The Reply URL is always the provider-specific Signet callback shown above. Prefer a relative application path when Signet and the application share an origin; otherwise the application origin must be in Signet’s trusted origins.

Prove the login domain and start SSO

Registration alone does not make email/domain discovery trusted. Complete the DNS TXT flow in Organization SSO discovery and domain ownership. This proof is also required before an org-linked provider can add new members at the callback, even when sign-in starts with an explicit providerId. Already-linked users who are already members can still sign in without this proof through an explicit providerId. Then start a login from the application with one of:

{"callbackURL":"/dashboard","providerId":"entra-acme"}
{"callbackURL":"/dashboard","email":"reader@acme.example"}
{"callbackURL":"/dashboard","organizationSlug":"acme"}

Send the selected body to POST /api/auth/sign-in/sso. The response contains the Entra redirect URL. A successful Entra response returns to the provider-specific Reply URL, consumes the pending AuthnRequest and RelayState, creates the ordinary Signet session, and redirects to callbackURL.

After the assertion and account checks pass, an org-linked provider's callback creates any missing organization membership with the member role before issuing a session. The organization must still exist, and the provider must have proved control of its domain to add a new member. If membership creation fails, no session is issued. Existing memberships keep their roles and bypass this creation gate. An assertion also cannot silently link itself to a pre-existing local account with the same email.

⚠ Upgrading a provider that ALREADY stored a mapping

Read this before upgrading if any registered SAML provider already has samlConfig.mapping.id set. Until this release the stored mapping was written and then ignored — every account was keyed on attributes["nameID"] falling back to the Subject NameID. From this release the key is the claim the mapping names, which is the whole point of the change and also its one hazard.

What happens on the first sign-in after upgrade. The account row is looked up by (providerId, accountId). Rows written under the old scheme carry the NameID as accountId, so the lookup misses:

  • if the asserted email still resolves to the existing local user, that user is refused with account_not_linked — a hard lockout of every existing SAML user on that provider;
  • if mapping.email also names a different claim, the email lookup misses too and a duplicate user and account are created, orphaning the original identity.

Neither case is an account takeover — a session is issued only when the account row's userId matches the user resolved independently by email, so a mismatched pair is refused rather than merged (verified by cross-family review, 2026-08-05). It is an availability and data-integrity hazard, not a security one.

There is no automatic migration, dual-key read, or backfill. Before upgrading such a provider, do one of:

  1. Backfill account.accountId for that provider from the old NameID to the value the mapped claim will assert, then upgrade; or
  2. Clear samlConfig.mapping.id before upgrading, so the provider keeps its existing NameID-derived key and nothing re-keys; adopt the mapped claim later behind a planned backfill.

A provider that never stored a mapping is unaffected — the default short-claim behaviour is byte-identical to the previous release.

Certificate rotation and current limitations

The current provider stores one trusted IdP certificate. Before Entra activates a replacement, update the registered provider through the supported operator process and drive a login using the new key. There is not yet a safe current-plus-next overlap or automatic metadata refresh, so this manual cutover is a release blocker for a broad support claim, not a finished rotation story.

Email/name claims populate a newly created SSO user. This slice does not synchronize later Entra profile changes. In particular, changing the asserted email can require operator intervention; Signet will not silently move an external account between local users.

The SAML callback validates the configured signing key, SHA-2 XML signature, issuer, audience, recipient/destination, assertion time window, response status, InResponseTo, and replay before it creates a session. Those checks do not prove that Entra evaluated a particular Conditional Access, MFA, assignment, or device-compliance policy.

Release gate

Before this becomes supported copy, repeat the entire guide from a fresh database against a real Entra workforce tenant and retain a redacted receipt for:

  1. assigned-user SP-initiated login and unassigned-user refusal;
  2. ObjectIdentifier-based repeat login when NameID/UPN changes;
  3. refusal for missing object ID/email, wrong issuer, wrong audience, wrong Reply URL, unknown signing key, expired assertion, unsolicited response, and assertion replay;
  4. the existing-email account-link refusal and the no-JIT-membership behavior; and
  5. certificate replacement without accepting an unknown or expired key.

Microsoft’s operator references:

Enter to open · Esc to close