diff --git a/src/client/auth.ts b/src/client/auth.ts index ab8aff0c..88d5f57c 100644 --- a/src/client/auth.ts +++ b/src/client/auth.ts @@ -605,6 +605,30 @@ async function discoverMetadataWithFallback( return response; } +/** + * Using metadata, identifies common issuers that need special handling. + * Only for large, unusual issuers, fully spec compliant issuers should not be identified, small issuers should not be given special treatment. + * e.g. Azure no PKCE advertised, scope param instead of resource param. + */ +function identifyQuirkyIssuer(metadata: AuthorizationServerMetadata): "azure_v2" | undefined { + const issuerString = metadata.issuer; + let issuerUrl: URL; + // Parse issuer URL and treat failed parse as normal issuer. + try { + issuerUrl = new URL(issuerString); + } catch (e) { + if (e instanceof TypeError && e.message === "Invalid URL") { + return undefined; + } + throw e; + } + // Check for known issuer types needing conditional handling + if (issuerUrl.hostname === "login.microsoftonline.com" && issuerUrl.pathname.endsWith('/v2.0')) { + return "azure_v2"; + } + return undefined; +} + /** * Looks up RFC 8414 OAuth 2.0 Authorization Server Metadata. * @@ -778,6 +802,10 @@ export async function discoverAuthorizationServerMetadata( return OAuthMetadataSchema.parse(await response.json()); } else { const metadata = OpenIdProviderDiscoveryMetadataSchema.parse(await response.json()); + // Azure Bypass + if (identifyQuirkyIssuer(metadata) === "azure_v2" && !metadata.code_challenge_methods_supported) { + metadata.code_challenge_methods_supported = ["S256"]; + } // MCP spec requires OIDC providers to support S256 PKCE if (!metadata.code_challenge_methods_supported?.includes('S256')) { @@ -869,7 +897,11 @@ export async function startAuthorization( } if (resource) { - authorizationUrl.searchParams.set("resource", resource.href); + if (metadata && identifyQuirkyIssuer(metadata) === "azure_v2") { + authorizationUrl.searchParams.set("scope", `${resource.href}/.default`); + } else { + authorizationUrl.searchParams.set("resource", resource.href); + } } return { authorizationUrl, codeVerifier }; @@ -947,7 +979,11 @@ export async function exchangeAuthorization( } if (resource) { - params.set("resource", resource.href); + if (metadata && identifyQuirkyIssuer(metadata) === "azure_v2") { + params.set("scope", `${resource.href}/.default`); + } else { + params.set("resource", resource.href); + } } const response = await (fetchFn ?? fetch)(tokenUrl, { @@ -1031,7 +1067,11 @@ export async function refreshAuthorization( } if (resource) { - params.set("resource", resource.href); + if (metadata && identifyQuirkyIssuer(metadata) === "azure_v2") { + params.set("scope", `${resource.href}/.default`); + } else { + params.set("resource", resource.href); + } } const response = await (fetchFn ?? fetch)(tokenUrl, {