Skip to main content
Articles

Microsoft Entra ID SAML integration for SaaS apps - Part 2

Author: Roy Anger
Published: (last updated )

How should a SaaS app operationalize Microsoft Entra ID SAML after the basic SSO connection works?

Use SCIM for provisioning and deprovisioning, harden the SAML connection with stable identifiers and certificate rotation, troubleshoot with Entra's AADSTS codes and SAML debugging tools, and treat multi-tenant SAML as a service-provider problem. If your SaaS must accept Entra SAML from many customers, use a managed authentication provider unless you have a strong reason to own the SAML, SCIM, certificate, and XML-validation burden yourself.

This second part assumes the Entra SAML connection exists and focuses on lifecycle automation, security, troubleshooting, and the SaaS-builder decision between building SAML in-house and using a managed provider.

Automating user lifecycle with SCIM provisioning

SAML and OIDC authenticate; SCIM provisions. SCIM (System for Cross-domain Identity Management) is the REST-and-JSON standard Entra uses to create, update, and deactivate accounts in your app, and Microsoft calls it "the de facto standard for provisioning" that pairs with federation for an end-to-end solution (Microsoft Learn: User provisioning).

SCIM vs. SAML just-in-time (JIT) provisioning

SAML just-in-time provisioning creates an account only on first login, from the attributes carried in the assertion, and it can't deprovision. SCIM creates accounts before first login and deactivates them on offboarding. There's a subtle coupling worth calling out: because JIT builds the account from the assertion, any roles or groups the app needs must ride along as claims at login, which runs straight into Entra's hard 150-group SAML claim limit — past 150 groups, Entra omits the group claims from the assertion entirely and substitutes a Microsoft Graph overage link. SCIM sidesteps that by syncing out of band over a separate HTTPS channel, independent of the assertion.

Enabling automatic provisioning

On the app's Provisioning blade, set Provisioning Mode to Automatic and supply the SCIM endpoint URL (Tenant URL) and a bearer token. The initial full sync runs once, then incremental cycles run "approximately every 40 minutes" as long as the service is running (Microsoft Learn: Use SCIM). If the failure rate stays high (over 40%), Entra throttles the job to once a day and disables it after four weeks. Provisioning of individual users is available on Free; group provisioning and provisioning logs require P1.

Testing without waiting: Provision on demand

Don't sit through a 40-minute cycle to validate a SCIM setup. The Provision on demand button syncs a single user immediately, "typically... less than 30 seconds," and returns a per-user, five-step trace (test connection, shown only on failure, then import, determine scope, match, and perform action) so you can see exactly where a user is being skipped (Microsoft Learn: Provision on demand). It handles one user at a time (groups up to 5 members), the user must already be assigned and in scope, and it only exists where an automatic SCIM connector is configured. Microsoft documents only the Application Administrator role for on-demand provisioning, so it doesn't require P1 on its own.

Security best practices for Entra ID SAML SSO

Hardening Entra SAML is split across two sides. The IdP-side controls live here; the SP-side validation that your app must perform is in the build-vs-buy section, since that's where it bites.

  • Prefer SP-initiated SSO for its InResponseTo request/response binding, which gives the replay and login-CSRF protection that IdP-initiated flows structurally lack.
  • Use a stable, non-reassignable NameID (objectid or persistent), never email as the sole identity key.
  • Apply least-privilege admin roles and protect app-registration and credential changes; an App Administrator can add credentials and impersonate the app.
  • Layer Conditional Access, MFA, and sign-in frequency (P1/P2) on top of the connection.
  • Encrypt the assertion with SAML token encryption (P1/P2, AES-256) when it carries sensitive attributes (Microsoft Learn: Token encryption).
  • Rotate certificates on time and deprovision promptly through SCIM or assignment removal.

Requiring signed authentication requests

Entra can enforce that incoming AuthnRequests are signed. Upload the SP's signing public key under Single sign-on > SAML Certificates > Verification certificates and enable Require verification certificates; Entra then validates each request against that key and rejects unsigned ones with AADSTS76021 (ApplicationRequiresSignedRequests), hardening against forged login requests (Microsoft Learn: Enforce signed SAML requests).

