Skip to content

Commit 3377be3

Browse files
authored
Adjust issuer validation to accept differing paths (#5466)
* Adjust issuer validation to only check host and scheme, not path * Make test better match bug report * Make test better match bug report * PR feedback
1 parent 27fbbdf commit 3377be3

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

src/client/Microsoft.Identity.Client/Instance/Oidc/OidcRetrieverWithCache.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,17 @@ private static void ValidateIssuer(Uri authority, string issuer)
8787
string normalizedAuthority = authority.AbsoluteUri.TrimEnd('/');
8888
string normalizedIssuer = issuer?.TrimEnd('/');
8989

90-
// Primary validation: check if normalized authority starts with normalized issuer (case-insensitive comparison)
91-
if (normalizedAuthority.StartsWith(normalizedIssuer, StringComparison.OrdinalIgnoreCase))
90+
// OIDC validation: if the issuer's scheme and host match the authority's, consider it valid
91+
if (!string.IsNullOrEmpty(issuer) && Uri.TryCreate(issuer, UriKind.Absolute, out Uri issuerUri))
9292
{
93-
return;
93+
if (string.Equals(authority.Scheme, issuerUri.Scheme, StringComparison.OrdinalIgnoreCase) &&
94+
string.Equals(authority.Host, issuerUri.Host, StringComparison.OrdinalIgnoreCase))
95+
{
96+
return;
97+
}
9498
}
9599

96-
// Extract tenant for CIAM scenarios. In a CIAM scenario the issuer is expected to have "{tenant}.ciamlogin.com"
100+
// CIAM-specific validation: In a CIAM scenario the issuer is expected to have "{tenant}.ciamlogin.com"
97101
// as the host, even when using a custom domain.
98102
string tenant = null;
99103
try

tests/Microsoft.Identity.Test.Unit/CoreTests/InstanceTests/GenericAuthorityTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,56 @@ public async Task BadOidcResponse_ThrowsException_Async(string badOidcResponseTy
333333
}
334334
}
335335

336+
[TestMethod]
337+
public async Task OidcIssuerValidation_AcceptsDifferentPath_Async()
338+
{
339+
using (var httpManager = new MockHttpManager())
340+
{
341+
// This test was made to cover an issue that realistically would only happen with Microsoft authorities in multi-tenant scenarios,
342+
// so it uses a Microsoft host instead of the custom domain used in other tests.
343+
string microsoftHost = "login.microsoftonline.com";
344+
string authority = $"https://{microsoftHost}/organizations/2.0/";
345+
string issuerWithDifferentPath = $"https://{microsoftHost}/someTenant/2.0/";
346+
347+
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
348+
.Create(TestConstants.ClientId)
349+
.WithHttpManager(httpManager)
350+
.WithOidcAuthority(authority)
351+
.WithClientSecret(TestConstants.ClientSecret)
352+
.Build();
353+
354+
// Create OIDC document with a Microsoft host and an issuer that has matching host but different path
355+
string oidcDocumentWithDifferentPath = TestConstants.GenericOidcResponse.Replace(
356+
$"\"issuer\":\"{TestConstants.GenericAuthority}\"",
357+
$"\"issuer\":\"{issuerWithDifferentPath}\"");
358+
oidcDocumentWithDifferentPath = oidcDocumentWithDifferentPath.Replace(
359+
"demo.duendesoftware.com",
360+
microsoftHost);
361+
362+
// Mock OIDC endpoint response
363+
httpManager.AddMockHandler(new MockHttpMessageHandler
364+
{
365+
ExpectedMethod = HttpMethod.Get,
366+
ExpectedUrl = $"{authority}{Constants.WellKnownOpenIdConfigurationPath}",
367+
ResponseMessage = MockHelpers.CreateSuccessResponseMessage(oidcDocumentWithDifferentPath)
368+
});
369+
370+
// Mock token endpoint response
371+
httpManager.AddMockHandler(
372+
CreateTokenResponseHttpHandler(
373+
$"https://{microsoftHost}/connect/token",
374+
scopesInRequest: "api",
375+
scopesInResponse: "api",
376+
grant: "client_credentials"));
377+
378+
// Should not throw an exception with our updated validation
379+
var result = await app.AcquireTokenForClient(new[] { "api" }).ExecuteAsync().ConfigureAwait(false);
380+
381+
Assert.IsNotNull(result);
382+
Assert.IsNotNull(result.AccessToken);
383+
}
384+
}
385+
336386
[TestMethod]
337387
public async Task OidcIssuerValidation_ThrowsForNonMatchingIssuer_Async()
338388
{

0 commit comments

Comments
 (0)