Federating Microsoft 365 with Cloudflare Zero Trust (SAML)
Jack BeamanShare
Microsoft Entra ID (formerly Azure AD) is a cloud-based identity and access management solution that natively supports SAML federation but, if you’re not using Microsoft as your Identity Provider (IdP), official guidance quickly runs thin.
At Shopify, and across many modern SaaS environments, identity is often centralized in platforms like Google Workspace, while Cloudflare Zero Trust is used as the control plane for authentication, access policies, and MFA enforcement. This guide documents a safe, production-tested approach to federating a Microsoft 365 domain using Cloudflare Access as the SAML Identity Provider, regardless of the upstream IdP (Google Workspace, Okta, etc.).
"Think of a traveler wishing to come from their home country to the USA. When they arrive at the border, they are asked for their passport in order to authenticate and possibly authorize their access. If the traveler has no passport, they are redirected to their home country's government to get one." -- Auth0
This post focuses as much on guardrails and recovery paths as it does on configuration.
Architecture Overview
Authentication Flow
User → Microsoft 365 → Cloudflare Access (SAML IdP)
→ Upstream IdP (Google Workspace, Okta, etc.)
→ Cloudflare MFA / Policies
→ Microsoft Entra ID
The Step-by-Step Flow
- Request Initiation (User → Microsoft 365):
The user attempts to log into a Microsoft 365 service (e.g., Outlook or Teams). Microsoft Entra ID recognizes the user's domain as "federated" and redirects the browser to the configured SAML IdP—in this case, the Cloudflare Team Domain. - Primary Authentication (Cloudflare → Upstream IdP):
Cloudflare Access receives the request but does not yet know the user's identity. It redirects the user to the Upstream IdP (e.g., Google Workspace or Okta). The user authenticates there using their standard corporate credentials. -
Security Enforcement (Cloudflare MFA / Policies):
Once the upstream IdP confirms the user's identity, the flow returns to Cloudflare. Before passing the user back to Microsoft, Cloudflare applies its Zero Trust policies. This may include:- MFA Challenges: Requiring a hardware key (FIDO2) or mobile push even if the upstream IdP didn't.
- Device Posture: Checking if the device is managed, has an active antivirus, or is at a specific version.
- Network Context: Verifying the request is coming from a permitted country or IP range.
-
Final Assertion (Cloudflare → Microsoft Entra ID):
After all policies are satisfied, Cloudflare generates a SAML assertion signed with its private key. This assertion is sent back to Microsoft Entra ID, which trusts the "stamp of approval" from Cloudflare. Entra ID then issues the final session token to the user for Microsoft 365 access.
Why use this flow?
- Centralized Control Plane: You can enforce strict Zero Trust rules across all Microsoft apps without migrating your entire identity away from Google or Okta.
- Enhanced MFA: It allows you to use Cloudflare’s modern MFA (like FIDO2) for Microsoft apps, even if your legacy IdP or Entra ID tier doesn't support it easily.
- Conditional Access: You can block access based on real-time Cloudflare intelligence (e.g., known malicious IPs) before the user even reaches the Microsoft login screen.
Phase 0: Should You Even Do This?
Before touching anything, answer these questions:
Decision Tree: Is Federation Appropriate?
Do you want Microsoft to control MFA and sign-in policy?
→ Yes → ❌ Do not federate
→ No → Continue
Do you already use Cloudflare Zero Trust for workforce access?
→ No → ❌ Federation adds complexity
→ Yes → Continue
Do you rely on Azure AD Connect or hybrid sync?
→ Yes → ⚠️ Federation may break sync
→ No → Continue
Do you have at least one non-federated admin account (two is recommended)?
→ No → ❌ STOP
→ Yes → Safe to proceed
Phase 1: Prerequisites & Health Checks
1. Create a Break-Glass Admin Account (Mandatory)
Before federating anything, ensure you have at least one (TWO!) Global Administrator that uses the *.onmicrosoft.com domain, is not federated and has a long password stored offline that is not MFA dependent. If SAML breaks, Cloudflare is down, or certificates expire, this account is your only recovery path.
2. Cloudflare Zero Trust Requirements
You must have Cloudflare Access enabled with at least one upstream IdP configured (Google Workspace, Okta, etc.) with access to create SaaS applications. Most Zero Trust plans support this. Free plans work for testing but are not recommended for production.
3. Understand Microsoft’s ImmutableId (Critical)
Microsoft Entra uses a value called onPremisesImmutableId to bind a user account to a federated identity. Rule: The SAML NameID must exactly match the user’s ImmutableId.
In this guide, we intentionally choose:
NameID = user email (UPN)
ImmutableId = user email (UPN)
This is simple, predictable, and works well for cloud-only tenants.
4. Audit Existing ImmutableId Values
Connect to Microsoft Graph:
Connect-MgGraph -TenantId "YOUR_TENANT_ID" `
-Scopes "User.Read.All" `
-UseDeviceAuthentication
Check your users:
Get-MgUser -All -Property OnPremisesImmutableId, UserPrincipalName |
Where-Object { $_.UserPrincipalName -like "*@yourdomain.com" } |
Select UserPrincipalName, OnPremisesImmutableId
Decision Tree: Can You Proceed?
• ImmutableId matches UPN → Proceed
• ImmutableId is blank → Legacy cleanup required
• ImmutableId is different → Legacy cleanup required
If you ignore this, users will hit:
AADSTS51004: The user account does not exist in the directory
5. Confirm Domain Authentication Type
Your domain must be Managed before federation:
Get-MgDomain -DomainId "yourdomain.com" |
Select AuthenticationType
If it’s already federated, stop and investigate before proceeding.
Phase 2: Configure Cloudflare Access (SaaS App)
- Go to Cloudflare One Dashboard
- Access → Applications → Add Application
- Choose SaaS
- Select Microsoft
- Authentication protocol: SAML
Required SAML Values
Setting
Value
Entity ID
urn:federation:MicrosoftOnline
ACS URL
https://login.microsoftonline.com/login.srf
NameID Format
EmailAddress
SAML Transformation (JSONata) - Only the NameID matters to Microsoft. Extra claims are ignored.
$merge([$, {"userPrincipalName": email}])
Save These Values (You’ll Need Them)
- Issuer URI
- SSO Endpoint
- Signing Certificate (Base64)
Phase 3: Federate the Domain (Microsoft Graph)
Connect to Graph:
Connect-MgGraph -TenantId "YOUR_TENANT_ID" `
-Scopes "Domain.ReadWrite.All" `
-UseDeviceAuthentication
Apply federation:
$domainName = "yourdomain.com"
$issuerUri = "CLOUDFLARE_ISSUER"
$ssoUrl = "CLOUDFLARE_SSO_URL"
$cert = "CLOUDFLARE_PUBLIC_CERT"New-MgDomainFederationConfiguration `
-DomainId $domainName `
-DisplayName "CloudflareZeroTrust" `
-IssuerUri $issuerUri `
-ActiveSignInUri $ssoUrl `
-PassiveSignInUri $ssoUrl `
-SigningCertificate $cert `
-PreferredAuthenticationProtocol saml `
-FederatedIdpMfaBehavior acceptIfMfaDoneByFederatedIdp `
-PromptLoginBehavior nativeSupport
Verify:
Get-MgDomain -DomainId $domainName |
Select AuthenticationType
Expected output: Federated
Phase 4: Testing & Troubleshooting
Test with Domain Hint - You shuold be redirected to Cloudflare.
https://login.microsoftonline.com/?domain_hint=yourdomain.com
Avoiding Double MFA
If users see Microsoft MFA after Cloudflare MFA, make sure to disable Security Defaults, use Conditional Access instead and ensure CA policies do not re-challenge federated users.
Emergency Rollback (Know This Before You Start) - Log in using your break-glass account.
$domainName = "yourdomain.com"
$fedId = (Get-MgDomainFederationConfiguration -DomainId $domainName).IdRemove-MgDomainFederationConfiguration `
-DomainId $domainName `
-InternalDomainFederationId $fedIdUpdate-MgDomain `
-DomainId $domainName `
-AuthenticationType "Managed"
Legacy Cleanup (Pre-Federation Only)
This is the most dangerous step in the entire process. This is required when Users have blank or mismatched ImmutableId, the Tenant is cloud-only and the domain is still Managed.
When You Must NOT Do This:
- Hybrid tenants
- Azure AD Connect
- Synced users
- Unscoped bulk operations
Safe Bulk Update (Scoped)
Connect-MgGraph -TenantId "YOUR_TENANT_ID" `
-Scopes "User.ReadWrite.All" `
-UseDeviceAuthenticationGet-MgUser -All |
Where-Object {
$_.UserPrincipalName -like "*@yourdomain.com" -and
$_.UserType -eq "Member"
} |
ForEach-Object {
Invoke-MgGraphRequest -Method PATCH `
-Uri "https://graph.microsoft.com/v1.0/users/$($_.Id)" `
-Body @{ onPremisesImmutableId = $_.UserPrincipalName }Write-Host "Updated: $($_.UserPrincipalName)"
}
“Don’t Brick Your Tenant” Checklist
I suppose if you've read this far and have decided to proceed, make sure to run through this checklist before moving along any further. It probably would have been best to put this in the beginning of the article, but here we are. Configuring network architecture on the fly is rarely a good idea, and ensuring your identity provider and firewall rules are aligned now will save you from an accidental lockout once we hit 'deploy.' Let’s verify the fundamentals before we get into the complex routing.
Before clicking Run:
• ☐ At least one onmicrosoft.com Global Admin
• ☐ Domain is Managed
• ☐ ImmutableId audit completed
• ☐ No Azure AD Connect
• ☐ Test users validated
• ☐ Federation rollback commands saved
• ☐ Cloudflare cert expiration tracked
• ☐ Change window approved
• ☐ Login tested via domain_hint
• ☐ Break-glass credentials verified
If any box is unchecked — stop.

Federating Microsoft 365 with Cloudflare Zero Trust is powerful. It centralizes identity, enforces consistent MFA, and removes Microsoft as the policy authority. But it is not forgiving. Treat this like a schema migration for identity. Test it. Stage it. Guard it. And always keep a way back in.
If you don’t, just remember Microsoft won’t be there to help you.