diff --git a/build/credscan-exclusion.json b/build/credscan-exclusion.json index 1a9e9038d..2d5203ce2 100644 --- a/build/credscan-exclusion.json +++ b/build/credscan-exclusion.json @@ -20,6 +20,10 @@ { "file": "*InternalAPI.Unshipped.txt", "_justification": "Unshipped public API." + }, + { + "file": "ClaimConstants.cs", + "_justification": "Constant contains the word Password as it is for the ROPC flow password claim constant." } ] } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs index 87357b93e..2e42e181c 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs @@ -99,5 +99,15 @@ public static class ClaimConstants /// Name Identifier ID claim: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier". /// public const string NameIdentifierId = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"; + + /// + /// Username claims for ROPC flow. + /// + public const string Username = "xms_username"; + + /// + /// Password claims for ROPC flow. + /// + public const string Password = "xms_password"; } } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt index 929635dc2..d1805fb40 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string! +const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string! Microsoft.Identity.Web.BeforeTokenAcquisitionForApp Microsoft.Identity.Web.TokenAcquisitionExtensionOptions Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt index 929635dc2..d1805fb40 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string! +const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string! Microsoft.Identity.Web.BeforeTokenAcquisitionForApp Microsoft.Identity.Web.TokenAcquisitionExtensionOptions Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/PublicAPI.Unshipped.txt index 929635dc2..d1805fb40 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string! +const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string! Microsoft.Identity.Web.BeforeTokenAcquisitionForApp Microsoft.Identity.Web.TokenAcquisitionExtensionOptions Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/PublicAPI.Unshipped.txt index 929635dc2..d1805fb40 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string! +const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string! Microsoft.Identity.Web.BeforeTokenAcquisitionForApp Microsoft.Identity.Web.TokenAcquisitionExtensionOptions Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 929635dc2..d1805fb40 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ #nullable enable +const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string! +const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string! Microsoft.Identity.Web.BeforeTokenAcquisitionForApp Microsoft.Identity.Web.TokenAcquisitionExtensionOptions Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 30834cec9..5d7a3e9da 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,3 +1,5 @@ +const Microsoft.Identity.Web.ClaimConstants.Password = "xms_password" -> string! +const Microsoft.Identity.Web.ClaimConstants.Username = "xms_username" -> string! Microsoft.Identity.Web.BeforeTokenAcquisitionForApp Microsoft.Identity.Web.TokenAcquisitionExtensionOptions Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForApp -> Microsoft.Identity.Web.BeforeTokenAcquisitionForApp? diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 721772528..a97db66da 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -254,6 +254,21 @@ public async Task GetAuthenticationResultForUserAsync( try { AuthenticationResult? authenticationResult; + + // If the user is not null and has claims xms-username and xms-password, perform ROPC for CCA + authenticationResult = await TryGetAuthenticationResultForConfidentialClientUsingRopcAsync( + application, + scopes, + user, + mergedOptions, + tokenAcquisitionOptions).ConfigureAwait(false); + + if (authenticationResult != null) + { + LogAuthResult(authenticationResult); + return authenticationResult; + } + // Access token will return if call is from a web API authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync( application, @@ -313,6 +328,59 @@ public async Task GetAuthenticationResultForUserAsync( } } + // This method mutate the user claims to include claims uid and utid to perform the silent flow for subsequent calls. + private async Task TryGetAuthenticationResultForConfidentialClientUsingRopcAsync(IConfidentialClientApplication application, IEnumerable scopes, ClaimsPrincipal? user, MergedOptions mergedOptions, TokenAcquisitionOptions? tokenAcquisitionOptions) + { + if (user != null && user.HasClaim(c => c.Type == ClaimConstants.Username) && user.HasClaim(c => c.Type == ClaimConstants.Password)) + { + string username = user.FindFirst(ClaimConstants.Username)?.Value ?? string.Empty; + string password = user.FindFirst(ClaimConstants.Password)?.Value ?? string.Empty; + + if (user.GetMsalAccountId() != null) + { + try + { + var account = await application.GetAccountAsync(user.GetMsalAccountId()).ConfigureAwait(false); + + // Silent flow + return await application.AcquireTokenSilent( + scopes.Except(_scopesRequestedByMsal), + account) + .ExecuteAsync() + .ConfigureAwait(false); + } + catch (MsalException ex) + { + // Log a message when the silent flow fails and try acquisition through ROPC. + Logger.TokenAcquisitionError(_logger, ex.Message, ex); + } + + } + + // ROPC flow + var authenticationResult = await ((IByUsernameAndPassword)application).AcquireTokenByUsernamePassword( + scopes.Except(_scopesRequestedByMsal), + username, + password) + .ExecuteAsync() + .ConfigureAwait(false); + + if (user.GetMsalAccountId() == null) + { + // Add the account id to the user (in case of ROPC flow) + user.AddIdentity(new CaseSensitiveClaimsIdentity(new[] + { + new Claim(ClaimConstants.UniqueObjectIdentifier, authenticationResult.Account.HomeAccountId.ObjectId), + new Claim(ClaimConstants.UniqueTenantIdentifier, authenticationResult.Account.HomeAccountId.TenantId), + })); + } + + return authenticationResult; + } + + return null; + } + private void LogAuthResult(AuthenticationResult? authenticationResult) { if (authenticationResult != null) diff --git a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs index 79070dd96..fabef065b 100644 --- a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs +++ b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs @@ -1,99 +1,99 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Security.Claims; -using Microsoft.IdentityModel.Tokens; - -namespace Microsoft.Identity.Web -{ - /// - /// Factory class to create objects. - /// - public static class ClaimsPrincipalFactory - { - /// - /// Instantiate a from a home account object ID and home tenant ID. This can - /// be useful when the web app subscribes to another service on behalf of the user - /// and then is called back by a notification where the user is identified by their home tenant - /// ID and home object ID (like in Microsoft Graph Web Hooks). - /// - /// Home tenant ID of the account. - /// Home object ID of the account in this tenant ID. - /// A containing these two claims. - /// - /// - /// - /// private async Task GetChangedMessagesAsync(IEnumerable<Notification> notifications) - /// { - /// HttpContext.User = ClaimsPrincipalExtension.FromHomeTenantIdAndHomeObjectId(subscription.HomeTenantId, - /// subscription.HomeUserId); - /// foreach (var notification in notifications) - /// { - /// SubscriptionStore subscription = - /// subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); - /// string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); - /// ...} - /// } - /// - /// - public static ClaimsPrincipal FromHomeTenantIdAndHomeObjectId(string homeTenantId, string homeObjectId) - { +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; + +namespace Microsoft.Identity.Web +{ + /// + /// Factory class to create objects. + /// + public static class ClaimsPrincipalFactory + { + /// + /// Instantiate a from a home account object ID and home tenant ID. This can + /// be useful when the web app subscribes to another service on behalf of the user + /// and then is called back by a notification where the user is identified by their home tenant + /// ID and home object ID (like in Microsoft Graph Web Hooks). + /// + /// Home tenant ID of the account. + /// Home object ID of the account in this tenant ID. + /// A containing these two claims. + /// + /// + /// + /// private async Task GetChangedMessagesAsync(IEnumerable<Notification> notifications) + /// { + /// HttpContext.User = ClaimsPrincipalExtension.FromHomeTenantIdAndHomeObjectId(subscription.HomeTenantId, + /// subscription.HomeUserId); + /// foreach (var notification in notifications) + /// { + /// SubscriptionStore subscription = + /// subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); + /// string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); + /// ...} + /// } + /// + /// + public static ClaimsPrincipal FromHomeTenantIdAndHomeObjectId(string homeTenantId, string homeObjectId) + { if (AppContextSwitches.UseClaimsIdentityType) { #pragma warning disable RS0030 // Do not use banned APIs return new ClaimsPrincipal( new ClaimsIdentity(new[] - { - new Claim(ClaimConstants.UniqueTenantIdentifier, homeTenantId), + { + new Claim(ClaimConstants.UniqueTenantIdentifier, homeTenantId), new Claim(ClaimConstants.UniqueObjectIdentifier, homeObjectId), })); #pragma warning restore RS0030 // Do not use banned APIs } else - { - return new ClaimsPrincipal( - new CaseSensitiveClaimsIdentity(new[] - { - new Claim(ClaimConstants.UniqueTenantIdentifier, homeTenantId), - new Claim(ClaimConstants.UniqueObjectIdentifier, homeObjectId), - })); - } - } - - /// - /// Instantiate a from an account object ID and tenant ID. This can - /// be useful when the web app subscribes to another service on behalf of the user - /// and then is called back by a notification where the user is identified by their tenant - /// ID and object ID (like in Microsoft Graph Web Hooks). - /// - /// Tenant ID of the account. - /// Object ID of the account in this tenant ID. - /// A containing these two claims. - /// - /// - /// - /// private async Task GetChangedMessagesAsync(IEnumerable<Notification> notifications) - /// { - /// HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, - /// subscription.UserId); - /// foreach (var notification in notifications) - /// { - /// SubscriptionStore subscription = - /// subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); - /// string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); - /// ...} - /// } - /// - /// - public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string objectId) - { + { + return new ClaimsPrincipal( + new CaseSensitiveClaimsIdentity(new[] + { + new Claim(ClaimConstants.UniqueTenantIdentifier, homeTenantId), + new Claim(ClaimConstants.UniqueObjectIdentifier, homeObjectId), + })); + } + } + + /// + /// Instantiate a from an account object ID and tenant ID. This can + /// be useful when the web app subscribes to another service on behalf of the user + /// and then is called back by a notification where the user is identified by their tenant + /// ID and object ID (like in Microsoft Graph Web Hooks). + /// + /// Tenant ID of the account. + /// Object ID of the account in this tenant ID. + /// A containing these two claims. + /// + /// + /// + /// private async Task GetChangedMessagesAsync(IEnumerable<Notification> notifications) + /// { + /// HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, + /// subscription.UserId); + /// foreach (var notification in notifications) + /// { + /// SubscriptionStore subscription = + /// subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); + /// string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); + /// ...} + /// } + /// + /// + public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string objectId) + { if (AppContextSwitches.UseClaimsIdentityType) { #pragma warning disable RS0030 // Do not use banned APIs return new ClaimsPrincipal( new ClaimsIdentity(new[] - { - new Claim(ClaimConstants.Tid, tenantId), + { + new Claim(ClaimConstants.Tid, tenantId), new Claim(ClaimConstants.Oid, objectId), })); #pragma warning restore RS0030 // Do not use banned APIs @@ -101,11 +101,28 @@ public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string ob { return new ClaimsPrincipal( new CaseSensitiveClaimsIdentity(new[] - { - new Claim(ClaimConstants.Tid, tenantId), + { + new Claim(ClaimConstants.Tid, tenantId), new Claim(ClaimConstants.Oid, objectId), })); - } - } - } -} + } + } + + /// + /// Instantiate a from a username and password. + /// This can be used for ROPC flow for testing purposes. + /// + /// UPN of the user for example username@domain. + /// Password for the user. + /// A containing these two claims. + public static ClaimsPrincipal FromUsernamePassword(string username, string password) + { + return new ClaimsPrincipal( + new CaseSensitiveClaimsIdentity(new[] + { + new Claim(ClaimConstants.Username, username), + new Claim(ClaimConstants.Password, password), + })); + } + } +} diff --git a/src/Microsoft.Identity.Web/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web/PublicAPI/net462/PublicAPI.Unshipped.txt index e69de29bb..1fcf3fa79 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +#nullable enable +static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! diff --git a/src/Microsoft.Identity.Web/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web/PublicAPI/net472/PublicAPI.Unshipped.txt index e69de29bb..1fcf3fa79 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +#nullable enable +static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! diff --git a/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt index e69de29bb..1fcf3fa79 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +#nullable enable +static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! diff --git a/src/Microsoft.Identity.Web/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web/PublicAPI/net7.0/PublicAPI.Unshipped.txt index e69de29bb..1fcf3fa79 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net7.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +#nullable enable +static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! diff --git a/src/Microsoft.Identity.Web/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web/PublicAPI/net8.0/PublicAPI.Unshipped.txt index e69de29bb..1fcf3fa79 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +#nullable enable +static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! diff --git a/src/Microsoft.Identity.Web/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web/PublicAPI/net9.0/PublicAPI.Unshipped.txt index e69de29bb..1fcf3fa79 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net9.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net9.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +#nullable enable +static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! diff --git a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs index fd2c7a639..aa48492a7 100644 --- a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs +++ b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs @@ -12,7 +12,9 @@ using Microsoft.Extensions.Options; using Microsoft.Graph; using Microsoft.Identity.Abstractions; +using Microsoft.Identity.Lab.Api; using Microsoft.Identity.Web; +using Microsoft.Identity.Web.Test.Common; using Microsoft.Identity.Web.TokenCacheProviders.InMemory; using Microsoft.IdentityModel.Tokens; using Xunit; @@ -91,6 +93,38 @@ public void AcquireToken_WithMultipleRegions() Assert.NotEqual(tokenAcquirerA, tokenAcquirerC); } + [Fact] + public async Task AcquireToken_ROPC_CCAasync() + { + var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance(); + _ = tokenAcquirerFactory.Build(); + + var labResponse = await LabUserHelper.GetDefaultUserAsync(); + + ITokenAcquirer tokenAcquirer = tokenAcquirerFactory.GetTokenAcquirer( + authority: "https://login.microsoftonline.com/organizations", + clientId: "88f91eac-c606-4c67-a0e2-a5e8a186854f", + clientCredentials: new[] + { + CertificateDescription.FromStoreWithDistinguishedName("CN=LabAuth.MSIDLab.com") + }); + + var user = ClaimsPrincipalFactory.FromUsernamePassword(labResponse.User.Upn, labResponse.User.GetOrFetchPassword()); + + var result = await tokenAcquirer.GetTokenForUserAsync( + scopes: new[] { "https://graph.microsoft.com/.default" }, user: user); + + Assert.NotNull(result); + Assert.NotNull(result.AccessToken); + + var result2 = await tokenAcquirer.GetTokenForUserAsync( + scopes: new[] { "https://graph.microsoft.com/.default" }, user: user); + + Assert.NotNull(result2); + Assert.NotNull(result2.AccessToken); + Assert.Equal(result.AccessToken, result2.AccessToken); + } + [Fact] public void AcquireToken_SafeFromMultipleThreads() { diff --git a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj index f2b7bec4b..2f003abdd 100644 --- a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj +++ b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj @@ -7,6 +7,7 @@ + @@ -19,6 +20,7 @@ +