Skip to content

Commit 6bab3bf

Browse files
more fixes
1 parent 25a4ac9 commit 6bab3bf

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientAssertionTests.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,16 @@ public async Task ClientAssertion_BearerAsync()
353353
var result = await cca.AcquireTokenForClient(TestConstants.s_scope)
354354
.ExecuteAsync().ConfigureAwait(false);
355355

356+
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
357+
356358
Assert.AreEqual(
357359
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
358360
handler.ActualRequestPostData["client_assertion_type"]);
361+
362+
result = await cca.AcquireTokenForClient(TestConstants.s_scope)
363+
.ExecuteAsync().ConfigureAwait(false);
364+
365+
Assert.AreEqual(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource);
359366
}
360367

361368
[TestMethod]
@@ -509,6 +516,73 @@ public async Task WithMtlsPop_AfterPoPDelegate_Works()
509516
}
510517
}
511518

519+
[TestMethod]
520+
public async Task PoP_CachedTokenWithDifferentCertificate_IsBypassedAsync()
521+
{
522+
const string region = "eastus";
523+
524+
// ─────────── Set up HTTP mocks ───────────
525+
using var httpManager = new MockHttpManager();
526+
using (var envContext = new EnvVariableContext())
527+
{
528+
Environment.SetEnvironmentVariable("REGION_NAME", region);
529+
530+
// 1st network call returns token‑A
531+
httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(
532+
tokenType: "mtls_pop");
533+
534+
// 2nd network call returns token‑B
535+
httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(
536+
tokenType: "mtls_pop");
537+
538+
// ─────────── Two distinct certificates ───────────
539+
var certA = CertHelper.GetOrCreateTestCert();
540+
var certB = CertHelper.GetOrCreateTestCert(regenerateCert: true);
541+
542+
// Delegate returns certA on first call, certB on second call
543+
int callCount = 0;
544+
Func<AssertionRequestOptions, CancellationToken, Task<AssertionResponse>> popDelegate =
545+
(opts, ct) =>
546+
{
547+
callCount++;
548+
var cert = (callCount == 1) ? certA : certB;
549+
return Task.FromResult(new AssertionResponse
550+
{
551+
Assertion = $"jwt_{callCount}", // payload not important for this test
552+
TokenBindingCertificate = cert
553+
});
554+
};
555+
556+
// ─────────── Build the app ───────────
557+
var cca = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
558+
.WithClientSecret(TestConstants.ClientSecret)
559+
.WithClientAssertion(popDelegate)
560+
.WithAuthority($"https://login.microsoftonline.com/123456-1234-2345-1234561234")
561+
.WithAzureRegion(ConfidentialClientApplication.AttemptRegionDiscovery)
562+
.WithHttpManager(httpManager)
563+
.BuildConcrete();
564+
565+
// ─────────── First acquire – network call, caches token‑A bound to certA ───────────
566+
AuthenticationResult first = await cca.AcquireTokenForClient(TestConstants.s_scope)
567+
.WithMtlsProofOfPossession()
568+
.ExecuteAsync()
569+
.ConfigureAwait(false);
570+
571+
Assert.AreEqual(TokenSource.IdentityProvider, first.AuthenticationResultMetadata.TokenSource);
572+
Assert.AreEqual(certA.Thumbprint, first.BindingCertificate.Thumbprint);
573+
574+
// ─────────── Second acquire – delegate now returns certB ───────────
575+
AuthenticationResult second = await cca.AcquireTokenForClient(TestConstants.s_scope)
576+
.WithMtlsProofOfPossession()
577+
.ExecuteAsync()
578+
.ConfigureAwait(false);
579+
580+
// The serial number mismatch should have forced a network call, not a cache hit
581+
Assert.AreEqual(TokenSource.IdentityProvider, second.AuthenticationResultMetadata.TokenSource);
582+
Assert.AreEqual(certB.Thumbprint, second.BindingCertificate.Thumbprint);
583+
}
584+
}
585+
512586
[TestMethod]
513587
public async Task WithMtlsPop_AfterBearerDelegate_Throws()
514588
{

tests/Microsoft.Identity.Test.Unit/PublicApiTests/MtlsPopTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,74 @@ public async Task AcquireMtlsPopTokenForClientWithTenantId_SuccessAsync()
328328
}
329329
}
330330

