fix(providers): Microsoft Entra ID multi-tenant issuer validation#13440
Open
guustgoossens wants to merge 1 commit into
Open
fix(providers): Microsoft Entra ID multi-tenant issuer validation#13440guustgoossens wants to merge 1 commit into
guustgoossens wants to merge 1 commit into
Conversation
The Microsoft Entra ID discovery document is templated -- it returns `issuer: "https://login.microsoftonline.com/{tenantid}/v2.0"` regardless of which tenant URL is queried. The `[customFetch]` hook substitutes the `{tenantid}` placeholder before handing the response to oauth4webapi. The substitution was reading the tenant from `config.issuer` only, defaulting to `"common"`. For the multi-tenant (`/common`, `/organizations`, `/consumers`) configurations the existing re-discovery in `handleOAuth()` (issued with the actual tenant from the `id_token`'s `tid` claim) was then rewritten back to `common/v2.0`, which mismatches the URL passed to `processDiscoveryResponse` and threw before the token could be validated. Read the tenant from the request URL first, so each discovery response identifies the tenant it was actually fetched for. The single-tenant path is unchanged. The regex is also relaxed from `\w+` to `[^/]+` so that tenant GUIDs (which contain hyphens) are matched. See: https://github.com/MicrosoftDocs/azure-docs/issues/113944
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
@guustgoossens is attempting to deploy a commit to the authjs Team on Vercel. A member of the Team first needs to authorize it. |
5 tasks
guustgoossens
added a commit
to guustgoossens/convex-auth
that referenced
this pull request
Jun 2, 2026
The previous re-discovery used `(\w+)` to extract the tenant segment from `as.issuer`, which doesn't match hyphenated tenant GUIDs like `8a2a7b4d-1234-5678-9abc-def012345678` — so when a caller configured the provider with a single-tenant authority, the match silently fell back to `"common"` and `.replace(tenantId, tid)` was a no-op. Switch to `[^/]+` so any URL path segment matches (authority names and GUIDs alike), and rewrite via the segment-anchored regex instead of a plain substring replace so we can't accidentally clobber an unrelated portion of the issuer URL. Matches the upstream fix in nextauthjs/next-auth#13440.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
☕️ Reasoning
The Microsoft Entra ID provider has been documented as supporting
multi-tenant sign-in (
/common,/organizations,/consumers) sincethe v2 endpoint was introduced, but in practice the multi-tenant
configurations fail discovery validation right before the ID token
would be accepted.
Background
Microsoft's OIDC discovery document is templated — for every tenant
URL, the response is:
{ "issuer": "https://login.microsoftonline.com/{tenantid}/v2.0", ... }The
[customFetch]hook inmicrosoft-entra-id.tssubstitutes the{tenantid}placeholder so the document can be processed byoauth4webapi. Today that substitution reads the tenant fromconfig.issueronly, defaulting to"common":Why this breaks multi-tenant
handleOAuth()already understands that the issuer on the returned IDtoken contains the actual tenant from the user that authenticated,
and re-runs discovery against that tenant
(callback.ts#L188-L224):
That second discovery is issued against
https://login.microsoftonline.com/<actual-tid>/v2.0/.well-known/openid-configuration,but the customFetch hook ignores the request URL and rewrites the
response issuer to
https://login.microsoftonline.com/common/v2.0(because
config.issuerstill points at/common).processDiscoveryResponsethen throws — the returned issuer does notmatch the expected issuer URL it was asked to discover, so the ID token
is never validated.
A second, smaller bug: the regex
\w+does not match hyphens, so aconfigured single-tenant issuer set to a real tenant GUID (e.g.
8a2a7b4d-1234-5678-9abc-def012345678) fails to match too and silentlyfalls back to
"common". The relaxed[^/]+covers both shapes.Fix
Derive the tenant from the request URL first, so each discovery
response identifies the tenant it was actually fetched for. The
single-tenant path is unchanged. Existing re-discovery in
callback.tsnow works as intended.Before / After
issuerunset"response" body "issuer" property does not match the expected valueissmatches per-tenant issuer/organizationsissuer: ".../organizations/v2.0"/consumersissuer: ".../consumers/v2.0"issuer: ".../<tenant-guid>/v2.0""common"if the customFetch path is exercised twice)Reproduction
MicrosoftEntraIDwithout anissuer(the documentedmulti-tenant configuration).
home tenant.
discovery error about the issuer not matching the expected value.
Tests
Added
packages/core/test/providers/microsoft-entra-id.test.ts:/commondiscovery rewrites issuer to/common/v2.0./organizationsand/consumersURLs rewrite to their respectivetenant strings.
config.issuerset to a tenant GUID)is preserved through discovery.
fetchunchanged.All 152 existing tests in
@auth/corestill pass:🧢 Checklist
/organizationsand/consumerssupport and per-tenant issuer validation)🎫 Affected issues
References:
acknowledges the
{tenantid}placeholder in the v2 discoverydocument)
📌 Resources
iss,tid)