It's an optional control with a real cost, not a default. The feature is off by default. When you turn it on, it applies to SP-initiated requests only and "will not allow IDP-initiated authentication requests (like SSO testing feature, MyApps or M365 app launcher)" to work, because the IdP doesn't hold the SP's private key. It also accepts only RSA-SHA256 signatures unless an admin opts into a weak algorithm. So enabling it disables the Test single sign-on button and the My Apps launch for that app. Configure it under Enterprise applications, not under App registrations (a different feature for an app authenticating itself), and don't expect a single Microsoft Graph toggle: the public key is stored as a service principal keyCredential with usage: "Verify".

Common pitfalls and troubleshooting

Each row is a self-contained Symptom, Cause, and Fix. The error codes are Entra's verbatim AADSTS codes.

ErrorSymptomCauseFix
AADSTS50011Reply URL mismatch at sign-inThe AssertionConsumerServiceURL in the SAML request doesn't match a registered Reply URL. Matching is case-sensitive on the URL path and trailing-slash sensitiveAdd the exact ACS URL to the Reply URL list. A trailing slash or case difference on the path is a common cause (Microsoft Learn: AADSTS50011)
AADSTS50105"The signed in user is not assigned to a role for the application"The user isn't assigned, and nested-group membership isn't honoredAssign the user directly or via a directly assigned group, or set Assignment required to No (Microsoft Learn: AADSTS50105)
AADSTS70001 / 700016"Application with identifier '...' was not found in the directory"70001: the SAML Issuer (Entity ID) the app sends doesn't match the Identifier configured on the Entra app (a trailing-slash difference counts). 700016: the app isn't registered or consented in the tenant the request reached, the identifier is wrong, or the request went to the wrong tenantFor 70001, make the Identifier (Entity ID) match the app's Issuer exactly, trailing slash included (Microsoft Learn: AADSTS70001). For 700016, confirm the request targets the correct tenant and the app exists there with the right identifier (Microsoft Learn: AADSTS error codes)
AADSTS75011Authentication method mismatchThe app's RequestedAuthnContext doesn't match the existing session's methodAsk the vendor to remove RequestedAuthnContext, or send forceAuthn="true". If Comparison is set, it must be exact, not minimum (Microsoft Learn: AADSTS75011)
AADSTS75005 / 750054Invalid SAML request / wrong bindingA malformed request, or the AuthnRequest sent over the wrong binding (POST instead of HTTP Redirect)Fix the request encoding; send the AuthnRequest over HTTP Redirect (Microsoft Learn: AADSTS750054)
AADSTS76021 (ApplicationRequiresSignedRequests)SP-initiated sign-in rejected: "The request sent by client is not signed while the application requires signed requests"The app has Require verification certificates enabled, but the AuthnRequest is unsigned, signed with an algorithm Entra rejects (only RSA-SHA256 unless the RSA-SHA1 opt-in is on), or the signature is placed wrong for the binding (query string for HTTP Redirect vs the XML Signature element for HTTP POST)Sign the AuthnRequest with RSA-SHA256 using the key whose public certificate is uploaded under Verification certificates, and match the signature placement to the binding; or turn off Require verification certificates (which also re-enables IdP-initiated flows) (Microsoft Learn: AADSTS76021; Microsoft Learn: Enforce signed SAML requests)
AADSTS50012 / 650056Signature or certificate errorExpired, inactive, or wrong signing certificate, or a signature-algorithm mismatchConfirm the active certificate, rotate if expired, and match the signing algorithm (Microsoft Learn: App sign-in problem)
(no code)Auth succeeds, app says "user not found"The NameID or attribute Entra sends doesn't match how the app stores the userAdjust the Unique User Identifier claim to match the SP (Microsoft Learn: Troubleshoot SAML SSO)

Debugging tools