331+
[TestMethod]
332+
public async Task AcquireMtlsPopTokenForClientWithTenantIdCertChecks_Async()
333+
{
334+
const string region = "eastus";
335+
336+
// ─────────── Two distinct certificates ───────────
337+
var certA = CertHelper.GetOrCreateTestCert();
338+
var certB = CertHelper.GetOrCreateTestCert(regenerateCert: true);
339+
340+
using (var envContext = new EnvVariableContext())
341+
{
342+
Environment.SetEnvironmentVariable("REGION_NAME", region);
343+
344+
// Set the expected mTLS endpoint for public cloud
345+
string globalEndpoint = "mtlsauth.microsoft.com";
346+
string expectedTokenEndpoint = $"https://{region}.{globalEndpoint}/123456-1234-2345-1234561234/oauth2/v2.0/token";
347+
348+
using (var httpManager = new MockHttpManager())
349+
{
350+
// Set up mock handler with expected token endpoint URL
351+
httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(
352+
tokenType: "mtls_pop");
353+
354+
var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
355+
.WithCertificate(certA)
356+
.WithTenantId("123456-1234-2345-1234561234")
357+
.WithAzureRegion(ConfidentialClientApplication.AttemptRegionDiscovery)
358+
.WithHttpManager(httpManager)
359+
.BuildConcrete();
360+
361+
// First token acquisition - should hit the identity provider
362+
AuthenticationResult result = await app.AcquireTokenForClient(TestConstants.s_scope)
363+
.WithMtlsProofOfPossession()
364+
.ExecuteAsync()
365+
.ConfigureAwait(false);
366+
367+
Assert.AreEqual("header.payload.signature", result.AccessToken);
368+
Assert.AreEqual(Constants.MtlsPoPAuthHeaderPrefix, result.TokenType);
369+
Assert.AreEqual(region, result.AuthenticationResultMetadata.RegionDetails.RegionUsed);
370+
Assert.AreEqual(expectedTokenEndpoint, result.AuthenticationResultMetadata.TokenEndpoint);
371+
Assert.AreEqual(certA.Thumbprint, result.BindingCertificate.Thumbprint);
372+
373+
app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
374+
.WithCertificate(certB)
375+
.WithTenantId("123456-1234-2345-1234561234")
376+
.WithAzureRegion(ConfidentialClientApplication.AttemptRegionDiscovery)
377+
.WithHttpManager(httpManager)
378+
.BuildConcrete();
379+
380+
// Set up mock handler with expected token endpoint URL
381+
httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(
382+
tokenType: "mtls_pop");
383+
384+
// Second token acquisition - should also be from IDP because we have a new cert
385+
AuthenticationResult secondResult = await app.AcquireTokenForClient(TestConstants.s_scope)
386+
.WithMtlsProofOfPossession()
387+
.ExecuteAsync()
388+
.ConfigureAwait(false);
389+
390+
Assert.AreEqual("header.payload.signature", secondResult.AccessToken);
391+
Assert.AreEqual(Constants.MtlsPoPAuthHeaderPrefix, secondResult.TokenType);
392+
Assert.AreEqual(TokenSource.IdentityProvider, secondResult.AuthenticationResultMetadata.TokenSource);
393+
Assert.AreEqual(expectedTokenEndpoint, result.AuthenticationResultMetadata.TokenEndpoint);
394+
Assert.AreEqual(certB.Thumbprint, secondResult.BindingCertificate.Thumbprint);
395+
}
396+
}
397+
}
398+
331399
[TestMethod]
332400
public async Task MtlsPop_KnownRegionAsync()
333401
{

0 commit comments

Comments
 (0)