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 @@
+