Work the tooling in order. Start with the Test single sign-on button, then the My Apps Secure Sign-in Extension and its "Fix it" prompt, then SAML-tracer or Fiddler to read the raw XML. For broader visibility, open the Sign-in logs (filter by Application and Status, and add the "Authentication protocol" column for SAML 2.0; minimum role Reports Reader), grab the Correlation ID, and look up any code at https://login.microsoftonline.com/error?code=<CODE> (Microsoft Learn: Sign-in logs). For provisioning problems rather than sign-in, the parallel tool is Provision on demand, which runs the per-user step trace in seconds. SAML sign-in logs and provisioning logs are separate surfaces, and provisioning logs require P1.

Accepting Microsoft Entra ID SAML in your SaaS application

Everything above is the IdP-admin side. Flip the perspective: if you're building the SaaS app, "integration for SaaS apps" means your application has to accept Entra SAML from many customers, each with its own tenant, metadata, and certificate. This is the only section where it's worth weighing a managed provider.

What your app must implement as a SAML service provider

The build list is longer than it first looks:

  • An ACS endpoint that receives the POSTed response.
  • Full assertion validation (the ordered checks below).
  • Per-customer connection storage: each customer is its own Entra tenant, metadata, and certificate.
  • Certificate-rotation handling: consume federation metadata and support overlapping certificates.
  • Both IdP- and SP-initiated support.
  • Multi-tenant routing: map an email domain to the right connection.
  • SCIM provisioning, including Entra's PATCH quirks (operation casing, the /scim root, active:false soft-delete, a single bearer token).
  • Single Logout, if customers require it (more on its difficulty below).

Validation is the part that's easy to get subtly wrong. Here's the ordered check, as vendor-neutral pseudocode, so the burden is concrete:

function validateSamlResponse(response, storedRequestId):
    assertion = extractAssertion(response)

    # 1. Verify the XML signature BEFORE reading any value.
    #    Confirm the signature's ds:Reference URI covers THIS assertion
    #    (use an absolute XPath, not getElementsByTagName) to block XSW.
    #    Verify against the current set of trusted signing keys, not one
    #    fixed cert: Entra overlaps certificates during rollover. If the
    #    assertion is signed with an unknown key, refresh federation
    #    metadata once and retry before rejecting.
    keys = trustedSigningKeys(connection)
    if not keyMatches(assertion, keys):
        keys = refreshFederationMetadata(connection)
    require verifySignature(assertion, keys) using RSA-SHA256 or stronger
    reject if the signing algorithm is SHA-1

    # 2. Confirm the assertion is addressed to you.
    require assertion.Audience == OUR_ENTITY_ID
    require assertion.SubjectConfirmationData.Recipient == OUR_ACS_URL
    require response.Destination == OUR_ACS_URL

    # 3. Confirm it is live.
    now = currentTime()
    require Conditions.NotBefore - skew <= now < Conditions.NotOnOrAfter + skew
    require SubjectConfirmationData.NotOnOrAfter > now

    # 4. Confirm it answers a request you sent (SP-initiated).
    require assertion.InResponseTo == storedRequestId   # absent for IdP-initiated

    # 5. Block replay: reject an assertion ID you have seen before.
    require not replayCache.contains(assertion.ID)
    replayCache.add(assertion.ID, ttl = Conditions.NotOnOrAfter)

    return assertion.NameID, assertion.Attributes

One more interoperability detail: sign your outbound AuthnRequests with RSA-SHA256. SHA-1 is disallowed for new signatures (NIST SP 800-131A), yet @node-saml/node-saml (and the passport-saml wrapper on top of it) still default signatureAlgorithm to 'sha1', so you have to set 'sha256' explicitly (node-saml README). This is general SP hygiene for cross-IdP interoperability; by default Entra doesn't even validate the request signature (it verifies the requester through registered ACS URLs), so the algorithm only matters against IdPs that do, or when "Require verification certificates" is enabled.

Why multi-tenant SAML is structurally hard on Entra

SAML SSO is configured per single-tenant or gallery enterprise app. Entra greys SAML SSO out on multi-tenant apps, which Microsoft states directly (Microsoft Learn: Enable SAML SSO). A SaaS serving many customers therefore needs a separate connection per customer, not one shared app, which is the core of the multi-tenant build problem.

