From 424d0c4d5698f9d21b731cc7ab4d5b8c888fd10e Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Wed, 23 Oct 2024 10:24:41 -0700 Subject: [PATCH 01/15] Add API and make ROPC call --- .../ClaimConstants.cs | 10 ++++++ .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 2 ++ .../TokenAcquisition.cs | 36 +++++++++++++++++++ .../ClaimsPrincipalFactory.cs | 31 ++++++++++++++++ .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 + .../TokenAcquirerTests/TokenAcquirer.cs | 27 ++++++++++++++ .../TokenAcquirerTests.csproj | 4 ++- 7 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs index 87357b93e..c40e12d0e 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 = "x-ms-username"; + + /// + /// Password claims for ROPC flow. + /// + public const string Password = "x-ms-password"; } } 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 f2ba07924..9934bf811 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 @@ -4,6 +4,7 @@ const Microsoft.Identity.Web.ClaimConstants.Name = "name" -> string! const Microsoft.Identity.Web.ClaimConstants.NameIdentifierId = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" -> string! const Microsoft.Identity.Web.ClaimConstants.ObjectId = "http://schemas.microsoft.com/identity/claims/objectidentifier" -> string! const Microsoft.Identity.Web.ClaimConstants.Oid = "oid" -> string! +const Microsoft.Identity.Web.ClaimConstants.Password = "x-ms-password" -> string! const Microsoft.Identity.Web.ClaimConstants.PreferredUserName = "preferred_username" -> string! const Microsoft.Identity.Web.ClaimConstants.Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" -> string! const Microsoft.Identity.Web.ClaimConstants.Roles = "roles" -> string! @@ -16,6 +17,7 @@ const Microsoft.Identity.Web.ClaimConstants.Tid = "tid" -> string! const Microsoft.Identity.Web.ClaimConstants.UniqueObjectIdentifier = "uid" -> string! const Microsoft.Identity.Web.ClaimConstants.UniqueTenantIdentifier = "utid" -> string! const Microsoft.Identity.Web.ClaimConstants.UserFlow = "http://schemas.microsoft.com/claims/authnclassreference" -> string! +const Microsoft.Identity.Web.ClaimConstants.Username = "x-ms-username" -> string! const Microsoft.Identity.Web.Constants.AzureAd = "AzureAd" -> string! const Microsoft.Identity.Web.Constants.AzureAdB2C = "AzureAdB2C" -> string! const Microsoft.Identity.Web.Constants.Bearer = "Bearer" -> string! diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 655d708a6..773b97baa 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -251,6 +251,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 GetAuthenticationResultForConfidentialClientUsingRopcAsync( + 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, @@ -310,6 +325,27 @@ public async Task GetAuthenticationResultForUserAsync( } } + private async Task GetAuthenticationResultForConfidentialClientUsingRopcAsync(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; + + // ROPC flow + var authenticationResult = await ((IByUsernameAndPassword)application).AcquireTokenByUsernamePassword( + scopes.Except(_scopesRequestedByMsal), + username, + password) + .ExecuteAsync() + .ConfigureAwait(false); + + 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..532a3eaac 100644 --- a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs +++ b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs @@ -107,5 +107,36 @@ public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string ob })); } } + + /// + /// 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. + /// + public static ClaimsPrincipal FromUsernamePassword(string username, string password) + { + if (AppContextSwitches.UseClaimsIdentityType) + { +#pragma warning disable RS0030 // Do not use banned APIs + return new ClaimsPrincipal( + new ClaimsIdentity(new[] + { + new Claim(ClaimConstants.Username, username), + new Claim(ClaimConstants.Password, password), + })); +#pragma warning restore RS0030 // Do not use banned APIs + } + else + { + return new ClaimsPrincipal( + new CaseSensitiveClaimsIdentity(new[] + { + new Claim(ClaimConstants.Username, username), + new Claim(ClaimConstants.Password, password), + })); + } + } } } 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 dd6bf919d..1a22cb571 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -208,6 +208,7 @@ static Microsoft.Identity.Web.AppServicesAuthenticationInformation.LogoutUrl.get static Microsoft.Identity.Web.AzureFunctionsAuthenticationHttpContextExtension.AuthenticateAzureFunctionAsync(this Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task<(bool, Microsoft.AspNetCore.Mvc.IActionResult?)>! static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromHomeTenantIdAndHomeObjectId(string! homeTenantId, string! homeObjectId) -> System.Security.Claims.ClaimsPrincipal! static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromTenantIdAndObjectId(string! tenantId, string! objectId) -> System.Security.Claims.ClaimsPrincipal! +static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! static Microsoft.Identity.Web.CookiePolicyOptionsExtensions.DisallowsSameSiteNone(string! userAgent) -> bool static Microsoft.Identity.Web.CookiePolicyOptionsExtensions.HandleSameSiteCookieCompatibility(this Microsoft.AspNetCore.Builder.CookiePolicyOptions! options) -> Microsoft.AspNetCore.Builder.CookiePolicyOptions! static Microsoft.Identity.Web.CookiePolicyOptionsExtensions.HandleSameSiteCookieCompatibility(this Microsoft.AspNetCore.Builder.CookiePolicyOptions! options, System.Func! disallowsSameSiteNone) -> Microsoft.AspNetCore.Builder.CookiePolicyOptions! diff --git a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs index fd2c7a639..6d6da7c6b 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,31 @@ 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).ConfigureAwait(false); + + Assert.NotNull(result); + Assert.NotNull(result.AccessToken); + } + [Fact] public void AcquireToken_SafeFromMultipleThreads() { diff --git a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj index dbf237031..408f5a7b6 100644 --- a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj +++ b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj @@ -1,4 +1,4 @@ - + net7.0; net8.0 @@ -7,6 +7,7 @@ + @@ -23,6 +24,7 @@ + From bebff84ee49fc56781275aafae9508def5ff095f Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Wed, 23 Oct 2024 13:31:32 -0700 Subject: [PATCH 02/15] Add silent call before attempting ROPC --- .../TokenAcquisition.cs | 20 +++++++++++++++++++ .../TokenAcquirerTests/TokenAcquirer.cs | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 773b97baa..e026b10c7 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -332,6 +332,26 @@ public async Task GetAuthenticationResultForUserAsync( string username = user.FindFirst(ClaimConstants.Username)?.Value ?? string.Empty; string password = user.FindFirst(ClaimConstants.Password)?.Value ?? string.Empty; + var accounts = await application.GetAccountsAsync().ConfigureAwait(false); + var account = accounts.FirstOrDefault(account => account.Username == username); + + if (account != null) + { + try + { + // Silent flow + return await application.AcquireTokenSilent( + scopes.Except(_scopesRequestedByMsal), + account) + .ExecuteAsync() + .ConfigureAwait(false); + } + catch + { + // Ignore this exception as we will retry with ROPC + } + } + // ROPC flow var authenticationResult = await ((IByUsernameAndPassword)application).AcquireTokenByUsernamePassword( scopes.Except(_scopesRequestedByMsal), diff --git a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs index 6d6da7c6b..08f411448 100644 --- a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs +++ b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs @@ -116,6 +116,13 @@ public async Task AcquireToken_ROPC_CCAasync() Assert.NotNull(result); Assert.NotNull(result.AccessToken); + + var result2 = await tokenAcquirer.GetTokenForUserAsync( + scopes: new[] { "https://graph.microsoft.com/.default" }, user: user).ConfigureAwait(false); + + Assert.NotNull(result2); + Assert.NotNull(result2.AccessToken); + Assert.Equal(result.AccessToken, result2.AccessToken); } [Fact] From ad6d4f6b8e243187e1fcf247ba48581c3344389d Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Wed, 23 Oct 2024 13:35:03 -0700 Subject: [PATCH 03/15] Minor updates to constants and comments --- src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs | 4 ++-- src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs index c40e12d0e..9f82caa2f 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs @@ -103,11 +103,11 @@ public static class ClaimConstants /// /// Username claims for ROPC flow. /// - public const string Username = "x-ms-username"; + public const string Username = "xms-username"; /// /// Password claims for ROPC flow. /// - public const string Password = "x-ms-password"; + public const string Password = "xms-password"; } } diff --git a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs index 532a3eaac..327418912 100644 --- a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs +++ b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs @@ -114,7 +114,7 @@ public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string ob /// /// 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) { if (AppContextSwitches.UseClaimsIdentityType) From 3c20f845569717b0a6ed3be8b4a2cc79fb42b25f Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Wed, 23 Oct 2024 14:56:55 -0700 Subject: [PATCH 04/15] Undo changes to txt files --- .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 2 -- .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 - 2 files changed, 3 deletions(-) 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 9934bf811..f2ba07924 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 @@ -4,7 +4,6 @@ const Microsoft.Identity.Web.ClaimConstants.Name = "name" -> string! const Microsoft.Identity.Web.ClaimConstants.NameIdentifierId = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" -> string! const Microsoft.Identity.Web.ClaimConstants.ObjectId = "http://schemas.microsoft.com/identity/claims/objectidentifier" -> string! const Microsoft.Identity.Web.ClaimConstants.Oid = "oid" -> string! -const Microsoft.Identity.Web.ClaimConstants.Password = "x-ms-password" -> string! const Microsoft.Identity.Web.ClaimConstants.PreferredUserName = "preferred_username" -> string! const Microsoft.Identity.Web.ClaimConstants.Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" -> string! const Microsoft.Identity.Web.ClaimConstants.Roles = "roles" -> string! @@ -17,7 +16,6 @@ const Microsoft.Identity.Web.ClaimConstants.Tid = "tid" -> string! const Microsoft.Identity.Web.ClaimConstants.UniqueObjectIdentifier = "uid" -> string! const Microsoft.Identity.Web.ClaimConstants.UniqueTenantIdentifier = "utid" -> string! const Microsoft.Identity.Web.ClaimConstants.UserFlow = "http://schemas.microsoft.com/claims/authnclassreference" -> string! -const Microsoft.Identity.Web.ClaimConstants.Username = "x-ms-username" -> string! const Microsoft.Identity.Web.Constants.AzureAd = "AzureAd" -> string! const Microsoft.Identity.Web.Constants.AzureAdB2C = "AzureAdB2C" -> string! const Microsoft.Identity.Web.Constants.Bearer = "Bearer" -> string! 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 1a22cb571..dd6bf919d 100644 --- a/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Web/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -208,7 +208,6 @@ static Microsoft.Identity.Web.AppServicesAuthenticationInformation.LogoutUrl.get static Microsoft.Identity.Web.AzureFunctionsAuthenticationHttpContextExtension.AuthenticateAzureFunctionAsync(this Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task<(bool, Microsoft.AspNetCore.Mvc.IActionResult?)>! static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromHomeTenantIdAndHomeObjectId(string! homeTenantId, string! homeObjectId) -> System.Security.Claims.ClaimsPrincipal! static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromTenantIdAndObjectId(string! tenantId, string! objectId) -> System.Security.Claims.ClaimsPrincipal! -static Microsoft.Identity.Web.ClaimsPrincipalFactory.FromUsernamePassword(string! username, string! password) -> System.Security.Claims.ClaimsPrincipal! static Microsoft.Identity.Web.CookiePolicyOptionsExtensions.DisallowsSameSiteNone(string! userAgent) -> bool static Microsoft.Identity.Web.CookiePolicyOptionsExtensions.HandleSameSiteCookieCompatibility(this Microsoft.AspNetCore.Builder.CookiePolicyOptions! options) -> Microsoft.AspNetCore.Builder.CookiePolicyOptions! static Microsoft.Identity.Web.CookiePolicyOptionsExtensions.HandleSameSiteCookieCompatibility(this Microsoft.AspNetCore.Builder.CookiePolicyOptions! options, System.Func! disallowsSameSiteNone) -> Microsoft.AspNetCore.Builder.CookiePolicyOptions! From a96b9d81905f4fd215e8bc8725f0829365dfa25c Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Thu, 24 Oct 2024 12:49:25 -0700 Subject: [PATCH 05/15] Address comments --- .../TokenAcquisition.cs | 5 ++-- .../ClaimsPrincipalFactory.cs | 26 +++++-------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index e026b10c7..ba940225a 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -253,7 +253,7 @@ public async Task GetAuthenticationResultForUserAsync( AuthenticationResult? authenticationResult; // If the user is not null and has claims xms-username and xms-password, perform ROPC for CCA - authenticationResult = await GetAuthenticationResultForConfidentialClientUsingRopcAsync( + authenticationResult = await TryGetAuthenticationResultForConfidentialClientUsingRopcAsync( application, scopes, user, @@ -325,7 +325,7 @@ public async Task GetAuthenticationResultForUserAsync( } } - private async Task GetAuthenticationResultForConfidentialClientUsingRopcAsync(IConfidentialClientApplication application, IEnumerable scopes, ClaimsPrincipal? user, MergedOptions mergedOptions, TokenAcquisitionOptions? tokenAcquisitionOptions) + 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)) { @@ -350,6 +350,7 @@ public async Task GetAuthenticationResultForUserAsync( { // Ignore this exception as we will retry with ROPC } + } // ROPC flow diff --git a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs index 327418912..25899261a 100644 --- a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs +++ b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs @@ -117,26 +117,12 @@ public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string ob /// A containing these two claims. public static ClaimsPrincipal FromUsernamePassword(string username, string password) { - if (AppContextSwitches.UseClaimsIdentityType) - { -#pragma warning disable RS0030 // Do not use banned APIs - return new ClaimsPrincipal( - new ClaimsIdentity(new[] - { - new Claim(ClaimConstants.Username, username), - new Claim(ClaimConstants.Password, password), - })); -#pragma warning restore RS0030 // Do not use banned APIs - } - else - { - return new ClaimsPrincipal( - new CaseSensitiveClaimsIdentity(new[] - { - new Claim(ClaimConstants.Username, username), - new Claim(ClaimConstants.Password, password), - })); - } + return new ClaimsPrincipal( + new CaseSensitiveClaimsIdentity(new[] + { + new Claim(ClaimConstants.Username, username), + new Claim(ClaimConstants.Password, password), + })); } } } From 5129cf12078fa42ef3a1b618c99122dbd7876964 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Fri, 25 Oct 2024 13:05:02 -0700 Subject: [PATCH 06/15] Exclude file ClaimsConstant as it contains the constant Password --- build/credscan-exclusion.json | 4 ++++ 1 file changed, 4 insertions(+) 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." } ] } From 229b190f5f1b2da5e992f74a0e3224786f42a264 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Mon, 28 Oct 2024 14:52:15 -0700 Subject: [PATCH 07/15] Address comments - Add logging and update constants --- .../ClaimConstants.cs | 4 ++-- .../TokenAcquisition.cs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs index 9f82caa2f..2e42e181c 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs @@ -103,11 +103,11 @@ public static class ClaimConstants /// /// Username claims for ROPC flow. /// - public const string Username = "xms-username"; + public const string Username = "xms_username"; /// /// Password claims for ROPC flow. /// - public const string Password = "xms-password"; + public const string Password = "xms_password"; } } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index b27fdfbfe..27536f476 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -336,7 +336,7 @@ public async Task GetAuthenticationResultForUserAsync( string password = user.FindFirst(ClaimConstants.Password)?.Value ?? string.Empty; var accounts = await application.GetAccountsAsync().ConfigureAwait(false); - var account = accounts.FirstOrDefault(account => account.Username == username); + var account = accounts.Where(account => account.Username == username).FirstOrDefault(); if (account != null) { @@ -349,9 +349,10 @@ public async Task GetAuthenticationResultForUserAsync( .ExecuteAsync() .ConfigureAwait(false); } - catch + catch (Exception ex) { - // Ignore this exception as we will retry with ROPC + // Log a message when the silent flow fails and try acquisition through ROPC. + Logger.TokenAcquisitionError(_logger, ex.Message, ex); } } From 7c80db388f68385ab9e1a550d120c5644e1733a2 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Mon, 28 Oct 2024 14:57:21 -0700 Subject: [PATCH 08/15] Resolve warnings --- .../PublicAPI/net462/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/net472/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/net7.0/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 2 ++ .../PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt | 2 ++ 6 files changed, 12 insertions(+) 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? From 42ad9a80cac6cf0a5f2b965b31b5b4f1e9a9a008 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Tue, 29 Oct 2024 14:09:47 -0700 Subject: [PATCH 09/15] Alternative to GetAccounts --- .../ClaimConstants.cs | 5 +++++ .../TokenAcquisition.cs | 13 +++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs index 2e42e181c..922039480 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs @@ -109,5 +109,10 @@ public static class ClaimConstants /// Password claims for ROPC flow. /// public const string Password = "xms_password"; + + /// + /// Account Id claim to store account id for silent flow with ROPC. + /// + public const string HomeAccountId = "xms_account_id"; } } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 27536f476..7641a6e1e 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -334,14 +334,14 @@ public async Task GetAuthenticationResultForUserAsync( { string username = user.FindFirst(ClaimConstants.Username)?.Value ?? string.Empty; string password = user.FindFirst(ClaimConstants.Password)?.Value ?? string.Empty; + string accountId = user.FindFirst(ClaimConstants.HomeAccountId)?.Value ?? string.Empty; - var accounts = await application.GetAccountsAsync().ConfigureAwait(false); - var account = accounts.Where(account => account.Username == username).FirstOrDefault(); - - if (account != null) + if (accountId != null) { try { + var account = await application.GetAccountAsync(accountId).ConfigureAwait(false); + // Silent flow return await application.AcquireTokenSilent( scopes.Except(_scopesRequestedByMsal), @@ -365,6 +365,11 @@ public async Task GetAuthenticationResultForUserAsync( .ExecuteAsync() .ConfigureAwait(false); + user.AddIdentity(new CaseSensitiveClaimsIdentity(new[] + { + new Claim(ClaimConstants.HomeAccountId, authenticationResult.Account.HomeAccountId.Identifier), + })); + return authenticationResult; } From 88606719cfc3de265c600cc5be328cc0b16e8833 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Tue, 29 Oct 2024 14:15:37 -0700 Subject: [PATCH 10/15] Update to only add the claim if not already present --- .../TokenAcquisition.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 7641a6e1e..5289fe8d5 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -365,10 +365,14 @@ public async Task GetAuthenticationResultForUserAsync( .ExecuteAsync() .ConfigureAwait(false); - user.AddIdentity(new CaseSensitiveClaimsIdentity(new[] + if (user.FindFirst(ClaimConstants.HomeAccountId) == null) { - new Claim(ClaimConstants.HomeAccountId, authenticationResult.Account.HomeAccountId.Identifier), - })); + // Add the account id to the user (in case of ROPC flow) + user.AddIdentity(new CaseSensitiveClaimsIdentity(new[] + { + new Claim(ClaimConstants.HomeAccountId, authenticationResult.Account.HomeAccountId.Identifier), + })); + } return authenticationResult; } From 9a0df38a6c2cb803f21dad2f1ca125e905472d14 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Tue, 29 Oct 2024 14:37:58 -0700 Subject: [PATCH 11/15] Update to use existing constants --- .../ClaimConstants.cs | 5 ----- .../TokenAcquisition.cs | 8 ++++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs index 922039480..2e42e181c 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/ClaimConstants.cs @@ -109,10 +109,5 @@ public static class ClaimConstants /// Password claims for ROPC flow. /// public const string Password = "xms_password"; - - /// - /// Account Id claim to store account id for silent flow with ROPC. - /// - public const string HomeAccountId = "xms_account_id"; } } diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 5289fe8d5..ba88fd752 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -334,13 +334,12 @@ public async Task GetAuthenticationResultForUserAsync( { string username = user.FindFirst(ClaimConstants.Username)?.Value ?? string.Empty; string password = user.FindFirst(ClaimConstants.Password)?.Value ?? string.Empty; - string accountId = user.FindFirst(ClaimConstants.HomeAccountId)?.Value ?? string.Empty; - if (accountId != null) + if (user.GetMsalAccountId() != null) { try { - var account = await application.GetAccountAsync(accountId).ConfigureAwait(false); + var account = await application.GetAccountAsync(user.GetMsalAccountId()).ConfigureAwait(false); // Silent flow return await application.AcquireTokenSilent( @@ -370,7 +369,8 @@ public async Task GetAuthenticationResultForUserAsync( // Add the account id to the user (in case of ROPC flow) user.AddIdentity(new CaseSensitiveClaimsIdentity(new[] { - new Claim(ClaimConstants.HomeAccountId, authenticationResult.Account.HomeAccountId.Identifier), + new Claim(ClaimConstants.UniqueObjectIdentifier, authenticationResult.Account.HomeAccountId.ObjectId), + new Claim(ClaimConstants.UniqueTenantIdentifier, authenticationResult.Account.HomeAccountId.TenantId), })); } From f7407e8e7e6f934a9dfe0d57b77be3f800889207 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Tue, 29 Oct 2024 14:41:26 -0700 Subject: [PATCH 12/15] Add check before setting --- src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index ba88fd752..2f48e635c 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -364,7 +364,7 @@ public async Task GetAuthenticationResultForUserAsync( .ExecuteAsync() .ConfigureAwait(false); - if (user.FindFirst(ClaimConstants.HomeAccountId) == null) + if (user.GetMsalAccountId() == null) { // Add the account id to the user (in case of ROPC flow) user.AddIdentity(new CaseSensitiveClaimsIdentity(new[] From 85eb1a474329728f851602ef697aea25b8c716f9 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Wed, 30 Oct 2024 16:10:51 -0700 Subject: [PATCH 13/15] Add comment to the method --- src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index 2f48e635c..b38046b27 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -328,6 +328,7 @@ 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)) From fe4e4d5c3b9fd5ca5dd597a1c637063601c27fc4 Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Wed, 30 Oct 2024 19:22:06 -0700 Subject: [PATCH 14/15] fixing warnings - public API - no ConfigureAwait(false) in tests (instable) --- .../ClaimsPrincipalFactory.cs | 196 +++++++++--------- .../PublicAPI/net462/PublicAPI.Unshipped.txt | 2 + .../PublicAPI/net472/PublicAPI.Unshipped.txt | 2 + .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 2 + .../PublicAPI/net7.0/PublicAPI.Unshipped.txt | 2 + .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 2 + .../PublicAPI/net9.0/PublicAPI.Unshipped.txt | 2 + .../TokenAcquirerTests/TokenAcquirer.cs | 4 +- 8 files changed, 112 insertions(+), 100 deletions(-) diff --git a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs index 25899261a..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,28 +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), - })); - } - } -} + /// 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 08f411448..aa48492a7 100644 --- a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs +++ b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs @@ -112,13 +112,13 @@ public async Task AcquireToken_ROPC_CCAasync() var user = ClaimsPrincipalFactory.FromUsernamePassword(labResponse.User.Upn, labResponse.User.GetOrFetchPassword()); var result = await tokenAcquirer.GetTokenForUserAsync( - scopes: new[] { "https://graph.microsoft.com/.default" }, user: user).ConfigureAwait(false); + 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).ConfigureAwait(false); + scopes: new[] { "https://graph.microsoft.com/.default" }, user: user); Assert.NotNull(result2); Assert.NotNull(result2.AccessToken); From 7a93b944f408b6563e82e2c7d48023bb7f4bb7b1 Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Mon, 4 Nov 2024 09:01:39 -0800 Subject: [PATCH 15/15] Address comments --- src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs index b38046b27..a97db66da 100644 --- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs @@ -349,7 +349,7 @@ public async Task GetAuthenticationResultForUserAsync( .ExecuteAsync() .ConfigureAwait(false); } - catch (Exception ex) + catch (MsalException ex) { // Log a message when the silent flow fails and try acquisition through ROPC. Logger.TokenAcquisitionError(_logger, ex.Message, ex);