diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirer.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirer.cs index 428f5fff5..cbfce0d69 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirer.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquirer.cs @@ -78,6 +78,7 @@ async Task ITokenAcquirer.GetTokenForAppAsync(string scope, PopPublicKey = tokenAcquisitionOptions.PopPublicKey, PopClaim = tokenAcquisitionOptions.PopClaim, ExtraParameters = tokenAcquisitionOptions.ExtraParameters, + ManagedIdentity = tokenAcquisitionOptions.ManagedIdentity, FmiPath = tokenAcquisitionOptions.FmiPath }; } diff --git a/tests/Microsoft.Identity.Web.Test/TokenAcquirerTests.cs b/tests/Microsoft.Identity.Web.Test/TokenAcquirerTests.cs index d9a1c776c..91e4c6167 100644 --- a/tests/Microsoft.Identity.Web.Test/TokenAcquirerTests.cs +++ b/tests/Microsoft.Identity.Web.Test/TokenAcquirerTests.cs @@ -261,6 +261,92 @@ public async Task GetTokenForUserAsync_WhenEffectiveKeyIsNullOrEmpty_DoesNotOver Assert.Equal(originalKey, callerOptions.LongRunningWebApiSessionKey); } + [Fact] + public async Task GetTokenForAppAsync_WithManagedIdentity_PreservesManagedIdentityInEffectiveOptions() + { + // Arrange + const string userAssignedClientId = "11111111-1111-1111-1111-111111111111"; + var authResult = CreateMockAuthenticationResult(); + TokenAcquisitionOptions? capturedOptions = null; + + _tokenAcquisition.GetAuthenticationResultForAppAsync( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .Returns(callInfo => + { + capturedOptions = callInfo.ArgAt(3); + return Task.FromResult(authResult); + }); + + var callerOptions = new AcquireTokenOptions + { + ManagedIdentity = new ManagedIdentityOptions + { + UserAssignedClientId = userAssignedClientId, + }, + }; + + var tokenAcquirer = new TokenAcquirer(_tokenAcquisition, _authenticationScheme); + + // Act + await ((ITokenAcquirer)tokenAcquirer).GetTokenForAppAsync( + _scope, + callerOptions, + CancellationToken.None); + + // Assert - the ManagedIdentity must flow through to the effective TokenAcquisitionOptions, + // otherwise the request silently falls back to a confidential client (regression guard). + Assert.NotNull(capturedOptions); + Assert.NotNull(capturedOptions!.ManagedIdentity); + Assert.Equal(userAssignedClientId, capturedOptions.ManagedIdentity!.UserAssignedClientId); + } + + [Fact] + public async Task GetTokenForUserAsync_WithManagedIdentity_PreservesManagedIdentityInEffectiveOptions() + { + // Arrange + const string userAssignedClientId = "11111111-1111-1111-1111-111111111111"; + var authResult = CreateMockAuthenticationResult(); + TokenAcquisitionOptions? capturedOptions = null; + + _tokenAcquisition.GetAuthenticationResultForUserAsync( + Arg.Any>(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()) + .Returns(callInfo => + { + capturedOptions = callInfo.ArgAt(5); + return Task.FromResult(authResult); + }); + + var callerOptions = new AcquireTokenOptions + { + ManagedIdentity = new ManagedIdentityOptions + { + UserAssignedClientId = userAssignedClientId, + }, + }; + + var tokenAcquirer = new TokenAcquirer(_tokenAcquisition, _authenticationScheme); + + // Act + await ((ITokenAcquirer)tokenAcquirer).GetTokenForUserAsync( + new[] { _scope }, + callerOptions, + user: null, + CancellationToken.None); + + // Assert + Assert.NotNull(capturedOptions); + Assert.NotNull(capturedOptions!.ManagedIdentity); + Assert.Equal(userAssignedClientId, capturedOptions.ManagedIdentity!.UserAssignedClientId); + } + private AuthenticationResult CreateMockAuthenticationResult(X509Certificate2? bindingCertificate = null) { var authResult = new AuthenticationResult(