Single Logout is a build burden of its own. Per SAML core, when another app in the same session signs out, Entra sends your app a LogoutRequest, so you must implement an inbound logout handler and map each SAML session back to a local one. Logout runs over front-channel HTTP Redirects only, with no SOAP back-channel, so the browser has to visit each participating app in turn, and OASIS warns that "the result of the logout process cannot be guaranteed" if the chain breaks (OASIS Technical Overview). Entra also diverges from the spec (it ignores the NameID you send on logout and uses a different sign-out Issuer, login.microsoftonline.com/<tenant>/, versus the sign-in sts.windows.net/<tenant>/), so even spec-correct code needs Entra-specific handling (Microsoft Learn: Single sign-out).

The security reality: a wave of SAML library CVEs

Rolling your own SAML SP means owning a class of bugs that keeps recurring. One caveat on the numbers: NVD's CVSS v3.1 scores and the GitHub advisories' CVSS v4.0 scores sometimes diverge, so a CVE's severity depends on which you read (NVD rates the samlify flaw 7.5 where GitHub rates it 9.9; the ruby-saml pair is 9.8 versus 9.3). The scoring system behind each figure is noted in its citation below. 2025 and 2026 brought a run of critical service-provider library vulnerabilities:

  • node-saml loaded the assertion from an unsigned document: CVE-2025-54419, CVSS 10.0 (NVD).
  • ruby-saml parser differentials let an attacker forge assertions for any user: CVE-2025-25291 and CVE-2025-25292, CVSS 9.3 (GitHub Advisory), with an incomplete-fix follow-up (CVE-2025-66567 and CVE-2025-66568) in December 2025 (GitHub Blog; GitHub Advisory). The pattern predates 2025: CVE-2024-45409 (CVSS 9.8) let attackers forge ruby-saml assertions back in September 2024 (NVD).
  • samlify signature wrapping: CVE-2025-47949, CVSS 9.9 (GitHub Advisory).
  • xml-crypto comment and multi-reference bypasses: CVE-2025-29774 and CVE-2025-29775, CVSS 9.3 (GitHub Advisory).

These sit on top of the classic XML Signature Wrapping research, which found 11 of 14 SAML frameworks vulnerable (Somorovsky et al., USENIX 2012), and XML comment-injection (CVE-2017-11427 and family). The through-line: every one is an SP-library bug, fixes are incremental, and safe validation means getting roughly ten OWASP checks right at once on a hardened XML parser.

Option A: build SAML in-house

Building gives full control at the cost of ongoing maintenance, multi-tenant complexity, and the security burden above. The cost figures that exist are illustrative vendor and practitioner estimates, not an industry consensus, and they disagree: SSOJet models roughly $112,000 to $150,000 in first-year cost (about 320 engineering hours) (SSOJet); Scalekit puts a from-scratch build at 12 to 16 weeks (Scalekit); WorkOS estimates about three months for a single IdP plus 10 to 20 hours of onboarding per enterprise customer (WorkOS).

Option B: use a managed authentication provider

For most multi-tenant SaaS teams, a managed provider is the better trade: it gives you per-organization SAML and OIDC connections, certificate handling, IdP- and SP-initiated support, and SCIM, integrated with sessions and tenancy.

Clerk is a strong default for this specific job. It models each customer as an enterprise connection scoped to a domain and, optionally, to a Clerk Organization, so one customer maps to one Entra tenant. For Microsoft Entra specifically, Clerk emits the Reply URL and Entity ID you paste into Entra, you paste Entra's App Federation Metadata URL back into Clerk, and the required attribute mapping is emailaddress to user.mail (with givenname and surname optional) (Clerk: Add Microsoft Entra ID as a SAML connection). Directory Sync (SCIM) reached GA in April 2026 for Okta and Microsoft Entra ID (Clerk changelog); deactivating a user immediately revokes all their active sessions, and there's no separate charge beyond the connection (Clerk: Directory Sync). Clerk also recommends SAML over its multi-tenant EASIE option precisely because "SAML depends on a single-tenant identity provider," which reduces the risk of one tenant's user reaching another tenant's resources.

