fix(auth): let Microsoft sign-in link via Entra's domain-verified email claim - #6546
Conversation
…il claim Microsoft is excluded from accountLinking.trustedProviders because the email claim is attacker-controllable on /common/ (nOAuth). Entra never emits email_verified for work/school accounts, so Better Auth refused to link a Microsoft identity onto any existing user row, permanently stranding those users on account_not_linked. Derive emailVerified from the xms_edov optional claim, which Entra emits only when the email's domain belongs to the user's tenant and an admin verified it — the one email signal a hostile tenant cannot forge. Microsoft stays untrusted; the guard now passes on its own merits. The mapper returns an empty object when unverified, so it can only ever promote unverified to verified, never downgrade.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Microsoft social login now uses The Reviewed by Cursor Bugbot for commit 5675934. Configure here. |
Greptile SummaryThe PR updates Microsoft authentication to recognize Entra’s domain-verified email claim while keeping Microsoft excluded from trusted account-linking providers.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/oauth/microsoft.ts | Adds a narrowly scoped mapper that promotes Microsoft email verification only for accepted xms_edov values. |
| apps/sim/lib/auth/auth.ts | Wires the mapper and explicit account-selection prompt into the Microsoft social provider configuration. |
| apps/sim/lib/oauth/microsoft.test.ts | Tests verified, unverified, absent, string, and numeric encodings of the Entra claim. |
| apps/sim/app/oauth-error/page.tsx | Adds recovery-oriented messages for account-linking and missing-email failures. |
Sequence Diagram
sequenceDiagram
participant User
participant Sim as Sim Auth
participant Entra as Microsoft Entra
participant BA as Better Auth
User->>Sim: Sign in with Microsoft
Sim->>Entra: "Authorize with prompt=select_account"
Entra-->>BA: ID token with email and optional xms_edov
BA->>Sim: Map Microsoft profile
alt xms_edov is verified
Sim-->>BA: "emailVerified=true"
BA->>BA: Evaluate existing-account linking
else claim absent or unverified
Sim-->>BA: No profile override
BA->>BA: Preserve existing verification result
end
Reviews (3): Last reviewed commit: "chore(auth): tighten the Microsoft linki..." | Re-trigger Greptile
Hosted Sim serves many Entra tenants, so it must stay on the multi-tenant endpoint — pinning is only meaningful for a self-hoster restricting sign-in to their own directory, and nobody is asking for that yet. The xms_edov fix is independent of the tenant setting, so this removes surface without touching behavior.
|
@cursor review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5675934. Configure here.
Summary
microsoftis excluded fromaccountLinking.trustedProvidersbecause the email claim is attacker-controllable on/common/(nOAuth). Entra never emitsemail_verifiedfor work/school accounts, so Better Auth refused to link a Microsoft identity onto any existing user row — those users hitaccount_not_linkedpermanently, with no recovery pathemailVerifiedfrom Entra'sxms_edovoptional claim, emitted only when the email's domain belongs to the user's tenant and an admin verified that domain. It's the one email signal a hostile tenant cannot forge, and it's Microsoft's documented nOAuth mitigation.microsoftstays untrusted — the linking guard now passes on its own merits{}when unverified, so it only ever promotes unverified → verified, never downgrades. With the claim absent, Better Auth's computation is byte-identical to todayprompt=select_account, so/common/stops silently reusing whichever Microsoft session the browser already holds/oauth-errorcopy foraccount_not_linkedandemail_not_found, which currently dead-end on "Please try again" — advice that can never succeedRequires an Entra change to take effect
Inert until
xms_edovandemailare added as optional claims on the ID token for the app registration (xms_edovrequiresemailto be present). Until then the mapper returns{}and behavior is unchanged.Known limitation:
xms_edovisfalsefor SAML/WS-Fed federated domains and for B2B guests, so those tenants remain unlinkable and should use Entra SSO, which is trusted for linking.Verified against the Better Auth source
callback.mjs:140-152passesgetUserInfo's result intohandleOAuthUserInfo;microsoft-entra-id.mjs:91-107builds it fromdecodeJwt(idToken), soxms_edovis visible tomapProfileToUser, whose result is spread last (:116)overrideUserInfoOnSignInis unset, so the branch that rewritesemail/emailVerifiedon every sign-in (link-account.mjs:67-77) never runsemailVerifiedupgraded on next sign-in (link-account.mjs:49,65), guarded on exact email match — an upgrade, never a downgradeauth.ts:348gates onemailVerified); after this they get one at creation.afterEmailVerificationonly runs in the verification flow, which OAuth signups don't enter — no duplicatelink-account.mjs:105) needs bothsendOnSignUpandsendVerificationEmail; we set neither, so it stays deadgenericOAuthentries with their ownproviderIdandgetUserInfoType of Change
Testing
bun run type-checkclean; 297 tests across 18 files inlib/auth+lib/oauthpass; verified the new tests fail when the mapper is stubbed out.Checklist