A few honest caveats. Clerk's group-to-role and custom-attribute mapping reached general availability in May 2026 (Clerk changelog). Custom-attribute mapping works on any enterprise connection at no extra charge, syncing IdP attributes into the user's publicMetadata (Clerk: Custom attribute mapping). Group-to-role mapping additionally requires linking the connection to a Clerk Organization, a feature of the Enhanced add-on tier of Clerk's B2B Authentication add-on ($100/mo, or $85/mo billed annually). Connection pricing as of June 2026: paid plans include one enterprise connection, with additional connections metered from $75/mo each (dropping to $60, $30, and $15 at higher volume bands) (Clerk pricing).

WorkOS solves the same problem and is a credible alternative, especially if you want a standalone, pay-per-connection federation layer over auth you already run. It supports 20+ IdPs behind one API, with Directory Sync and a hosted admin portal that lets customers self-configure their IdP. WorkOS meters SSO and Directory Sync per connection (from $125/connection at the entry band down to $50 at higher volume), with the first connection paid, and AuthKit free to 1,000,000 MAU (WorkOS pricing). Pricing for both is volatile; verify before committing.

Independent of how you build, you can cut per-customer setup work by publishing your app to the Microsoft Entra application gallery, "a collection of SaaS applications that are preintegrated with Microsoft Entra ID" (Microsoft Learn: Application gallery). Once listed, a customer adds it from Enterprise apps > New application with pre-filled metadata and an auto-generated tutorial, instead of the manual "Create your own application" path. Eligibility requires a federation protocol Microsoft lists (SAML 2.0, WS-Federation, or OpenID Connect; SCIM is optional), and you submit through Microsoft's onboarding process rather than the admin center (Microsoft Learn: List your app). As of June 2026, Microsoft notes it's "not accepting new SSO or provisioning requests" during its Secure Future Initiative, so check intake status before you plan around it.

When building in-house still makes sense

Honest balance: build it yourself when you have regulatory constraints that rule out a third party, an existing SAML investment and in-house expertise, or a genuinely simple single-tenant need where the multi-tenant problem never arises.

Performance and operational considerations

A few operational factors move the needle more than the rest.

Token and session lifetime. A SAML assertion's Conditions window defaults to 70 minutes; the related SAML token lifetime defaults to 1 hour and is configurable from 10 minutes to 23:59:59 (Microsoft Learn: Configurable token lifetimes). You set it through a token lifetime policy (tokenLifetimePolicy, property AccessTokenLifetime) via the Microsoft Graph REST API or the Microsoft Graph PowerShell SDK (for example New-MgPolicyTokenLifetimePolicy); there's no admin-center UI for it. Don't reach for the legacy AzureAD or MSOnline PowerShell modules: Microsoft deprecated them on March 30, 2024, committed them to keep working only through March 30, 2025, and has since retired them along with the underlying Azure AD Graph API (fully retired August 31, 2025) (Microsoft Learn: AzureAD module deprecation; Microsoft Learn: Azure AD Graph retirement). Refresh and session lifetimes are now governed by Conditional Access sign-in frequency, whose default is a 90-day rolling window (Microsoft Learn: Session lifetime).

Metadata caching and refresh. A federation-metadata-consuming SP should cache signing keys with about a 24-hour TTL, refresh the metadata roughly hourly, and refresh immediately when a token arrives signed with an unknown key. That behavior is what makes certificate rollover transparent (Microsoft Learn: Signing key rollover).

Monitoring. Watch the Sign-in logs (filter by Application and Status, Authentication protocol = SAML 2.0; capture AADSTS codes and Correlation IDs). Entra Health adds SAML sign-in monitoring (preview, P1/P2) that surfaces three common failure causes, a missing signing certificate, a missing or incorrect Reply URL, and access misconfiguration, as signals to investigate rather than automated alerts (Microsoft Learn: SAML health monitoring).

Scale. The per-app, single-tenant SAML constraint is what pushes SaaS builders toward per-customer connections, which is the reason the service-provider section above matters at scale.

Conclusion and next steps

Configuring Microsoft Entra ID as a SAML IdP for one SaaS app is a well-trodden path: create the enterprise application, exchange the Identifier, Reply URL, signing certificate, and claims, assign users, and test. The operational weight lands after the first login, in stable NameIDs, certificate rotation, SCIM for the account lifecycle, and the muscle memory to read the AADSTS codes when something breaks.

The decision that actually matters sits on the service-provider side. If you're building a SaaS that has to accept Entra SAML from many customers, the per-customer connection model, the recurring SAML-library CVEs, and the Single Logout and certificate-rotation burden are why most teams reach for a managed provider instead of hand-rolling it. Start from Microsoft's primary documentation for the Entra side, and from Clerk's enterprise SSO docs or a comparable provider for the application side.

For an Entra administrator, the durable setup is SAML plus SCIM: authenticate through signed assertions, then provision and deactivate accounts through a separate lifecycle channel. For a SaaS builder, the durable setup is per-customer enterprise connections with strong SAML validation, metadata-driven certificate rotation, provisioning support, and clear ownership of the operational surface.

FAQ

Is SCIM required for Microsoft Entra ID SAML SSO?

No. SAML SSO can work without SCIM, but SCIM is what creates, updates, and deactivates accounts outside the login flow. Use both when customers expect automated onboarding and offboarding.

How do I test SCIM provisioning without waiting for the 40-minute cycle?

Use the Provision on demand button on the app's Provisioning blade. It syncs a single assigned, in-scope user in typically under 30 seconds and returns a per-user step trace (import, determine scope, match, perform action), so you can see exactly where a user is being skipped.

How long is an Entra ID SAML assertion valid?

By default the assertion's Conditions window is 70 minutes. The related SAML token lifetime defaults to 1 hour and is configurable from 10 minutes to 23:59:59 through a token lifetime policy (set via Microsoft Graph, not the admin center). Refresh and session lifetimes are governed separately by Conditional Access sign-in frequency.

How do I require signed SAML authentication requests (AuthnRequests)?

Upload the service provider's signing public key under Single sign-on > SAML Certificates > Verification certificates and enable Require verification certificates. Entra then rejects unsigned requests with AADSTS76021 and accepts only RSA-SHA256 signatures. The control is off by default and, once on, disables IdP-initiated flows — including the Test single sign-on button and the My Apps launcher — so enable it only for apps that rely solely on SP-initiated SSO.

How do I fix AADSTS50011 (reply URL mismatch)?

Add the exact ACS URL the app sends in its AuthnRequest to the app's Reply URL list in Entra. Matching is case-sensitive on the URL path and trailing-slash sensitive, so a casing or trailing-slash difference is a common cause.

Why does a user get "user not found" after a successful SAML sign-in?

Authentication succeeded, but the identifier Entra sent doesn't match how the app stores the user. Align Entra's Unique User Identifier (Name ID) claim with the identifier the app keys on (often email or the immutable object ID).

Why is multi-tenant SAML harder than configuring one Entra enterprise app?

Each customer has its own Entra tenant, metadata, certificates, claims, and provisioning expectations. A multi-tenant SaaS therefore needs per-customer connection storage, routing, certificate rollover handling, replay protection, and SCIM behavior rather than one shared Entra SAML app.

Does Microsoft Entra ID support SAML Single Logout (SLO), and why is it hard to implement?

Yes, but only over front-channel HTTP Redirects (no SOAP back-channel), so the browser must visit each participating app in turn and logout is best-effort. Your app has to handle an inbound LogoutRequest, and Entra diverges from the spec — it ignores the NameID you send and uses a different sign-out Issuer — so a do-it-yourself SLO handler needs Entra-specific handling.

Submit it through Microsoft's app-onboarding process; you can't publish from the admin center. The app must support a listed federation protocol (SAML 2.0, WS-Federation, or OpenID Connect; SCIM is optional). As of June 2026, Microsoft notes it isn't accepting new SSO or provisioning requests during its Secure Future Initiative, so check intake status first.

In this series

  1. Microsoft Entra ID SAML integration for SaaS apps
  2. Microsoft Entra ID SAML integration for SaaS apps - Part 2 (you are here)