From 53640e17abcd8991a1246ae018a545208ac77bf5 Mon Sep 17 00:00:00 2001 From: Jennyf19 Date: Wed, 24 Mar 2021 09:06:52 -0700 Subject: [PATCH] change tabs to spaces for feedback PR --- .../CSharp/SonarLint.xml | 164 +- LICENSE | 34 +- .../AccountExtensions.cs | 64 +- ...ServicesAuthenticationBuilderExtensions.cs | 48 +- .../AppServicesAuthenticationDefaults.cs | 20 +- .../AppServicesAuthenticationHandler.cs | 76 +- .../AppServicesAuthenticationInformation.cs | 300 +- .../AppServicesAuthenticationOptions.cs | 12 +- ...pServicesAuthenticationTokenAcquisition.cs | 332 +- src/Microsoft.Identity.Web/Architecture.dgml | 2328 +++---- .../AuthorityHelpers.cs | 56 +- .../AuthorizeForScopesAttribute.cs | 296 +- .../AzureADB2COpenIDConnectEventHandlers.cs | 198 +- ...tionsAuthenticationHttpContextExtension.cs | 68 +- .../Base64UrlHelpers.cs | 130 +- .../CertificateDescription.cs | 538 +- .../CertificateSource.cs | 60 +- .../DefaultCertificateLoader.cs | 468 +- .../ICertificateLoader.cs | 46 +- .../ClaimsPrincipalExtensions.cs | 416 +- .../ClaimsPrincipalFactory.cs | 76 +- src/Microsoft.Identity.Web/ClientInfo.cs | 66 +- .../Constants/ClaimConstants.cs | 194 +- .../Constants/Constants.cs | 242 +- .../Constants/ErrorCodes.cs | 18 +- .../Constants/IDWebErrorMessage.cs | 116 +- .../Constants/LogMessages.cs | 36 +- .../Constants/OidcConstants.cs | 16 +- .../CookiePolicyOptionsExtensions.cs | 342 +- src/Microsoft.Identity.Web/Diagrams.cd | 208 +- .../DownstreamWebApi.cs | 404 +- .../DownstreamWebApiExtensions.cs | 100 +- .../DownstreamWebApiGenericExtensions.cs | 546 +- .../DownstreamWebApiOptions.cs | 162 +- .../IDownstreamWebApi.cs | 190 +- src/Microsoft.Identity.Web/Extensions.cs | 44 +- .../HttpContextExtensions.cs | 44 +- .../ILoginErrorAccessor.cs | 46 +- .../ITokenAcquisition.cs | 186 +- .../ITokenAcquisitionInternal.cs | 86 +- src/Microsoft.Identity.Web/IdHelper.cs | 50 +- ...mentalConsentAndConditionalAccessHelper.cs | 152 +- .../IssuerConfigurationRetriever.cs | 64 +- .../InstanceDiscovery/IssuerMetadata.cs | 42 +- .../InstanceDiscovery/Metadata.cs | 44 +- .../Microsoft.Identity.Web.csproj | 150 +- .../Microsoft.Identity.Web.xml | 5674 ++++++++--------- ...rosoftIdentityBaseAuthenticationBuilder.cs | 58 +- .../MicrosoftIdentityCircuitHandler.cs | 422 +- .../MicrosoftIdentityOptions.cs | 254 +- .../MicrosoftIdentityOptionsValidation.cs | 92 +- ...rosoftIdentityWebChallengeUserException.cs | 76 +- .../MsalAspNetCoreHttpClientFactory.cs | 28 +- src/Microsoft.Identity.Web/NuGet.Config | 4 +- .../Properties/InternalsVisibleTo.cs | 12 +- .../Resource/AadIssuerValidator.cs | 388 +- .../Resource/AadIssuerValidatorOptions.cs | 22 +- .../IJwtBearerMiddlewareDiagnostics.cs | 24 +- .../IOpenIdConnectMiddlewareDiagnostics.cs | 24 +- .../JwtBearerMiddlewareDiagnostics.cs | 190 +- ...MicrosoftIdentityIssuerValidatorFactory.cs | 124 +- .../OpenIdConnectMiddlewareDiagnostics.cs | 404 +- .../Resource/RegisterValidAudience.cs | 140 +- .../Resource/RequiredScopeAttribute.cs | 124 +- .../Resource/RequiredScopeFilter.cs | 182 +- .../RolesRequiredHttpContextExtensions.cs | 92 +- .../ScopesRequiredHttpContextExtensions.cs | 112 +- .../ServiceCollectionExtensions.cs | 124 +- .../TempDataLoginErrorAccessor.cs | 116 +- .../TokenAcquisition.cs | 1604 ++--- .../TokenAcquisitionOptions.cs | 98 +- .../DistributedTokenCacheAdapterExtension.cs | 42 +- .../MsalDistributedTokenCacheAdapter.cs | 406 +- ...MsalDistributedTokenCacheAdapterOptions.cs | 62 +- .../IMsalTokenCacheProvider.cs | 36 +- .../InMemoryTokenCacheProviderExtension.cs | 40 +- .../InMemory/MsalMemoryTokenCacheOptions.cs | 50 +- .../InMemory/MsalMemoryTokenCacheProvider.cs | 136 +- .../MeasureDurationResult.cs | 50 +- .../MsalAbstractTokenCacheProvider.cs | 188 +- .../Session/MsalSessionTokenCacheProvider.cs | 212 +- .../SessionTokenCacheProviderExtension.cs | 192 +- .../TokenCacheSerializers.cd | 176 +- .../TokenCacheProviders/Utility.cs | 66 +- .../WebApiExtensions/ClassDiagram1.cd | 20 +- ...softIdentityWebApiAuthenticationBuilder.cs | 190 +- ...tyWebApiAuthenticationBuilderExtensions.cs | 452 +- ...iAuthenticationBuilderWithConfiguration.cs | 56 +- ...entityWebApiServiceCollectionExtensions.cs | 62 +- ...tyAppCallingWebApiAuthenticationBuilder.cs | 170 +- ...softIdentityWebAppAuthenticationBuilder.cs | 312 +- ...tyWebAppAuthenticationBuilderExtensions.cs | 882 +-- ...pAuthenticationBuilderWithConfiguration.cs | 98 +- ...entityWebAppServiceCollectionExtensions.cs | 80 +- stylecop.json | 40 +- 95 files changed, 11842 insertions(+), 11842 deletions(-) diff --git a/.sonarlint/azuread_microsoft-identity-web/CSharp/SonarLint.xml b/.sonarlint/azuread_microsoft-identity-web/CSharp/SonarLint.xml index 21acd70e2..6a75acee3 100644 --- a/.sonarlint/azuread_microsoft-identity-web/CSharp/SonarLint.xml +++ b/.sonarlint/azuread_microsoft-identity-web/CSharp/SonarLint.xml @@ -1,89 +1,89 @@ - - sonar.cs.analyzeGeneratedCode - false - - - sonar.cs.file.suffixes - .cs - - - sonar.cs.ignoreHeaderComments - true - - - sonar.cs.roslyn.ignoreIssues - false - + + sonar.cs.analyzeGeneratedCode + false + + + sonar.cs.file.suffixes + .cs + + + sonar.cs.ignoreHeaderComments + true + + + sonar.cs.roslyn.ignoreIssues + false + - - S107 - - - max - 7 - - - - - S110 - - - max - 5 - - - - - S1479 - - - maximum - 30 - - - - - S2342 - - - flagsAttributeFormat - ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ - - - format - ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ - - - - - S2436 - - - max - 2 - - - maxMethod - 3 - - - - - S3776 - - - propertyThreshold - 3 - - - threshold - 15 - - - + + S107 + + + max + 7 + + + + + S110 + + + max + 5 + + + + + S1479 + + + maximum + 30 + + + + + S2342 + + + flagsAttributeFormat + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?s$ + + + format + ^([A-Z]{1,3}[a-z0-9]+)*([A-Z]{2})?$ + + + + + S2436 + + + max + 2 + + + maxMethod + 3 + + + + + S3776 + + + propertyThreshold + 3 + + + threshold + 15 + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 3d8b93bc7..a2425b01a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ - MIT License + MIT License - Copyright (c) Microsoft Corporation. + Copyright (c) Microsoft Corporation. - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/src/Microsoft.Identity.Web/AccountExtensions.cs b/src/Microsoft.Identity.Web/AccountExtensions.cs index 605e77913..39eb9a563 100644 --- a/src/Microsoft.Identity.Web/AccountExtensions.cs +++ b/src/Microsoft.Identity.Web/AccountExtensions.cs @@ -7,40 +7,40 @@ namespace Microsoft.Identity.Web { - /// - /// Extension methods for . - /// - public static class AccountExtensions - { - /// - /// Creates the from the values found - /// in an . - /// - /// The instance. - /// A built from . - public static ClaimsPrincipal ToClaimsPrincipal(this IAccount account) - { - if (account == null) - { - throw new ArgumentNullException(nameof(account)); - } + /// + /// Extension methods for . + /// + public static class AccountExtensions + { + /// + /// Creates the from the values found + /// in an . + /// + /// The instance. + /// A built from . + public static ClaimsPrincipal ToClaimsPrincipal(this IAccount account) + { + if (account == null) + { + throw new ArgumentNullException(nameof(account)); + } - ClaimsIdentity identity = new ClaimsIdentity(new Claim[] - { - new Claim(ClaimTypes.Upn, account.Username), - }); + ClaimsIdentity identity = new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.Upn, account.Username), + }); - if (!string.IsNullOrEmpty(account.HomeAccountId?.ObjectId)) - { - identity.AddClaim(new Claim(ClaimConstants.Oid, account.HomeAccountId.ObjectId)); - } + if (!string.IsNullOrEmpty(account.HomeAccountId?.ObjectId)) + { + identity.AddClaim(new Claim(ClaimConstants.Oid, account.HomeAccountId.ObjectId)); + } - if (!string.IsNullOrEmpty(account.HomeAccountId?.TenantId)) - { - identity.AddClaim(new Claim(ClaimConstants.Tid, account.HomeAccountId.TenantId)); - } + if (!string.IsNullOrEmpty(account.HomeAccountId?.TenantId)) + { + identity.AddClaim(new Claim(ClaimConstants.Tid, account.HomeAccountId.TenantId)); + } - return new ClaimsPrincipal(identity); - } - } + return new ClaimsPrincipal(identity); + } + } } diff --git a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationBuilderExtensions.cs index 0b0eb4a35..652d72f07 100644 --- a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationBuilderExtensions.cs @@ -5,30 +5,30 @@ namespace Microsoft.Identity.Web { - /// - /// Extension methods related to App Services authentication (Easy Auth). - /// - public static class AppServicesAuthenticationBuilderExtensions - { - /// - /// Add authentication with App Services. - /// - /// Authentication builder. - /// The builder, to chain commands. - public static AuthenticationBuilder AddAppServicesAuthentication( - this AuthenticationBuilder builder) - { - if (builder is null) - { - throw new System.ArgumentNullException(nameof(builder)); - } + /// + /// Extension methods related to App Services authentication (Easy Auth). + /// + public static class AppServicesAuthenticationBuilderExtensions + { + /// + /// Add authentication with App Services. + /// + /// Authentication builder. + /// The builder, to chain commands. + public static AuthenticationBuilder AddAppServicesAuthentication( + this AuthenticationBuilder builder) + { + if (builder is null) + { + throw new System.ArgumentNullException(nameof(builder)); + } - builder.AddScheme( - AppServicesAuthenticationDefaults.AuthenticationScheme, - AppServicesAuthenticationDefaults.AuthenticationScheme, - options => { }); + builder.AddScheme( + AppServicesAuthenticationDefaults.AuthenticationScheme, + AppServicesAuthenticationDefaults.AuthenticationScheme, + options => { }); - return builder; - } - } + return builder; + } + } } diff --git a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationDefaults.cs b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationDefaults.cs index 8fb5ba699..8ad04fd27 100644 --- a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationDefaults.cs +++ b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationDefaults.cs @@ -3,14 +3,14 @@ namespace Microsoft.Identity.Web { - /// - /// Default values related to AppServiceAuthentication handler. - /// - public static class AppServicesAuthenticationDefaults - { - /// - /// The default value used for AppServiceAuthenticationOptions.AuthenticationScheme. - /// - public const string AuthenticationScheme = "AppServicesAuthentication"; - } + /// + /// Default values related to AppServiceAuthentication handler. + /// + public static class AppServicesAuthenticationDefaults + { + /// + /// The default value used for AppServiceAuthenticationOptions.AuthenticationScheme. + /// + public const string AuthenticationScheme = "AppServicesAuthentication"; + } } diff --git a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs index 9a626ccd9..92bc654a9 100644 --- a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs +++ b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs @@ -12,44 +12,44 @@ namespace Microsoft.Identity.Web { - /// - /// App service authentication handler. - /// - public class AppServicesAuthenticationHandler : AuthenticationHandler - { - /// - /// Constructor for the AppServiceAuthenticationHandler. - /// Note the parameters are required by the base class. - /// - /// App service authentication options. - /// Logger factory. - /// URL encoder. - /// System clock. - public AppServicesAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder, - ISystemClock clock) - : base(options, logger, encoder, clock) - { - } + /// + /// App service authentication handler. + /// + public class AppServicesAuthenticationHandler : AuthenticationHandler + { + /// + /// Constructor for the AppServiceAuthenticationHandler. + /// Note the parameters are required by the base class. + /// + /// App service authentication options. + /// Logger factory. + /// URL encoder. + /// System clock. + public AppServicesAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) + : base(options, logger, encoder, clock) + { + } - /// - protected override Task HandleAuthenticateAsync() - { - if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) - { - ClaimsPrincipal? claimsPrincipal = AppServicesAuthenticationInformation.GetUser(Context.Request.Headers); - if (claimsPrincipal != null) - { - AuthenticationTicket ticket = new AuthenticationTicket(claimsPrincipal, AppServicesAuthenticationDefaults.AuthenticationScheme); - AuthenticateResult success = AuthenticateResult.Success(ticket); - return Task.FromResult(success); - } - } + /// + protected override Task HandleAuthenticateAsync() + { + if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) + { + ClaimsPrincipal? claimsPrincipal = AppServicesAuthenticationInformation.GetUser(Context.Request.Headers); + if (claimsPrincipal != null) + { + AuthenticationTicket ticket = new AuthenticationTicket(claimsPrincipal, AppServicesAuthenticationDefaults.AuthenticationScheme); + AuthenticateResult success = AuthenticateResult.Success(ticket); + return Task.FromResult(success); + } + } - // Try another handler - return Task.FromResult(AuthenticateResult.NoResult()); - } - } + // Try another handler + return Task.FromResult(AuthenticateResult.NoResult()); + } + } } diff --git a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationInformation.cs b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationInformation.cs index 1952a2502..ca3c3e19b 100644 --- a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationInformation.cs +++ b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationInformation.cs @@ -10,173 +10,173 @@ namespace Microsoft.Identity.Web { - /// - /// Information about the App Services configuration on the host. - /// - public static class AppServicesAuthenticationInformation - { - // Environment variables. - private const string AppServicesAuthEnabledEnvironmentVariable = "WEBSITE_AUTH_ENABLED"; // True - private const string AppServicesAuthOpenIdIssuerEnvironmentVariable = "WEBSITE_AUTH_OPENID_ISSUER"; // for instance https://sts.windows.net// - private const string AppServicesAuthClientIdEnvironmentVariable = "WEBSITE_AUTH_CLIENT_ID"; // A GUID - private const string AppServicesAuthClientSecretEnvironmentVariable = "WEBSITE_AUTH_CLIENT_SECRET"; // A string - private const string AppServicesAuthLogoutPathEnvironmentVariable = "WEBSITE_AUTH_LOGOUT_PATH"; // /.auth/logout - private const string AppServicesAuthIdentityProviderEnvironmentVariable = "WEBSITE_AUTH_DEFAULT_PROVIDER"; // AzureActiveDirectory - private const string AppServicesAuthAzureActiveDirectory = "AzureActiveDirectory"; - private const string AppServicesAuthIdTokenHeader = "X-MS-TOKEN-AAD-ID-TOKEN"; - private const string AppServicesAuthIdpTokenHeader = "X-MS-CLIENT-PRINCIPAL-IDP"; + /// + /// Information about the App Services configuration on the host. + /// + public static class AppServicesAuthenticationInformation + { + // Environment variables. + private const string AppServicesAuthEnabledEnvironmentVariable = "WEBSITE_AUTH_ENABLED"; // True + private const string AppServicesAuthOpenIdIssuerEnvironmentVariable = "WEBSITE_AUTH_OPENID_ISSUER"; // for instance https://sts.windows.net// + private const string AppServicesAuthClientIdEnvironmentVariable = "WEBSITE_AUTH_CLIENT_ID"; // A GUID + private const string AppServicesAuthClientSecretEnvironmentVariable = "WEBSITE_AUTH_CLIENT_SECRET"; // A string + private const string AppServicesAuthLogoutPathEnvironmentVariable = "WEBSITE_AUTH_LOGOUT_PATH"; // /.auth/logout + private const string AppServicesAuthIdentityProviderEnvironmentVariable = "WEBSITE_AUTH_DEFAULT_PROVIDER"; // AzureActiveDirectory + private const string AppServicesAuthAzureActiveDirectory = "AzureActiveDirectory"; + private const string AppServicesAuthIdTokenHeader = "X-MS-TOKEN-AAD-ID-TOKEN"; + private const string AppServicesAuthIdpTokenHeader = "X-MS-CLIENT-PRINCIPAL-IDP"; - // Artificially added by Microsoft.Identity.Web to help debugging App Services. See the Debug controller of the test app - private const string AppServicesAuthDebugHeadersEnvironmentVariable = "APP_SERVICES_AUTH_LOCAL_DEBUG"; + // Artificially added by Microsoft.Identity.Web to help debugging App Services. See the Debug controller of the test app + private const string AppServicesAuthDebugHeadersEnvironmentVariable = "APP_SERVICES_AUTH_LOCAL_DEBUG"; - /// - /// Is App Services authentication enabled?. - /// - public static bool IsAppServicesAadAuthenticationEnabled - { - get - { - return (Environment.GetEnvironmentVariable(AppServicesAuthEnabledEnvironmentVariable) == Constants.True) - && Environment.GetEnvironmentVariable(AppServicesAuthIdentityProviderEnvironmentVariable) == AppServicesAuthAzureActiveDirectory; - } - } + /// + /// Is App Services authentication enabled?. + /// + public static bool IsAppServicesAadAuthenticationEnabled + { + get + { + return (Environment.GetEnvironmentVariable(AppServicesAuthEnabledEnvironmentVariable) == Constants.True) + && Environment.GetEnvironmentVariable(AppServicesAuthIdentityProviderEnvironmentVariable) == AppServicesAuthAzureActiveDirectory; + } + } - /// - /// Logout URL for App Services Auth web sites. - /// - public static string? LogoutUrl - { - get - { - return Environment.GetEnvironmentVariable(AppServicesAuthLogoutPathEnvironmentVariable); - } - } + /// + /// Logout URL for App Services Auth web sites. + /// + public static string? LogoutUrl + { + get + { + return Environment.GetEnvironmentVariable(AppServicesAuthLogoutPathEnvironmentVariable); + } + } - /// - /// ClientID of the App Services Auth web site. - /// - internal static string? ClientId - { - get - { - return Environment.GetEnvironmentVariable(AppServicesAuthClientIdEnvironmentVariable); - } - } + /// + /// ClientID of the App Services Auth web site. + /// + internal static string? ClientId + { + get + { + return Environment.GetEnvironmentVariable(AppServicesAuthClientIdEnvironmentVariable); + } + } - /// - /// Client secret of the App Services Auth web site. - /// - internal static string? ClientSecret - { - get - { - return Environment.GetEnvironmentVariable(AppServicesAuthClientSecretEnvironmentVariable); - } - } + /// + /// Client secret of the App Services Auth web site. + /// + internal static string? ClientSecret + { + get + { + return Environment.GetEnvironmentVariable(AppServicesAuthClientSecretEnvironmentVariable); + } + } - /// - /// Issuer of the App Services Auth web site. - /// - internal static string? Issuer - { - get - { - return Environment.GetEnvironmentVariable(AppServicesAuthOpenIdIssuerEnvironmentVariable); - } - } + /// + /// Issuer of the App Services Auth web site. + /// + internal static string? Issuer + { + get + { + return Environment.GetEnvironmentVariable(AppServicesAuthOpenIdIssuerEnvironmentVariable); + } + } #if DEBUG - /// - /// Get headers from environment to help debugging App Services authentication. - /// - internal static string? SimulateGetttingHeaderFromDebugEnvironmentVariable(string header) - { - string? headerPlusValue = Environment.GetEnvironmentVariable(AppServicesAuthDebugHeadersEnvironmentVariable) - ?.Split(';') - ?.FirstOrDefault(h => h.StartsWith(header)); - return headerPlusValue?.Substring(header.Length + 1); - } + /// + /// Get headers from environment to help debugging App Services authentication. + /// + internal static string? SimulateGetttingHeaderFromDebugEnvironmentVariable(string header) + { + string? headerPlusValue = Environment.GetEnvironmentVariable(AppServicesAuthDebugHeadersEnvironmentVariable) + ?.Split(';') + ?.FirstOrDefault(h => h.StartsWith(header)); + return headerPlusValue?.Substring(header.Length + 1); + } #endif - /// - /// Get the ID token from the headers sent by App services authentication. - /// - /// Headers. - /// The ID Token. - internal static string? GetIdToken(IDictionary headers) - { - if (headers is null) - { - throw new ArgumentNullException(nameof(headers)); - } + /// + /// Get the ID token from the headers sent by App services authentication. + /// + /// Headers. + /// The ID Token. + internal static string? GetIdToken(IDictionary headers) + { + if (headers is null) + { + throw new ArgumentNullException(nameof(headers)); + } - string? idToken = null; - if (headers.ContainsKey(AppServicesAuthIdTokenHeader)) - { - idToken = headers[AppServicesAuthIdTokenHeader]; - } + string? idToken = null; + if (headers.ContainsKey(AppServicesAuthIdTokenHeader)) + { + idToken = headers[AppServicesAuthIdTokenHeader]; + } #if DEBUG - if (string.IsNullOrEmpty(idToken)) - { - idToken = AppServicesAuthenticationInformation.SimulateGetttingHeaderFromDebugEnvironmentVariable(AppServicesAuthIdTokenHeader); - } + if (string.IsNullOrEmpty(idToken)) + { + idToken = AppServicesAuthenticationInformation.SimulateGetttingHeaderFromDebugEnvironmentVariable(AppServicesAuthIdTokenHeader); + } #endif - return idToken; - } + return idToken; + } - /// - /// Get the IDP from the headers sent by App services authentication. - /// - /// Headers. - /// The IDP. - internal static string? GetIdp(IDictionary headers) - { - if (headers is null) - { - throw new ArgumentNullException(nameof(headers)); - } + /// + /// Get the IDP from the headers sent by App services authentication. + /// + /// Headers. + /// The IDP. + internal static string? GetIdp(IDictionary headers) + { + if (headers is null) + { + throw new ArgumentNullException(nameof(headers)); + } - string? idp = null; - if (headers.ContainsKey(AppServicesAuthIdTokenHeader)) - { - idp = headers[AppServicesAuthIdpTokenHeader]; - } + string? idp = null; + if (headers.ContainsKey(AppServicesAuthIdTokenHeader)) + { + idp = headers[AppServicesAuthIdpTokenHeader]; + } #if DEBUG - if (string.IsNullOrEmpty(idp)) - { - idp = AppServicesAuthenticationInformation.SimulateGetttingHeaderFromDebugEnvironmentVariable(AppServicesAuthIdpTokenHeader); - } + if (string.IsNullOrEmpty(idp)) + { + idp = AppServicesAuthenticationInformation.SimulateGetttingHeaderFromDebugEnvironmentVariable(AppServicesAuthIdpTokenHeader); + } #endif - return idp; - } + return idp; + } - /// - /// Get the user claims from the headers and environment variables. - /// - /// Headers. - /// User claims. - internal static ClaimsPrincipal? GetUser(IDictionary headers) - { - ClaimsPrincipal? claimsPrincipal; - string? idToken = AppServicesAuthenticationInformation.GetIdToken(headers); - string? idp = AppServicesAuthenticationInformation.GetIdp(headers); - if (idToken != null && idp != null) - { - JsonWebToken jsonWebToken = new JsonWebToken(idToken); - bool isAadV1Token = jsonWebToken.Claims - .Any(c => c.Type == Constants.Version && c.Value == Constants.V1); - claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity( - jsonWebToken.Claims, - idp, - isAadV1Token ? Constants.NameClaim : Constants.PreferredUserName, - ClaimsIdentity.DefaultRoleClaimType)); - } - else - { - claimsPrincipal = null; - } + /// + /// Get the user claims from the headers and environment variables. + /// + /// Headers. + /// User claims. + internal static ClaimsPrincipal? GetUser(IDictionary headers) + { + ClaimsPrincipal? claimsPrincipal; + string? idToken = AppServicesAuthenticationInformation.GetIdToken(headers); + string? idp = AppServicesAuthenticationInformation.GetIdp(headers); + if (idToken != null && idp != null) + { + JsonWebToken jsonWebToken = new JsonWebToken(idToken); + bool isAadV1Token = jsonWebToken.Claims + .Any(c => c.Type == Constants.Version && c.Value == Constants.V1); + claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity( + jsonWebToken.Claims, + idp, + isAadV1Token ? Constants.NameClaim : Constants.PreferredUserName, + ClaimsIdentity.DefaultRoleClaimType)); + } + else + { + claimsPrincipal = null; + } - return claimsPrincipal; - } - } + return claimsPrincipal; + } + } } diff --git a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationOptions.cs b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationOptions.cs index 859988832..59caf6f70 100644 --- a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationOptions.cs +++ b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationOptions.cs @@ -5,10 +5,10 @@ namespace Microsoft.Identity.Web { - /// - /// Options for Azure App Services authentication. - /// - public class AppServicesAuthenticationOptions : AuthenticationSchemeOptions - { - } + /// + /// Options for Azure App Services authentication. + /// + public class AppServicesAuthenticationOptions : AuthenticationSchemeOptions + { + } } diff --git a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs index 166720626..9df5905b2 100644 --- a/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationTokenAcquisition.cs @@ -13,181 +13,181 @@ namespace Microsoft.Identity.Web { - /// - /// Implementation of ITokenAcquisition for App Services authentication (EasyAuth). - /// - public class AppServicesAuthenticationTokenAcquisition : ITokenAcquisition - { - private IConfidentialClientApplication? _confidentialClientApplication; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IMsalHttpClientFactory _httpClientFactory; - private readonly IMsalTokenCacheProvider _tokenCacheProvider; - - internal class Account : IAccount - { - public Account(ClaimsPrincipal claimsPrincipal) - { - _claimsPrincipal = claimsPrincipal; - } - - private readonly ClaimsPrincipal _claimsPrincipal; + /// + /// Implementation of ITokenAcquisition for App Services authentication (EasyAuth). + /// + public class AppServicesAuthenticationTokenAcquisition : ITokenAcquisition + { + private IConfidentialClientApplication? _confidentialClientApplication; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMsalHttpClientFactory _httpClientFactory; + private readonly IMsalTokenCacheProvider _tokenCacheProvider; + + internal class Account : IAccount + { + public Account(ClaimsPrincipal claimsPrincipal) + { + _claimsPrincipal = claimsPrincipal; + } + + private readonly ClaimsPrincipal _claimsPrincipal; #pragma warning disable CS8603 // Possible null reference return. - public string Username => _claimsPrincipal.GetDisplayName(); + public string Username => _claimsPrincipal.GetDisplayName(); #pragma warning restore CS8603 // Possible null reference return. - public string Environment => _claimsPrincipal.FindFirstValue("iss"); - - public AccountId HomeAccountId => new AccountId( - $"{_claimsPrincipal.GetObjectId()}.{_claimsPrincipal.GetTenantId()}", - _claimsPrincipal.GetObjectId(), - _claimsPrincipal.GetTenantId()); - } - - private HttpContext? CurrentHttpContext - { - get - { - return _httpContextAccessor.HttpContext; - } - } - - /// - /// Constructor of the AppServicesAuthenticationTokenAcquisition. - /// - /// The App token cache provider. - /// Access to the HttpContext of the request. - /// HTTP client factory. - public AppServicesAuthenticationTokenAcquisition( - IMsalTokenCacheProvider tokenCacheProvider, - IHttpContextAccessor httpContextAccessor, - IHttpClientFactory httpClientFactory) - { - _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); - _httpClientFactory = new MsalAspNetCoreHttpClientFactory(httpClientFactory); - _tokenCacheProvider = tokenCacheProvider; - } - - private async Task GetOrCreateApplication() - { - if (_confidentialClientApplication == null) - { - ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions() - { - ClientId = AppServicesAuthenticationInformation.ClientId, - ClientSecret = AppServicesAuthenticationInformation.ClientSecret, - Instance = AppServicesAuthenticationInformation.Issuer, - }; - _confidentialClientApplication = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options) - .WithHttpClientFactory(_httpClientFactory) - .Build(); - await _tokenCacheProvider.InitializeAsync(_confidentialClientApplication.AppTokenCache).ConfigureAwait(false); - await _tokenCacheProvider.InitializeAsync(_confidentialClientApplication.UserTokenCache).ConfigureAwait(false); - } - - return _confidentialClientApplication; - } - - /// - public async Task GetAccessTokenForAppAsync( - string scope, - string? tenant = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - // We could use MSI - if (scope is null) - { - throw new ArgumentNullException(nameof(scope)); - } - - var app = await GetOrCreateApplication().ConfigureAwait(false); - AuthenticationResult result = await app.AcquireTokenForClient(new string[] { scope }) - .ExecuteAsync() - .ConfigureAwait(false); - - return result.AccessToken; - } - - /// - public async Task GetAccessTokenForUserAsync( - IEnumerable scopes, - string? tenantId = null, - string? userFlow = null, - ClaimsPrincipal? user = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - string accessToken = GetAccessToken(CurrentHttpContext?.Request.Headers); - - return await Task.FromResult(accessToken).ConfigureAwait(false); - } - - private string GetAccessToken(IHeaderDictionary? headers) - { - const string AppServicesAuthAccessTokenHeader = "X-MS-TOKEN-AAD-ACCESS-TOKEN"; - - string? accessToken = null; - if (headers != null) - { - accessToken = headers[AppServicesAuthAccessTokenHeader]; - } + public string Environment => _claimsPrincipal.FindFirstValue("iss"); + + public AccountId HomeAccountId => new AccountId( + $"{_claimsPrincipal.GetObjectId()}.{_claimsPrincipal.GetTenantId()}", + _claimsPrincipal.GetObjectId(), + _claimsPrincipal.GetTenantId()); + } + + private HttpContext? CurrentHttpContext + { + get + { + return _httpContextAccessor.HttpContext; + } + } + + /// + /// Constructor of the AppServicesAuthenticationTokenAcquisition. + /// + /// The App token cache provider. + /// Access to the HttpContext of the request. + /// HTTP client factory. + public AppServicesAuthenticationTokenAcquisition( + IMsalTokenCacheProvider tokenCacheProvider, + IHttpContextAccessor httpContextAccessor, + IHttpClientFactory httpClientFactory) + { + _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); + _httpClientFactory = new MsalAspNetCoreHttpClientFactory(httpClientFactory); + _tokenCacheProvider = tokenCacheProvider; + } + + private async Task GetOrCreateApplication() + { + if (_confidentialClientApplication == null) + { + ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions() + { + ClientId = AppServicesAuthenticationInformation.ClientId, + ClientSecret = AppServicesAuthenticationInformation.ClientSecret, + Instance = AppServicesAuthenticationInformation.Issuer, + }; + _confidentialClientApplication = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options) + .WithHttpClientFactory(_httpClientFactory) + .Build(); + await _tokenCacheProvider.InitializeAsync(_confidentialClientApplication.AppTokenCache).ConfigureAwait(false); + await _tokenCacheProvider.InitializeAsync(_confidentialClientApplication.UserTokenCache).ConfigureAwait(false); + } + + return _confidentialClientApplication; + } + + /// + public async Task GetAccessTokenForAppAsync( + string scope, + string? tenant = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + // We could use MSI + if (scope is null) + { + throw new ArgumentNullException(nameof(scope)); + } + + var app = await GetOrCreateApplication().ConfigureAwait(false); + AuthenticationResult result = await app.AcquireTokenForClient(new string[] { scope }) + .ExecuteAsync() + .ConfigureAwait(false); + + return result.AccessToken; + } + + /// + public async Task GetAccessTokenForUserAsync( + IEnumerable scopes, + string? tenantId = null, + string? userFlow = null, + ClaimsPrincipal? user = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + string accessToken = GetAccessToken(CurrentHttpContext?.Request.Headers); + + return await Task.FromResult(accessToken).ConfigureAwait(false); + } + + private string GetAccessToken(IHeaderDictionary? headers) + { + const string AppServicesAuthAccessTokenHeader = "X-MS-TOKEN-AAD-ACCESS-TOKEN"; + + string? accessToken = null; + if (headers != null) + { + accessToken = headers[AppServicesAuthAccessTokenHeader]; + } #if DEBUG - if (string.IsNullOrEmpty(accessToken)) - { - accessToken = AppServicesAuthenticationInformation.SimulateGetttingHeaderFromDebugEnvironmentVariable(AppServicesAuthAccessTokenHeader); - } + if (string.IsNullOrEmpty(accessToken)) + { + accessToken = AppServicesAuthenticationInformation.SimulateGetttingHeaderFromDebugEnvironmentVariable(AppServicesAuthAccessTokenHeader); + } #endif - if (!string.IsNullOrEmpty(accessToken)) - { - return accessToken; - } + if (!string.IsNullOrEmpty(accessToken)) + { + return accessToken; + } - return string.Empty; - } + return string.Empty; + } - /// + /// #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async Task GetAuthenticationResultForUserAsync( - IEnumerable scopes, - string? tenantId = null, - string? userFlow = null, - ClaimsPrincipal? user = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - string? idToken = AppServicesAuthenticationInformation.GetIdToken(CurrentHttpContext?.Request?.Headers!); - ClaimsPrincipal? userClaims = AppServicesAuthenticationInformation.GetUser(CurrentHttpContext?.Request?.Headers!); - string accessToken = await GetAccessTokenForUserAsync(scopes, tenantId, userFlow, user, tokenAcquisitionOptions).ConfigureAwait(false); - string expiration = userClaims.FindFirstValue("exp"); - DateTimeOffset dateTimeOffset = (expiration != null) - ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiration, CultureInfo.InvariantCulture)) - : DateTimeOffset.Now; - - AuthenticationResult authenticationResult = new AuthenticationResult( - accessToken, - isExtendedLifeTimeToken: false, - userClaims?.GetDisplayName(), - dateTimeOffset, - dateTimeOffset, - userClaims?.GetTenantId(), - userClaims != null ? new Account(userClaims) : null, - idToken, - scopes, - tokenAcquisitionOptions != null ? tokenAcquisitionOptions.CorrelationId : Guid.Empty); - return authenticationResult; - } - - /// - public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null) - { - // Not implemented for the moment - throw new NotImplementedException(); - } - - /// - public Task GetAuthenticationResultForAppAsync(string scope, string? tenant = null, TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - throw new NotImplementedException(); - } + public async Task GetAuthenticationResultForUserAsync( + IEnumerable scopes, + string? tenantId = null, + string? userFlow = null, + ClaimsPrincipal? user = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + string? idToken = AppServicesAuthenticationInformation.GetIdToken(CurrentHttpContext?.Request?.Headers!); + ClaimsPrincipal? userClaims = AppServicesAuthenticationInformation.GetUser(CurrentHttpContext?.Request?.Headers!); + string accessToken = await GetAccessTokenForUserAsync(scopes, tenantId, userFlow, user, tokenAcquisitionOptions).ConfigureAwait(false); + string expiration = userClaims.FindFirstValue("exp"); + DateTimeOffset dateTimeOffset = (expiration != null) + ? DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiration, CultureInfo.InvariantCulture)) + : DateTimeOffset.Now; + + AuthenticationResult authenticationResult = new AuthenticationResult( + accessToken, + isExtendedLifeTimeToken: false, + userClaims?.GetDisplayName(), + dateTimeOffset, + dateTimeOffset, + userClaims?.GetTenantId(), + userClaims != null ? new Account(userClaims) : null, + idToken, + scopes, + tokenAcquisitionOptions != null ? tokenAcquisitionOptions.CorrelationId : Guid.Empty); + return authenticationResult; + } + + /// + public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null) + { + // Not implemented for the moment + throw new NotImplementedException(); + } + + /// + public Task GetAuthenticationResultForAppAsync(string scope, string? tenant = null, TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + throw new NotImplementedException(); + } #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - } + } } diff --git a/src/Microsoft.Identity.Web/Architecture.dgml b/src/Microsoft.Identity.Web/Architecture.dgml index d2c46bdb0..dd1db65ae 100644 --- a/src/Microsoft.Identity.Web/Architecture.dgml +++ b/src/Microsoft.Identity.Web/Architecture.dgml @@ -1,1183 +1,1183 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + \ No newline at end of file diff --git a/src/Microsoft.Identity.Web/AuthorityHelpers.cs b/src/Microsoft.Identity.Web/AuthorityHelpers.cs index f17f00f96..e6073a40d 100644 --- a/src/Microsoft.Identity.Web/AuthorityHelpers.cs +++ b/src/Microsoft.Identity.Web/AuthorityHelpers.cs @@ -6,35 +6,35 @@ namespace Microsoft.Identity.Web { - internal static class AuthorityHelpers - { - internal static string BuildAuthority(MicrosoftIdentityOptions options) - { - Uri baseUri = new Uri(options.Instance); - string pathBase = baseUri.PathAndQuery.TrimEnd('/'); - var domain = options.Domain; - var tenantId = options.TenantId; + internal static class AuthorityHelpers + { + internal static string BuildAuthority(MicrosoftIdentityOptions options) + { + Uri baseUri = new Uri(options.Instance); + string pathBase = baseUri.PathAndQuery.TrimEnd('/'); + var domain = options.Domain; + var tenantId = options.TenantId; - if (options.IsB2C) - { - var userFlow = options.DefaultUserFlow; - return new Uri(baseUri, new PathString($"{pathBase}/{domain}/{userFlow}/v2.0")).ToString(); - } - else - { - return new Uri(baseUri, new PathString($"{pathBase}/{tenantId}/v2.0")).ToString(); - } - } + if (options.IsB2C) + { + var userFlow = options.DefaultUserFlow; + return new Uri(baseUri, new PathString($"{pathBase}/{domain}/{userFlow}/v2.0")).ToString(); + } + else + { + return new Uri(baseUri, new PathString($"{pathBase}/{tenantId}/v2.0")).ToString(); + } + } - internal static string EnsureAuthorityIsV2(string authority) - { - authority = authority.Trim().TrimEnd('/'); - if (!authority.EndsWith("v2.0", StringComparison.InvariantCulture)) - { - authority += "/v2.0"; - } + internal static string EnsureAuthorityIsV2(string authority) + { + authority = authority.Trim().TrimEnd('/'); + if (!authority.EndsWith("v2.0", StringComparison.InvariantCulture)) + { + authority += "/v2.0"; + } - return authority; - } - } + return authority; + } + } } diff --git a/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs b/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs index 097547d93..2469f2a7b 100644 --- a/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs +++ b/src/Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs @@ -14,152 +14,152 @@ namespace Microsoft.Identity.Web { - /// - /// Filter used on a controller action to trigger incremental consent. - /// - /// - /// The following controller action will trigger. - /// - /// [AuthorizeForScopes(Scopes = new[] {"Mail.Send"})] - /// public async Task<IActionResult> SendEmail() - /// { - /// } - /// - /// - public class AuthorizeForScopesAttribute : ExceptionFilterAttribute - { - /// - /// Scopes to request. - /// - public string[]? Scopes { get; set; } - - /// - /// Key section on the configuration file that holds the scope value. - /// - public string? ScopeKeySection { get; set; } - - /// - /// Azure AD B2C user flow. - /// - public string? UserFlow { get; set; } - - /// - /// Allows specifying an AuthenticationScheme if OpenIdConnect is not the default challenge scheme. - /// - public string? AuthenticationScheme { get; set; } - - /// - /// Handles the . - /// - /// Context provided by ASP.NET Core. - public override void OnException(ExceptionContext context) - { - if (context != null) - { - MsalUiRequiredException? msalUiRequiredException = FindMsalUiRequiredExceptionIfAny(context.Exception); - if (msalUiRequiredException != null && - IncrementalConsentAndConditionalAccessHelper.CanBeSolvedByReSignInOfUser(msalUiRequiredException)) - { - // the users cannot provide both scopes and ScopeKeySection at the same time - if (!string.IsNullOrWhiteSpace(ScopeKeySection) && Scopes != null && Scopes.Length > 0) - { - throw new InvalidOperationException( - string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.ProvideEitherScopeKeySectionOrScopes, - nameof(ScopeKeySection), - nameof(Scopes))); - } - - // Do not re-use the property Scopes. For more info: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/273 - string[]? incrementalConsentScopes; - - // If the user wishes us to pick the Scopes from a particular config setting. - if (!string.IsNullOrWhiteSpace(ScopeKeySection)) - { - // Load the injected IConfiguration - IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService(); - - if (configuration == null) - { - throw new InvalidOperationException( - string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.ScopeKeySectionIsProvidedButNotPresentInTheServicesCollection, - nameof(ScopeKeySection))); - } - - incrementalConsentScopes = new string[] { configuration.GetValue(ScopeKeySection) }; - - if (Scopes != null && Scopes.Length > 0 && incrementalConsentScopes.Length > 0) - { - throw new InvalidOperationException(IDWebErrorMessage.NoScopesProvided); - } - } - else - { - incrementalConsentScopes = Scopes; - } - - AuthenticationProperties properties = IncrementalConsentAndConditionalAccessHelper.BuildAuthenticationProperties( - incrementalConsentScopes, - msalUiRequiredException, - context.HttpContext.User, - UserFlow); - - if (IsAjaxRequest(context.HttpContext.Request) && (!string.IsNullOrEmpty(context.HttpContext.Request.Headers[Constants.XReturnUrl]) - || !string.IsNullOrEmpty(context.HttpContext.Request.Query[Constants.XReturnUrl]))) - { - string redirectUri = !string.IsNullOrEmpty(context.HttpContext.Request.Headers[Constants.XReturnUrl]) ? context.HttpContext.Request.Headers[Constants.XReturnUrl] - : context.HttpContext.Request.Query[Constants.XReturnUrl]; - - UrlHelper urlHelper = new UrlHelper(context); - if (urlHelper.IsLocalUrl(redirectUri)) - { - properties.RedirectUri = redirectUri; - } - } - - if (AuthenticationScheme != null) - { - context.Result = new ChallengeResult(AuthenticationScheme, properties); - } - else - { - context.Result = new ChallengeResult(properties); - } - } - } - - base.OnException(context); - } - - /// - /// Finds an MsalUiRequiredException in one of the inner exceptions. - /// - /// Exception from which we look for an MsalUiRequiredException. - /// The MsalUiRequiredException if there is one, null, otherwise. - internal /* for testing */ static MsalUiRequiredException? FindMsalUiRequiredExceptionIfAny(Exception exception) - { - MsalUiRequiredException? msalUiRequiredException = exception as MsalUiRequiredException; - if (msalUiRequiredException != null) - { - return msalUiRequiredException; - } - else if (exception.InnerException != null) - { - return FindMsalUiRequiredExceptionIfAny(exception.InnerException); - } - else - { - return null; - } - } - - private static bool IsAjaxRequest(HttpRequest request) - { - return string.Equals(request.Query[Constants.XRequestedWith], Constants.XmlHttpRequest, StringComparison.Ordinal) || - string.Equals(request.Headers[Constants.XRequestedWith], Constants.XmlHttpRequest, StringComparison.Ordinal); - } - } + /// + /// Filter used on a controller action to trigger incremental consent. + /// + /// + /// The following controller action will trigger. + /// + /// [AuthorizeForScopes(Scopes = new[] {"Mail.Send"})] + /// public async Task<IActionResult> SendEmail() + /// { + /// } + /// + /// + public class AuthorizeForScopesAttribute : ExceptionFilterAttribute + { + /// + /// Scopes to request. + /// + public string[]? Scopes { get; set; } + + /// + /// Key section on the configuration file that holds the scope value. + /// + public string? ScopeKeySection { get; set; } + + /// + /// Azure AD B2C user flow. + /// + public string? UserFlow { get; set; } + + /// + /// Allows specifying an AuthenticationScheme if OpenIdConnect is not the default challenge scheme. + /// + public string? AuthenticationScheme { get; set; } + + /// + /// Handles the . + /// + /// Context provided by ASP.NET Core. + public override void OnException(ExceptionContext context) + { + if (context != null) + { + MsalUiRequiredException? msalUiRequiredException = FindMsalUiRequiredExceptionIfAny(context.Exception); + if (msalUiRequiredException != null && + IncrementalConsentAndConditionalAccessHelper.CanBeSolvedByReSignInOfUser(msalUiRequiredException)) + { + // the users cannot provide both scopes and ScopeKeySection at the same time + if (!string.IsNullOrWhiteSpace(ScopeKeySection) && Scopes != null && Scopes.Length > 0) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.ProvideEitherScopeKeySectionOrScopes, + nameof(ScopeKeySection), + nameof(Scopes))); + } + + // Do not re-use the property Scopes. For more info: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/273 + string[]? incrementalConsentScopes; + + // If the user wishes us to pick the Scopes from a particular config setting. + if (!string.IsNullOrWhiteSpace(ScopeKeySection)) + { + // Load the injected IConfiguration + IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService(); + + if (configuration == null) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.ScopeKeySectionIsProvidedButNotPresentInTheServicesCollection, + nameof(ScopeKeySection))); + } + + incrementalConsentScopes = new string[] { configuration.GetValue(ScopeKeySection) }; + + if (Scopes != null && Scopes.Length > 0 && incrementalConsentScopes.Length > 0) + { + throw new InvalidOperationException(IDWebErrorMessage.NoScopesProvided); + } + } + else + { + incrementalConsentScopes = Scopes; + } + + AuthenticationProperties properties = IncrementalConsentAndConditionalAccessHelper.BuildAuthenticationProperties( + incrementalConsentScopes, + msalUiRequiredException, + context.HttpContext.User, + UserFlow); + + if (IsAjaxRequest(context.HttpContext.Request) && (!string.IsNullOrEmpty(context.HttpContext.Request.Headers[Constants.XReturnUrl]) + || !string.IsNullOrEmpty(context.HttpContext.Request.Query[Constants.XReturnUrl]))) + { + string redirectUri = !string.IsNullOrEmpty(context.HttpContext.Request.Headers[Constants.XReturnUrl]) ? context.HttpContext.Request.Headers[Constants.XReturnUrl] + : context.HttpContext.Request.Query[Constants.XReturnUrl]; + + UrlHelper urlHelper = new UrlHelper(context); + if (urlHelper.IsLocalUrl(redirectUri)) + { + properties.RedirectUri = redirectUri; + } + } + + if (AuthenticationScheme != null) + { + context.Result = new ChallengeResult(AuthenticationScheme, properties); + } + else + { + context.Result = new ChallengeResult(properties); + } + } + } + + base.OnException(context); + } + + /// + /// Finds an MsalUiRequiredException in one of the inner exceptions. + /// + /// Exception from which we look for an MsalUiRequiredException. + /// The MsalUiRequiredException if there is one, null, otherwise. + internal /* for testing */ static MsalUiRequiredException? FindMsalUiRequiredExceptionIfAny(Exception exception) + { + MsalUiRequiredException? msalUiRequiredException = exception as MsalUiRequiredException; + if (msalUiRequiredException != null) + { + return msalUiRequiredException; + } + else if (exception.InnerException != null) + { + return FindMsalUiRequiredExceptionIfAny(exception.InnerException); + } + else + { + return null; + } + } + + private static bool IsAjaxRequest(HttpRequest request) + { + return string.Equals(request.Query[Constants.XRequestedWith], Constants.XmlHttpRequest, StringComparison.Ordinal) || + string.Equals(request.Headers[Constants.XRequestedWith], Constants.XmlHttpRequest, StringComparison.Ordinal); + } + } } diff --git a/src/Microsoft.Identity.Web/AzureADB2COpenIDConnectEventHandlers.cs b/src/Microsoft.Identity.Web/AzureADB2COpenIDConnectEventHandlers.cs index 364f04bed..7f154a455 100644 --- a/src/Microsoft.Identity.Web/AzureADB2COpenIDConnectEventHandlers.cs +++ b/src/Microsoft.Identity.Web/AzureADB2COpenIDConnectEventHandlers.cs @@ -10,103 +10,103 @@ namespace Microsoft.Identity.Web { - internal class AzureADB2COpenIDConnectEventHandlers - { - private readonly ILoginErrorAccessor _errorAccessor; - - private readonly IDictionary _userFlowToIssuerAddress = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - public AzureADB2COpenIDConnectEventHandlers( - string schemeName, - MicrosoftIdentityOptions options, - ILoginErrorAccessor errorAccessor) - { - SchemeName = schemeName; - Options = options; - _errorAccessor = errorAccessor; - } - - public string SchemeName { get; } - - public MicrosoftIdentityOptions Options { get; } - - public Task OnRedirectToIdentityProvider(RedirectContext context) - { - var defaultUserFlow = Options.DefaultUserFlow; - if (context.Properties.Items.TryGetValue(OidcConstants.PolicyKey, out var userFlow) && - !string.IsNullOrEmpty(userFlow) && - !string.Equals(userFlow, defaultUserFlow, StringComparison.OrdinalIgnoreCase)) - { - context.ProtocolMessage.IssuerAddress = BuildIssuerAddress(context, defaultUserFlow, userFlow); - context.Properties.Items.Remove(OidcConstants.PolicyKey); - - if (!Options.HasClientCredentials) - { - context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken; - } - else - { - if (Options.IsB2C) - { - context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken; - } - else - { - context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.Code; - } - } - } - - return Task.CompletedTask; - } - - public Task OnRemoteFailure(RemoteFailureContext context) - { - context.HandleResponse(); - - // Handle the error code that Azure Active Directory B2C throws when trying to reset a password from the login page - // because password reset is not supported by a "sign-up or sign-in user flow". - // Below is a sample error message: - // 'access_denied', error_description: 'AADB2C90118: The user has forgotten their password. - // Correlation ID: f99deff4-f43b-43cc-b4e7-36141dbaf0a0 - // Timestamp: 2018-03-05 02:49:35Z - // ', error_uri: 'error_uri is null'. - if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains(ErrorCodes.B2CForgottenPassword)) - { - // If the user clicked the reset password link, redirect to the reset password route - context.Response.Redirect($"{context.Request.PathBase}/MicrosoftIdentity/Account/ResetPassword/{SchemeName}"); - } - - // Access denied errors happen when a user cancels an action on the Azure Active Directory B2C UI. We just redirect back to - // the main page in that case. - // Message contains error: 'access_denied', error_description: 'AADB2C90091: The user has canceled entering self-asserted information. - // Correlation ID: d01c8878-0732-4eb2-beb8-da82a57432e0 - // Timestamp: 2018-03-05 02:56:49Z - // ', error_uri: 'error_uri is null'. - else if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains(ErrorCodes.AccessDenied)) - { - context.Response.Redirect($"{context.Request.PathBase}/"); - } - else - { - _errorAccessor.SetMessage(context.HttpContext, context.Failure?.Message); - - context.Response.Redirect($"{context.Request.PathBase}/MicrosoftIdentity/Account/Error"); - } - - return Task.CompletedTask; - } - - private string BuildIssuerAddress(RedirectContext context, string? defaultUserFlow, string userFlow) - { - if (!_userFlowToIssuerAddress.TryGetValue(userFlow, out var issuerAddress)) - { - _userFlowToIssuerAddress[userFlow] = context.ProtocolMessage.IssuerAddress.ToLowerInvariant() - .Replace($"/{defaultUserFlow?.ToLowerInvariant()}/", $"/{userFlow.ToLowerInvariant()}/"); - } - - return _userFlowToIssuerAddress[userFlow]; - } - } + internal class AzureADB2COpenIDConnectEventHandlers + { + private readonly ILoginErrorAccessor _errorAccessor; + + private readonly IDictionary _userFlowToIssuerAddress = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + public AzureADB2COpenIDConnectEventHandlers( + string schemeName, + MicrosoftIdentityOptions options, + ILoginErrorAccessor errorAccessor) + { + SchemeName = schemeName; + Options = options; + _errorAccessor = errorAccessor; + } + + public string SchemeName { get; } + + public MicrosoftIdentityOptions Options { get; } + + public Task OnRedirectToIdentityProvider(RedirectContext context) + { + var defaultUserFlow = Options.DefaultUserFlow; + if (context.Properties.Items.TryGetValue(OidcConstants.PolicyKey, out var userFlow) && + !string.IsNullOrEmpty(userFlow) && + !string.Equals(userFlow, defaultUserFlow, StringComparison.OrdinalIgnoreCase)) + { + context.ProtocolMessage.IssuerAddress = BuildIssuerAddress(context, defaultUserFlow, userFlow); + context.Properties.Items.Remove(OidcConstants.PolicyKey); + + if (!Options.HasClientCredentials) + { + context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken; + } + else + { + if (Options.IsB2C) + { + context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken; + } + else + { + context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.Code; + } + } + } + + return Task.CompletedTask; + } + + public Task OnRemoteFailure(RemoteFailureContext context) + { + context.HandleResponse(); + + // Handle the error code that Azure Active Directory B2C throws when trying to reset a password from the login page + // because password reset is not supported by a "sign-up or sign-in user flow". + // Below is a sample error message: + // 'access_denied', error_description: 'AADB2C90118: The user has forgotten their password. + // Correlation ID: f99deff4-f43b-43cc-b4e7-36141dbaf0a0 + // Timestamp: 2018-03-05 02:49:35Z + // ', error_uri: 'error_uri is null'. + if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains(ErrorCodes.B2CForgottenPassword)) + { + // If the user clicked the reset password link, redirect to the reset password route + context.Response.Redirect($"{context.Request.PathBase}/MicrosoftIdentity/Account/ResetPassword/{SchemeName}"); + } + + // Access denied errors happen when a user cancels an action on the Azure Active Directory B2C UI. We just redirect back to + // the main page in that case. + // Message contains error: 'access_denied', error_description: 'AADB2C90091: The user has canceled entering self-asserted information. + // Correlation ID: d01c8878-0732-4eb2-beb8-da82a57432e0 + // Timestamp: 2018-03-05 02:56:49Z + // ', error_uri: 'error_uri is null'. + else if (context.Failure is OpenIdConnectProtocolException && context.Failure.Message.Contains(ErrorCodes.AccessDenied)) + { + context.Response.Redirect($"{context.Request.PathBase}/"); + } + else + { + _errorAccessor.SetMessage(context.HttpContext, context.Failure?.Message); + + context.Response.Redirect($"{context.Request.PathBase}/MicrosoftIdentity/Account/Error"); + } + + return Task.CompletedTask; + } + + private string BuildIssuerAddress(RedirectContext context, string? defaultUserFlow, string userFlow) + { + if (!_userFlowToIssuerAddress.TryGetValue(userFlow, out var issuerAddress)) + { + _userFlowToIssuerAddress[userFlow] = context.ProtocolMessage.IssuerAddress.ToLowerInvariant() + .Replace($"/{defaultUserFlow?.ToLowerInvariant()}/", $"/{userFlow.ToLowerInvariant()}/"); + } + + return _userFlowToIssuerAddress[userFlow]; + } + } } diff --git a/src/Microsoft.Identity.Web/AzureFunctionsAuthenticationHttpContextExtension.cs b/src/Microsoft.Identity.Web/AzureFunctionsAuthenticationHttpContextExtension.cs index 9ff4aebc5..82f2e1780 100644 --- a/src/Microsoft.Identity.Web/AzureFunctionsAuthenticationHttpContextExtension.cs +++ b/src/Microsoft.Identity.Web/AzureFunctionsAuthenticationHttpContextExtension.cs @@ -9,42 +9,42 @@ namespace Microsoft.Identity.Web { - /// - /// Extensions for . - /// - public static class AzureFunctionsAuthenticationHttpContextExtension - { - /// - /// Enables an Azure Function to act as/expose a protected web API, enabling bearer token authentication. Calling this method from your Azure function validates the token and exposes the identity of the user or app on behalf of which your function is called, in the HttpContext.User member, where your function can make use of it. - /// - /// The current HTTP Context, such as req.HttpContext. - /// A task indicating success or failure. In case of failure . - public static async Task<(bool, IActionResult?)> AuthenticateAzureFunctionAsync( - this HttpContext httpContext) - { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } + /// + /// Extensions for . + /// + public static class AzureFunctionsAuthenticationHttpContextExtension + { + /// + /// Enables an Azure Function to act as/expose a protected web API, enabling bearer token authentication. Calling this method from your Azure function validates the token and exposes the identity of the user or app on behalf of which your function is called, in the HttpContext.User member, where your function can make use of it. + /// + /// The current HTTP Context, such as req.HttpContext. + /// A task indicating success or failure. In case of failure . + public static async Task<(bool, IActionResult?)> AuthenticateAzureFunctionAsync( + this HttpContext httpContext) + { + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } - AuthenticateResult result = - await httpContext.AuthenticateAsync(Constants.Bearer).ConfigureAwait(false); + AuthenticateResult result = + await httpContext.AuthenticateAsync(Constants.Bearer).ConfigureAwait(false); - if (result != null && result.Succeeded) - { + if (result != null && result.Succeeded) + { #pragma warning disable CS8601 // Possible null reference assignment. - httpContext.User = result.Principal; + httpContext.User = result.Principal; #pragma warning restore CS8601 // Possible null reference assignment. - return (true, null); - } - else - { - return (false, new UnauthorizedObjectResult(new ProblemDetails - { - Title = "Authorization failed.", - Detail = result?.Failure?.Message, - })); - } - } - } + return (true, null); + } + else + { + return (false, new UnauthorizedObjectResult(new ProblemDetails + { + Title = "Authorization failed.", + Detail = result?.Failure?.Message, + })); + } + } + } } diff --git a/src/Microsoft.Identity.Web/Base64UrlHelpers.cs b/src/Microsoft.Identity.Web/Base64UrlHelpers.cs index aea897e26..af4d63cdd 100644 --- a/src/Microsoft.Identity.Web/Base64UrlHelpers.cs +++ b/src/Microsoft.Identity.Web/Base64UrlHelpers.cs @@ -7,80 +7,80 @@ namespace Microsoft.Identity.Web { - internal static class Base64UrlHelpers - { - private const char Base64PadCharacter = '='; - private const char Base64Character62 = '+'; - private const char Base64Character63 = '/'; - private const char Base64UrlCharacter62 = '-'; - private const char Base64UrlCharacter63 = '_'; - private static readonly Encoding TextEncoding = Encoding.UTF8; + internal static class Base64UrlHelpers + { + private const char Base64PadCharacter = '='; + private const char Base64Character62 = '+'; + private const char Base64Character63 = '/'; + private const char Base64UrlCharacter62 = '-'; + private const char Base64UrlCharacter63 = '_'; + private static readonly Encoding TextEncoding = Encoding.UTF8; - private static readonly string DoubleBase64PadCharacter = string.Format(CultureInfo.InvariantCulture, "{0}{0}", Base64PadCharacter); + private static readonly string DoubleBase64PadCharacter = string.Format(CultureInfo.InvariantCulture, "{0}{0}", Base64PadCharacter); - // The following functions perform Base64URL encoding which differs from regular Base64 encoding: - // * Padding is skipped so the pad character '=' doesn't have to be percent encoded. - // * The 62nd and 63rd regular Base64 encoding characters ('+' and '/') are replaced with ('-' and '_'). - // The changes make the encoding alphabet file and URL safe. - // See RFC4648, section 5 for more info. - public static string? Encode(string arg) - { - if (arg == null) - { - return null; - } + // The following functions perform Base64URL encoding which differs from regular Base64 encoding: + // * Padding is skipped so the pad character '=' doesn't have to be percent encoded. + // * The 62nd and 63rd regular Base64 encoding characters ('+' and '/') are replaced with ('-' and '_'). + // The changes make the encoding alphabet file and URL safe. + // See RFC4648, section 5 for more info. + public static string? Encode(string arg) + { + if (arg == null) + { + return null; + } - return Encode(TextEncoding.GetBytes(arg)); - } + return Encode(TextEncoding.GetBytes(arg)); + } - public static string DecodeToString(string arg) - { - byte[] decoded = DecodeToBytes(arg); - return CreateString(decoded); - } + public static string DecodeToString(string arg) + { + byte[] decoded = DecodeToBytes(arg); + return CreateString(decoded); + } - public static string CreateString(byte[] bytes) - { - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } + public static string CreateString(byte[] bytes) + { + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } - public static byte[] DecodeToBytes(string arg) - { - string s = arg; - s = s.Replace(Base64UrlCharacter62, Base64Character62); // 62nd char of encoding - s = s.Replace(Base64UrlCharacter63, Base64Character63); // 63rd char of encoding + public static byte[] DecodeToBytes(string arg) + { + string s = arg; + s = s.Replace(Base64UrlCharacter62, Base64Character62); // 62nd char of encoding + s = s.Replace(Base64UrlCharacter63, Base64Character63); // 63rd char of encoding - switch (s.Length % 4) - { - // Pad - case 0: - break; // No pad chars in this case - case 2: - s += DoubleBase64PadCharacter; - break; // Two pad chars - case 3: - s += Base64PadCharacter; - break; // One pad char - default: - throw new ArgumentException(IDWebErrorMessage.InvalidBase64UrlString, nameof(arg)); - } + switch (s.Length % 4) + { + // Pad + case 0: + break; // No pad chars in this case + case 2: + s += DoubleBase64PadCharacter; + break; // Two pad chars + case 3: + s += Base64PadCharacter; + break; // One pad char + default: + throw new ArgumentException(IDWebErrorMessage.InvalidBase64UrlString, nameof(arg)); + } - return Convert.FromBase64String(s); // Standard Base64 decoder - } + return Convert.FromBase64String(s); // Standard Base64 decoder + } - internal static string? Encode(byte[] arg) - { - if (arg == null) - { - return null; - } + internal static string? Encode(byte[] arg) + { + if (arg == null) + { + return null; + } - string s = Convert.ToBase64String(arg); - s = s.Split(Base64PadCharacter)[0]; // Remove any trailing padding - s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding - s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding + string s = Convert.ToBase64String(arg); + s = s.Split(Base64PadCharacter)[0]; // Remove any trailing padding + s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding + s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding - return s; - } - } + return s; + } + } } diff --git a/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs b/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs index abe0b2598..45a941189 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/CertificateDescription.cs @@ -5,291 +5,291 @@ namespace Microsoft.Identity.Web { - /// - /// Description of a certificate. - /// - public class CertificateDescription - { - /// - /// Creates a certificate description from a certificate (by code). - /// - /// Certificate. - /// A certificate description. - public static CertificateDescription FromCertificate(X509Certificate2 x509certificate2) - { - return new CertificateDescription - { - SourceType = CertificateSource.Certificate, - Certificate = x509certificate2, - }; - } + /// + /// Description of a certificate. + /// + public class CertificateDescription + { + /// + /// Creates a certificate description from a certificate (by code). + /// + /// Certificate. + /// A certificate description. + public static CertificateDescription FromCertificate(X509Certificate2 x509certificate2) + { + return new CertificateDescription + { + SourceType = CertificateSource.Certificate, + Certificate = x509certificate2, + }; + } - /// - /// Creates a certificate description from Key Vault. - /// - /// The Key Vault URL. - /// The name of the certificate in Key Vault. - /// A certificate description. - public static CertificateDescription FromKeyVault( - string keyVaultUrl, - string keyVaultCertificateName) - { - return new CertificateDescription - { - SourceType = CertificateSource.KeyVault, - KeyVaultUrl = keyVaultUrl, - KeyVaultCertificateName = keyVaultCertificateName, - }; - } + /// + /// Creates a certificate description from Key Vault. + /// + /// The Key Vault URL. + /// The name of the certificate in Key Vault. + /// A certificate description. + public static CertificateDescription FromKeyVault( + string keyVaultUrl, + string keyVaultCertificateName) + { + return new CertificateDescription + { + SourceType = CertificateSource.KeyVault, + KeyVaultUrl = keyVaultUrl, + KeyVaultCertificateName = keyVaultCertificateName, + }; + } - /// - /// Creates a certificate description from a Base64 encoded value. - /// - /// Base64 encoded certificate value. - /// A certificate description. - public static CertificateDescription FromBase64Encoded(string base64EncodedValue) - { - return new CertificateDescription - { - SourceType = CertificateSource.Base64Encoded, - Base64EncodedValue = base64EncodedValue, - }; - } + /// + /// Creates a certificate description from a Base64 encoded value. + /// + /// Base64 encoded certificate value. + /// A certificate description. + public static CertificateDescription FromBase64Encoded(string base64EncodedValue) + { + return new CertificateDescription + { + SourceType = CertificateSource.Base64Encoded, + Base64EncodedValue = base64EncodedValue, + }; + } - /// - /// Creates a certificate description from path on disk. - /// - /// Path where to find the certificate file. - /// Certificate password. - /// A certificate description. - public static CertificateDescription FromPath(string path, string? password = null) - { - return new CertificateDescription - { - SourceType = CertificateSource.Path, - CertificateDiskPath = path, - CertificatePassword = password, - }; - } + /// + /// Creates a certificate description from path on disk. + /// + /// Path where to find the certificate file. + /// Certificate password. + /// A certificate description. + public static CertificateDescription FromPath(string path, string? password = null) + { + return new CertificateDescription + { + SourceType = CertificateSource.Path, + CertificateDiskPath = path, + CertificatePassword = password, + }; + } - /// - /// Creates a certificate description from a thumbprint and store location (Certificate Manager on Windows, for instance). - /// - /// Certificate thumbprint. - /// Store location where to find the certificate. - /// Store name where to find the certificate. - /// A certificate description. - public static CertificateDescription FromStoreWithThumprint( - string certificateThumbprint, - StoreLocation certificateStoreLocation = StoreLocation.CurrentUser, - StoreName certificateStoreName = StoreName.My) - { - return new CertificateDescription - { - SourceType = CertificateSource.StoreWithThumbprint, - CertificateStorePath = $"{certificateStoreLocation}/{certificateStoreName}", - CertificateThumbprint = certificateThumbprint, - }; - } + /// + /// Creates a certificate description from a thumbprint and store location (Certificate Manager on Windows, for instance). + /// + /// Certificate thumbprint. + /// Store location where to find the certificate. + /// Store name where to find the certificate. + /// A certificate description. + public static CertificateDescription FromStoreWithThumprint( + string certificateThumbprint, + StoreLocation certificateStoreLocation = StoreLocation.CurrentUser, + StoreName certificateStoreName = StoreName.My) + { + return new CertificateDescription + { + SourceType = CertificateSource.StoreWithThumbprint, + CertificateStorePath = $"{certificateStoreLocation}/{certificateStoreName}", + CertificateThumbprint = certificateThumbprint, + }; + } - /// - /// Creates a certificate description from a certificate distinguished name (such as CN=name) - /// and store location (Certificate Manager on Windows, for instance). - /// - /// Certificate distinguished named. - /// Store location where to find the certificate. - /// Store name where to find the certificate. - /// A certificate description. - public static CertificateDescription FromStoreWithDistinguishedName( - string certificateDistinguishedName, - StoreLocation certificateStoreLocation = StoreLocation.CurrentUser, - StoreName certificateStoreName = StoreName.My) - { - return new CertificateDescription - { - SourceType = CertificateSource.StoreWithDistinguishedName, - CertificateStorePath = $"{certificateStoreLocation}/{certificateStoreName}", - CertificateDistinguishedName = certificateDistinguishedName, - }; - } + /// + /// Creates a certificate description from a certificate distinguished name (such as CN=name) + /// and store location (Certificate Manager on Windows, for instance). + /// + /// Certificate distinguished named. + /// Store location where to find the certificate. + /// Store name where to find the certificate. + /// A certificate description. + public static CertificateDescription FromStoreWithDistinguishedName( + string certificateDistinguishedName, + StoreLocation certificateStoreLocation = StoreLocation.CurrentUser, + StoreName certificateStoreName = StoreName.My) + { + return new CertificateDescription + { + SourceType = CertificateSource.StoreWithDistinguishedName, + CertificateStorePath = $"{certificateStoreLocation}/{certificateStoreName}", + CertificateDistinguishedName = certificateDistinguishedName, + }; + } - /// - /// Type of the source of the certificate. - /// - public CertificateSource SourceType { get; set; } + /// + /// Type of the source of the certificate. + /// + public CertificateSource SourceType { get; set; } - /// - /// Container in which to find the certificate. - /// - /// If equals , then - /// the container is the Key Vault base URL. - /// If equals , then - /// this value is not used. - /// If equals , then - /// this value is the path on disk where to find the certificate. - /// If equals , - /// or , then - /// this value is the path to the certificate in the cert store, for instance CurrentUser/My. - /// - /// - internal string? Container - { - get - { - switch (SourceType) - { - case CertificateSource.Certificate: - return null; - case CertificateSource.KeyVault: - return KeyVaultUrl; - case CertificateSource.Base64Encoded: - return null; - case CertificateSource.Path: - return CertificateDiskPath; - case CertificateSource.StoreWithThumbprint: - case CertificateSource.StoreWithDistinguishedName: - return CertificateStorePath; - default: - return null; - } - } - set - { - switch (SourceType) - { - case CertificateSource.Certificate: - break; - case CertificateSource.KeyVault: - KeyVaultUrl = value; - break; - case CertificateSource.Base64Encoded: - break; - case CertificateSource.Path: - CertificateDiskPath = value; - break; - case CertificateSource.StoreWithDistinguishedName: - case CertificateSource.StoreWithThumbprint: - CertificateStorePath = value; - break; - default: - break; - } - } - } + /// + /// Container in which to find the certificate. + /// + /// If equals , then + /// the container is the Key Vault base URL. + /// If equals , then + /// this value is not used. + /// If equals , then + /// this value is the path on disk where to find the certificate. + /// If equals , + /// or , then + /// this value is the path to the certificate in the cert store, for instance CurrentUser/My. + /// + /// + internal string? Container + { + get + { + switch (SourceType) + { + case CertificateSource.Certificate: + return null; + case CertificateSource.KeyVault: + return KeyVaultUrl; + case CertificateSource.Base64Encoded: + return null; + case CertificateSource.Path: + return CertificateDiskPath; + case CertificateSource.StoreWithThumbprint: + case CertificateSource.StoreWithDistinguishedName: + return CertificateStorePath; + default: + return null; + } + } + set + { + switch (SourceType) + { + case CertificateSource.Certificate: + break; + case CertificateSource.KeyVault: + KeyVaultUrl = value; + break; + case CertificateSource.Base64Encoded: + break; + case CertificateSource.Path: + CertificateDiskPath = value; + break; + case CertificateSource.StoreWithDistinguishedName: + case CertificateSource.StoreWithThumbprint: + CertificateStorePath = value; + break; + default: + break; + } + } + } - /// - /// URL of the Key Vault, for instance https://msidentitywebsamples.vault.azure.net. - /// - public string? KeyVaultUrl { get; set; } + /// + /// URL of the Key Vault, for instance https://msidentitywebsamples.vault.azure.net. + /// + public string? KeyVaultUrl { get; set; } - /// - /// Certificate store path, for instance "CurrentUser/My". - /// - /// This property should only be used in conjunction with DistinguishedName or Thumbprint. - public string? CertificateStorePath { get; set; } + /// + /// Certificate store path, for instance "CurrentUser/My". + /// + /// This property should only be used in conjunction with DistinguishedName or Thumbprint. + public string? CertificateStorePath { get; set; } - /// - /// Certificate distinguished name. - /// - public string? CertificateDistinguishedName { get; set; } + /// + /// Certificate distinguished name. + /// + public string? CertificateDistinguishedName { get; set; } - /// - /// Name of the certificate in Key Vault. - /// - public string? KeyVaultCertificateName { get; set; } + /// + /// Name of the certificate in Key Vault. + /// + public string? KeyVaultCertificateName { get; set; } - /// - /// Certificate thumbprint. - /// - public string? CertificateThumbprint { get; set; } + /// + /// Certificate thumbprint. + /// + public string? CertificateThumbprint { get; set; } - /// - /// Path on disk to the certificate. - /// - public string? CertificateDiskPath { get; set; } + /// + /// Path on disk to the certificate. + /// + public string? CertificateDiskPath { get; set; } - /// - /// Path on disk to the certificate password. - /// - public string? CertificatePassword { get; set; } + /// + /// Path on disk to the certificate password. + /// + public string? CertificatePassword { get; set; } - /// - /// Base64 encoded certificate value. - /// - public string? Base64EncodedValue { get; set; } + /// + /// Base64 encoded certificate value. + /// + public string? Base64EncodedValue { get; set; } - /// - /// Defines where and how to import the private key of an X.509 certificate. - /// - public X509KeyStorageFlags X509KeyStorageFlags { get; set; } = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet; + /// + /// Defines where and how to import the private key of an X.509 certificate. + /// + public X509KeyStorageFlags X509KeyStorageFlags { get; set; } = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet; - /// - /// Reference to the certificate or value. - /// - /// - /// If equals , then - /// the reference is the name of the certificate in Key Vault (maybe the version?). - /// If equals , then - /// this value is the base 64 encoded certificate itself. - /// If equals , then - /// this value is the password to access the certificate (if needed). - /// If equals , - /// this value is the distinguished name. - /// If equals , - /// this value is the thumbprint. - /// - internal string? ReferenceOrValue - { - get - { - switch (SourceType) - { - case CertificateSource.KeyVault: - return KeyVaultCertificateName; - case CertificateSource.Path: - return CertificatePassword; - case CertificateSource.StoreWithThumbprint: - return CertificateThumbprint; - case CertificateSource.StoreWithDistinguishedName: - return CertificateDistinguishedName; - case CertificateSource.Certificate: - case CertificateSource.Base64Encoded: - return Base64EncodedValue; - default: - return null; - } - } - set - { - switch (SourceType) - { - case CertificateSource.Certificate: - break; - case CertificateSource.KeyVault: - KeyVaultCertificateName = value; - break; - case CertificateSource.Base64Encoded: - Base64EncodedValue = value; - break; - case CertificateSource.Path: - CertificateDiskPath = value; - break; - case CertificateSource.StoreWithThumbprint: - CertificateThumbprint = value; - break; - case CertificateSource.StoreWithDistinguishedName: - CertificateDistinguishedName = value; - break; - default: - break; - } - } - } + /// + /// Reference to the certificate or value. + /// + /// + /// If equals , then + /// the reference is the name of the certificate in Key Vault (maybe the version?). + /// If equals , then + /// this value is the base 64 encoded certificate itself. + /// If equals , then + /// this value is the password to access the certificate (if needed). + /// If equals , + /// this value is the distinguished name. + /// If equals , + /// this value is the thumbprint. + /// + internal string? ReferenceOrValue + { + get + { + switch (SourceType) + { + case CertificateSource.KeyVault: + return KeyVaultCertificateName; + case CertificateSource.Path: + return CertificatePassword; + case CertificateSource.StoreWithThumbprint: + return CertificateThumbprint; + case CertificateSource.StoreWithDistinguishedName: + return CertificateDistinguishedName; + case CertificateSource.Certificate: + case CertificateSource.Base64Encoded: + return Base64EncodedValue; + default: + return null; + } + } + set + { + switch (SourceType) + { + case CertificateSource.Certificate: + break; + case CertificateSource.KeyVault: + KeyVaultCertificateName = value; + break; + case CertificateSource.Base64Encoded: + Base64EncodedValue = value; + break; + case CertificateSource.Path: + CertificateDiskPath = value; + break; + case CertificateSource.StoreWithThumbprint: + CertificateThumbprint = value; + break; + case CertificateSource.StoreWithDistinguishedName: + CertificateDistinguishedName = value; + break; + default: + break; + } + } + } - /// - /// The certificate, either provided directly in code - /// or loaded from the description. - /// - public X509Certificate2? Certificate { get; internal set; } - } + /// + /// The certificate, either provided directly in code + /// or loaded from the description. + /// + public X509Certificate2? Certificate { get; internal set; } + } } diff --git a/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs b/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs index 97827eeee..a8b010330 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/CertificateSource.cs @@ -3,39 +3,39 @@ namespace Microsoft.Identity.Web { - /// - /// Source for a certificate. - /// - public enum CertificateSource - { - /// - /// Certificate itself. - /// - Certificate = 0, + /// + /// Source for a certificate. + /// + public enum CertificateSource + { + /// + /// Certificate itself. + /// + Certificate = 0, - /// - /// From an Azure Key Vault. - /// - KeyVault = 1, + /// + /// From an Azure Key Vault. + /// + KeyVault = 1, - /// - /// Base64 encoded string directly from the configuration. - /// - Base64Encoded = 2, + /// + /// Base64 encoded string directly from the configuration. + /// + Base64Encoded = 2, - /// - /// From local path on disk. - /// - Path = 3, + /// + /// From local path on disk. + /// + Path = 3, - /// - /// From the certificate store, described by its thumbprint. - /// - StoreWithThumbprint = 4, + /// + /// From the certificate store, described by its thumbprint. + /// + StoreWithThumbprint = 4, - /// - /// From the certificate store, described by its distinguished name. - /// - StoreWithDistinguishedName = 5, - } + /// + /// From the certificate store, described by its distinguished name. + /// + StoreWithDistinguishedName = 5, + } } diff --git a/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs b/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs index afac6bac7..d071b4f8f 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/DefaultCertificateLoader.cs @@ -13,265 +13,265 @@ namespace Microsoft.Identity.Web { - /// - /// Certificate Loader. - /// Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. - /// For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. - /// - /// IConfidentialClientApplication app; - /// ICertificateLoader certificateLoader = new DefaultCertificateLoader(); - /// certificateLoader.LoadIfNeeded(config.CertificateDescription); - /// - /// app = ConfidentialClientApplicationBuilder.Create(config.ClientId) - /// .WithCertificate(config.CertificateDescription.Certificate) - /// .WithAuthority(new Uri(config.Authority)) - /// .Build(); - /// - /// - public class DefaultCertificateLoader : ICertificateLoader - { - /// - /// User assigned managed identity client ID (as opposed to system assigned managed identity) - /// See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. - /// - public static string? UserAssignedManagedIdentityClientId { get; set; } + /// + /// Certificate Loader. + /// Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. + /// For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. + /// + /// IConfidentialClientApplication app; + /// ICertificateLoader certificateLoader = new DefaultCertificateLoader(); + /// certificateLoader.LoadIfNeeded(config.CertificateDescription); + /// + /// app = ConfidentialClientApplicationBuilder.Create(config.ClientId) + /// .WithCertificate(config.CertificateDescription.Certificate) + /// .WithAuthority(new Uri(config.Authority)) + /// .Build(); + /// + /// + public class DefaultCertificateLoader : ICertificateLoader + { + /// + /// User assigned managed identity client ID (as opposed to system assigned managed identity) + /// See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. + /// + public static string? UserAssignedManagedIdentityClientId { get; set; } - /// - /// Load the certificate from the description, if needed. - /// - /// Description of the certificate. - public void LoadIfNeeded(CertificateDescription certificateDescription) - { - if (certificateDescription == null) - { - throw new ArgumentNullException(nameof(certificateDescription)); - } + /// + /// Load the certificate from the description, if needed. + /// + /// Description of the certificate. + public void LoadIfNeeded(CertificateDescription certificateDescription) + { + if (certificateDescription == null) + { + throw new ArgumentNullException(nameof(certificateDescription)); + } - if (certificateDescription.Certificate == null) - { - switch (certificateDescription.SourceType) - { - case CertificateSource.KeyVault: - certificateDescription.Certificate = LoadFromKeyVault( - certificateDescription.Container!, - certificateDescription.ReferenceOrValue!, - certificateDescription.X509KeyStorageFlags!); - break; - case CertificateSource.Base64Encoded: - certificateDescription.Certificate = LoadFromBase64Encoded( - certificateDescription.ReferenceOrValue!, - certificateDescription.X509KeyStorageFlags!); - break; - case CertificateSource.Path: - certificateDescription.Certificate = LoadFromPath( - certificateDescription.Container!, - certificateDescription.ReferenceOrValue!); - break; - case CertificateSource.StoreWithThumbprint: - certificateDescription.Certificate = LoadFromStoreWithThumbprint( - certificateDescription.ReferenceOrValue!, - certificateDescription.Container!); - break; - case CertificateSource.StoreWithDistinguishedName: - certificateDescription.Certificate = LoadFromStoreWithDistinguishedName( - certificateDescription.ReferenceOrValue!, - certificateDescription.Container!); - break; - default: - break; - } - } - } + if (certificateDescription.Certificate == null) + { + switch (certificateDescription.SourceType) + { + case CertificateSource.KeyVault: + certificateDescription.Certificate = LoadFromKeyVault( + certificateDescription.Container!, + certificateDescription.ReferenceOrValue!, + certificateDescription.X509KeyStorageFlags!); + break; + case CertificateSource.Base64Encoded: + certificateDescription.Certificate = LoadFromBase64Encoded( + certificateDescription.ReferenceOrValue!, + certificateDescription.X509KeyStorageFlags!); + break; + case CertificateSource.Path: + certificateDescription.Certificate = LoadFromPath( + certificateDescription.Container!, + certificateDescription.ReferenceOrValue!); + break; + case CertificateSource.StoreWithThumbprint: + certificateDescription.Certificate = LoadFromStoreWithThumbprint( + certificateDescription.ReferenceOrValue!, + certificateDescription.Container!); + break; + case CertificateSource.StoreWithDistinguishedName: + certificateDescription.Certificate = LoadFromStoreWithDistinguishedName( + certificateDescription.ReferenceOrValue!, + certificateDescription.Container!); + break; + default: + break; + } + } + } - private static X509Certificate2 LoadFromBase64Encoded(string certificateBase64, X509KeyStorageFlags x509KeyStorageFlags) - { - byte[] decoded = Convert.FromBase64String(certificateBase64); - return new X509Certificate2( - decoded, - (string?)null, - x509KeyStorageFlags); - } + private static X509Certificate2 LoadFromBase64Encoded(string certificateBase64, X509KeyStorageFlags x509KeyStorageFlags) + { + byte[] decoded = Convert.FromBase64String(certificateBase64); + return new X509Certificate2( + decoded, + (string?)null, + x509KeyStorageFlags); + } - /// - /// Load a certificate from Key Vault, including the private key. - /// - /// URL of Key Vault. - /// Name of the certificate. - /// Defines where and how to import the private key of an X.509 certificate. - /// An certificate. - /// This code is inspired by Heath Stewart's code in: - /// https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs#L46-L82. - /// - private static X509Certificate2 LoadFromKeyVault( - string keyVaultUrl, - string certificateName, - X509KeyStorageFlags x509KeyStorageFlags) - { - Uri keyVaultUri = new Uri(keyVaultUrl); - DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); - options.ManagedIdentityClientId = UserAssignedManagedIdentityClientId; - DefaultAzureCredential credential = new DefaultAzureCredential(options); - CertificateClient certificateClient = new CertificateClient(keyVaultUri, credential); - SecretClient secretClient = new SecretClient(keyVaultUri, credential); + /// + /// Load a certificate from Key Vault, including the private key. + /// + /// URL of Key Vault. + /// Name of the certificate. + /// Defines where and how to import the private key of an X.509 certificate. + /// An certificate. + /// This code is inspired by Heath Stewart's code in: + /// https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs#L46-L82. + /// + private static X509Certificate2 LoadFromKeyVault( + string keyVaultUrl, + string certificateName, + X509KeyStorageFlags x509KeyStorageFlags) + { + Uri keyVaultUri = new Uri(keyVaultUrl); + DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); + options.ManagedIdentityClientId = UserAssignedManagedIdentityClientId; + DefaultAzureCredential credential = new DefaultAzureCredential(options); + CertificateClient certificateClient = new CertificateClient(keyVaultUri, credential); + SecretClient secretClient = new SecretClient(keyVaultUri, credential); - KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName); + KeyVaultCertificateWithPolicy certificate = certificateClient.GetCertificate(certificateName); - // Return a certificate with only the public key if the private key is not exportable. - if (certificate.Policy?.Exportable != true) - { - return new X509Certificate2( - certificate.Cer, - (string?)null, - x509KeyStorageFlags); - } + // Return a certificate with only the public key if the private key is not exportable. + if (certificate.Policy?.Exportable != true) + { + return new X509Certificate2( + certificate.Cer, + (string?)null, + x509KeyStorageFlags); + } - // Parse the secret ID and version to retrieve the private key. - string[] segments = certificate.SecretId.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - if (segments.Length != 3) - { - throw new InvalidOperationException(string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.IncorrectNumberOfUriSegments, - segments.Length, - certificate.SecretId)); - } + // Parse the secret ID and version to retrieve the private key. + string[] segments = certificate.SecretId.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if (segments.Length != 3) + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.IncorrectNumberOfUriSegments, + segments.Length, + certificate.SecretId)); + } - string secretName = segments[1]; - string secretVersion = segments[2]; + string secretName = segments[1]; + string secretVersion = segments[2]; - KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion); + KeyVaultSecret secret = secretClient.GetSecret(secretName, secretVersion); - // For PEM, you'll need to extract the base64-encoded message body. - // .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier. - if (Constants.MediaTypePksc12.Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase)) - { - return LoadFromBase64Encoded(secret.Value, x509KeyStorageFlags); - } + // For PEM, you'll need to extract the base64-encoded message body. + // .NET 5.0 preview introduces the System.Security.Cryptography.PemEncoding class to make this easier. + if (Constants.MediaTypePksc12.Equals(secret.Properties.ContentType, StringComparison.InvariantCultureIgnoreCase)) + { + return LoadFromBase64Encoded(secret.Value, x509KeyStorageFlags); + } - throw new NotSupportedException( - string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.OnlyPkcs12IsSupported, - secret.Properties.ContentType)); - } + throw new NotSupportedException( + string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.OnlyPkcs12IsSupported, + secret.Properties.ContentType)); + } - private static X509Certificate2? LoadFromStoreWithThumbprint( - string certificateThumbprint, - string storeDescription = Constants.PersonalUserCertificateStorePath) - { - StoreLocation certificateStoreLocation = StoreLocation.CurrentUser; - StoreName certificateStoreName = StoreName.My; - ParseStoreLocationAndName( - storeDescription, - ref certificateStoreLocation, - ref certificateStoreName); + private static X509Certificate2? LoadFromStoreWithThumbprint( + string certificateThumbprint, + string storeDescription = Constants.PersonalUserCertificateStorePath) + { + StoreLocation certificateStoreLocation = StoreLocation.CurrentUser; + StoreName certificateStoreName = StoreName.My; + ParseStoreLocationAndName( + storeDescription, + ref certificateStoreLocation, + ref certificateStoreName); - X509Certificate2? cert; - using (X509Store x509Store = new X509Store( - certificateStoreName, - certificateStoreLocation)) - { - cert = FindCertificateByCriterium( - x509Store, - X509FindType.FindByThumbprint, - certificateThumbprint); - } + X509Certificate2? cert; + using (X509Store x509Store = new X509Store( + certificateStoreName, + certificateStoreLocation)) + { + cert = FindCertificateByCriterium( + x509Store, + X509FindType.FindByThumbprint, + certificateThumbprint); + } - return cert; - } + return cert; + } - private static X509Certificate2? LoadFromStoreWithDistinguishedName( - string certificateSubjectDistinguishedName, - string storeDescription = Constants.PersonalUserCertificateStorePath) - { - StoreLocation certificateStoreLocation = StoreLocation.CurrentUser; - StoreName certificateStoreName = StoreName.My; - ParseStoreLocationAndName( - storeDescription, - ref certificateStoreLocation, - ref certificateStoreName); + private static X509Certificate2? LoadFromStoreWithDistinguishedName( + string certificateSubjectDistinguishedName, + string storeDescription = Constants.PersonalUserCertificateStorePath) + { + StoreLocation certificateStoreLocation = StoreLocation.CurrentUser; + StoreName certificateStoreName = StoreName.My; + ParseStoreLocationAndName( + storeDescription, + ref certificateStoreLocation, + ref certificateStoreName); - X509Certificate2? cert; - using (X509Store x509Store = new X509Store( - certificateStoreName, - certificateStoreLocation)) - { - cert = FindCertificateByCriterium( - x509Store, - X509FindType.FindBySubjectDistinguishedName, - certificateSubjectDistinguishedName); - } + X509Certificate2? cert; + using (X509Store x509Store = new X509Store( + certificateStoreName, + certificateStoreLocation)) + { + cert = FindCertificateByCriterium( + x509Store, + X509FindType.FindBySubjectDistinguishedName, + certificateSubjectDistinguishedName); + } - return cert; - } + return cert; + } - private static X509Certificate2 LoadFromPath( - string certificateFileName, - string? password = null) - { - return new X509Certificate2( - certificateFileName, - password, - X509KeyStorageFlags.EphemeralKeySet); - } + private static X509Certificate2 LoadFromPath( + string certificateFileName, + string? password = null) + { + return new X509Certificate2( + certificateFileName, + password, + X509KeyStorageFlags.EphemeralKeySet); + } - private static void ParseStoreLocationAndName( - string storeDescription, - ref StoreLocation certificateStoreLocation, - ref StoreName certificateStoreName) - { - string[] path = storeDescription.Split('/'); + private static void ParseStoreLocationAndName( + string storeDescription, + ref StoreLocation certificateStoreLocation, + ref StoreName certificateStoreName) + { + string[] path = storeDescription.Split('/'); - if (path.Length != 2 - || !Enum.TryParse(path[0], true, out certificateStoreLocation) - || !Enum.TryParse(path[1], true, out certificateStoreName)) - { - throw new ArgumentException(string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.InvalidCertificateStorePath, - string.Join("', '", typeof(StoreName).GetEnumNames()))); - } - } + if (path.Length != 2 + || !Enum.TryParse(path[0], true, out certificateStoreLocation) + || !Enum.TryParse(path[1], true, out certificateStoreName)) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.InvalidCertificateStorePath, + string.Join("', '", typeof(StoreName).GetEnumNames()))); + } + } - /// - /// Find a certificate by criteria. - /// - private static X509Certificate2? FindCertificateByCriterium( - X509Store x509Store, - X509FindType identifierCriterium, - string certificateIdentifier) - { - x509Store.Open(OpenFlags.ReadOnly); + /// + /// Find a certificate by criteria. + /// + private static X509Certificate2? FindCertificateByCriterium( + X509Store x509Store, + X509FindType identifierCriterium, + string certificateIdentifier) + { + x509Store.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = x509Store.Certificates; + X509Certificate2Collection certCollection = x509Store.Certificates; - // Find unexpired certificates. - X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); + // Find unexpired certificates. + X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); - // From the collection of unexpired certificates, find the ones with the correct name. - X509Certificate2Collection signingCert = currentCerts.Find(identifierCriterium, certificateIdentifier, false); + // From the collection of unexpired certificates, find the ones with the correct name. + X509Certificate2Collection signingCert = currentCerts.Find(identifierCriterium, certificateIdentifier, false); - // Return the first certificate in the collection, has the right name and is current. - var cert = signingCert.OfType().OrderByDescending(c => c.NotBefore).FirstOrDefault(); - return cert; - } + // Return the first certificate in the collection, has the right name and is current. + var cert = signingCert.OfType().OrderByDescending(c => c.NotBefore).FirstOrDefault(); + return cert; + } - internal /*for test only*/ static X509Certificate2? LoadFirstCertificate(IEnumerable certificateDescription) - { - DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); - CertificateDescription certDescription = certificateDescription.First(); - defaultCertificateLoader.LoadIfNeeded(certDescription); - return certDescription.Certificate; - } + internal /*for test only*/ static X509Certificate2? LoadFirstCertificate(IEnumerable certificateDescription) + { + DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); + CertificateDescription certDescription = certificateDescription.First(); + defaultCertificateLoader.LoadIfNeeded(certDescription); + return certDescription.Certificate; + } - internal /*for test only*/ static IEnumerable LoadAllCertificates(IEnumerable certificateDescriptions) - { - DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); - foreach (var certDescription in certificateDescriptions) - { - defaultCertificateLoader.LoadIfNeeded(certDescription); - yield return certDescription.Certificate; - } - } - } + internal /*for test only*/ static IEnumerable LoadAllCertificates(IEnumerable certificateDescriptions) + { + DefaultCertificateLoader defaultCertificateLoader = new DefaultCertificateLoader(); + foreach (var certDescription in certificateDescriptions) + { + defaultCertificateLoader.LoadIfNeeded(certDescription); + yield return certDescription.Certificate; + } + } + } } diff --git a/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs b/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs index 3394dc8df..5bb4ccc60 100644 --- a/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs +++ b/src/Microsoft.Identity.Web/CertificateManagement/ICertificateLoader.cs @@ -3,27 +3,27 @@ namespace Microsoft.Identity.Web { - /// - /// Interface to implement loading of a certificate. - /// Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. - /// For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. - /// - /// IConfidentialClientApplication app; - /// ICertificateLoader certificateLoader = new DefaultCertificateLoader(); - /// certificateLoader.LoadIfNeeded(config.CertificateDescription); - /// - /// app = ConfidentialClientApplicationBuilder.Create(config.ClientId) - /// .WithCertificate(config.CertificateDescription.Certificate) - /// .WithAuthority(new Uri(config.Authority)) - /// .Build(); - /// - /// - public interface ICertificateLoader - { - /// - /// Load the certificate from the description, if needed. - /// - /// Description of the certificate. - void LoadIfNeeded(CertificateDescription certificateDescription); - } + /// + /// Interface to implement loading of a certificate. + /// Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. + /// For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. + /// + /// IConfidentialClientApplication app; + /// ICertificateLoader certificateLoader = new DefaultCertificateLoader(); + /// certificateLoader.LoadIfNeeded(config.CertificateDescription); + /// + /// app = ConfidentialClientApplicationBuilder.Create(config.ClientId) + /// .WithCertificate(config.CertificateDescription.Certificate) + /// .WithAuthority(new Uri(config.Authority)) + /// .Build(); + /// + /// + public interface ICertificateLoader + { + /// + /// Load the certificate from the description, if needed. + /// + /// Description of the certificate. + void LoadIfNeeded(CertificateDescription certificateDescription); + } } diff --git a/src/Microsoft.Identity.Web/ClaimsPrincipalExtensions.cs b/src/Microsoft.Identity.Web/ClaimsPrincipalExtensions.cs index df530c345..8736349f0 100644 --- a/src/Microsoft.Identity.Web/ClaimsPrincipalExtensions.cs +++ b/src/Microsoft.Identity.Web/ClaimsPrincipalExtensions.cs @@ -6,215 +6,215 @@ namespace Microsoft.Identity.Web { - /// - /// Extensions for . - /// - public static class ClaimsPrincipalExtensions - { - /// - /// Gets the account identifier for an MSAL.NET account from a . - /// - /// Claims principal. - /// A string corresponding to an account identifier as defined in . - public static string? GetMsalAccountId(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - string? uniqueObjectIdentifier = claimsPrincipal?.GetHomeObjectId(); - string? uniqueTenantIdentifier = claimsPrincipal?.GetHomeTenantId(); - - if (!string.IsNullOrWhiteSpace(uniqueObjectIdentifier) && !string.IsNullOrWhiteSpace(uniqueTenantIdentifier)) - { - // AAD pattern: {uid}.{utid} - // B2C pattern: {uid}-{userFlow}.{utid} -> userFlow is included in the uid for B2C - return $"{uniqueObjectIdentifier}.{uniqueTenantIdentifier}"; - } - - return null; - } - - /// - /// Gets the unique object ID associated with the . - /// - /// The from which to retrieve the unique object ID. - /// This method returns the object ID both in case the developer has enabled or not claims mapping. - /// Unique object ID of the identity, or null if it cannot be found. - public static string? GetObjectId(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - string? userObjectId = claimsPrincipal.FindFirstValue(ClaimConstants.Oid); - if (string.IsNullOrEmpty(userObjectId)) - { - userObjectId = claimsPrincipal.FindFirstValue(ClaimConstants.ObjectId); - } - - return userObjectId; - } - - /// - /// Gets the Tenant ID associated with the . - /// - /// The from which to retrieve the tenant ID. - /// Tenant ID of the identity, or null if it cannot be found. - /// This method returns the tenant ID both in case the developer has enabled or not claims mapping. - public static string? GetTenantId(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - string? tenantId = claimsPrincipal.FindFirstValue(ClaimConstants.Tid); - if (string.IsNullOrEmpty(tenantId)) - { - return claimsPrincipal.FindFirstValue(ClaimConstants.TenantId); - } - - return tenantId; - } - - /// - /// Gets the login-hint associated with a . - /// - /// Identity for which to complete the login-hint. - /// The login hint for the identity, or null if it cannot be found. - public static string? GetLoginHint(this ClaimsPrincipal claimsPrincipal) - { - return GetDisplayName(claimsPrincipal); - } - - /// - /// Gets the domain-hint associated with an identity. - /// - /// Identity for which to compute the domain-hint. - /// The domain hint for the identity, or null if it cannot be found. - public static string? GetDomainHint(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - string? tenantId = GetTenantId(claimsPrincipal); - string? domainHint = string.IsNullOrWhiteSpace(tenantId) - ? null - : tenantId!.Equals(Constants.MsaTenantId, StringComparison.OrdinalIgnoreCase) ? Constants.Consumers : Constants.Organizations; - - return domainHint; - } - - /// - /// Get the display name for the signed-in user, from the . - /// - /// Claims about the user/account. - /// A string containing the display name for the user, as determined by Azure AD (v1.0) and Microsoft identity platform (v2.0) tokens, - /// or null if the claims cannot be found. - /// See https://docs.microsoft.com/azure/active-directory/develop/id-tokens#payload-claims. - public static string? GetDisplayName(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - // Use the claims in a Microsoft identity platform token first - string? displayName = claimsPrincipal.FindFirstValue(ClaimConstants.PreferredUserName); - - if (!string.IsNullOrWhiteSpace(displayName)) - { - return displayName; - } - - // Otherwise fall back to the claims in an Azure AD v1.0 token - displayName = claimsPrincipal.FindFirstValue(ClaimsIdentity.DefaultNameClaimType); - - if (!string.IsNullOrWhiteSpace(displayName)) - { - return displayName; - } - - // Finally falling back to name - return claimsPrincipal.FindFirstValue(ClaimConstants.Name); - } - - /// - /// Gets the user flow ID associated with the . - /// - /// The from which to retrieve the user flow ID. - /// User flow ID of the identity, or null if it cannot be found. - public static string? GetUserFlowId(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - string? userFlowId = claimsPrincipal.FindFirstValue(ClaimConstants.Tfp); - if (string.IsNullOrEmpty(userFlowId)) - { - return claimsPrincipal.FindFirstValue(ClaimConstants.UserFlow); - } - - return userFlowId; - } - - /// - /// Gets the Home Object ID associated with the . - /// - /// The from which to retrieve the sub claim. - /// Home Object ID (sub) of the identity, or null if it cannot be found. - public static string? GetHomeObjectId(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - return claimsPrincipal.FindFirstValue(ClaimConstants.UniqueObjectIdentifier); - } - - /// - /// Gets the Home Tenant ID associated with the . - /// - /// The from which to retrieve the sub claim. - /// Home Tenant ID (sub) of the identity, or null if it cannot be found. - public static string? GetHomeTenantId(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - return claimsPrincipal.FindFirstValue(ClaimConstants.UniqueTenantIdentifier); - } - - /// - /// Gets the NameIdentifierId associated with the . - /// - /// The from which to retrieve the NameIdentifierId claim. - /// Name identifier ID of the identity, or null if it cannot be found. - public static string? GetNameIdentifierId(this ClaimsPrincipal claimsPrincipal) - { - if (claimsPrincipal == null) - { - throw new ArgumentNullException(nameof(claimsPrincipal)); - } - - return claimsPrincipal.FindFirstValue(ClaimConstants.NameIdentifierId); - } + /// + /// Extensions for . + /// + public static class ClaimsPrincipalExtensions + { + /// + /// Gets the account identifier for an MSAL.NET account from a . + /// + /// Claims principal. + /// A string corresponding to an account identifier as defined in . + public static string? GetMsalAccountId(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + string? uniqueObjectIdentifier = claimsPrincipal?.GetHomeObjectId(); + string? uniqueTenantIdentifier = claimsPrincipal?.GetHomeTenantId(); + + if (!string.IsNullOrWhiteSpace(uniqueObjectIdentifier) && !string.IsNullOrWhiteSpace(uniqueTenantIdentifier)) + { + // AAD pattern: {uid}.{utid} + // B2C pattern: {uid}-{userFlow}.{utid} -> userFlow is included in the uid for B2C + return $"{uniqueObjectIdentifier}.{uniqueTenantIdentifier}"; + } + + return null; + } + + /// + /// Gets the unique object ID associated with the . + /// + /// The from which to retrieve the unique object ID. + /// This method returns the object ID both in case the developer has enabled or not claims mapping. + /// Unique object ID of the identity, or null if it cannot be found. + public static string? GetObjectId(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + string? userObjectId = claimsPrincipal.FindFirstValue(ClaimConstants.Oid); + if (string.IsNullOrEmpty(userObjectId)) + { + userObjectId = claimsPrincipal.FindFirstValue(ClaimConstants.ObjectId); + } + + return userObjectId; + } + + /// + /// Gets the Tenant ID associated with the . + /// + /// The from which to retrieve the tenant ID. + /// Tenant ID of the identity, or null if it cannot be found. + /// This method returns the tenant ID both in case the developer has enabled or not claims mapping. + public static string? GetTenantId(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + string? tenantId = claimsPrincipal.FindFirstValue(ClaimConstants.Tid); + if (string.IsNullOrEmpty(tenantId)) + { + return claimsPrincipal.FindFirstValue(ClaimConstants.TenantId); + } + + return tenantId; + } + + /// + /// Gets the login-hint associated with a . + /// + /// Identity for which to complete the login-hint. + /// The login hint for the identity, or null if it cannot be found. + public static string? GetLoginHint(this ClaimsPrincipal claimsPrincipal) + { + return GetDisplayName(claimsPrincipal); + } + + /// + /// Gets the domain-hint associated with an identity. + /// + /// Identity for which to compute the domain-hint. + /// The domain hint for the identity, or null if it cannot be found. + public static string? GetDomainHint(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + string? tenantId = GetTenantId(claimsPrincipal); + string? domainHint = string.IsNullOrWhiteSpace(tenantId) + ? null + : tenantId!.Equals(Constants.MsaTenantId, StringComparison.OrdinalIgnoreCase) ? Constants.Consumers : Constants.Organizations; + + return domainHint; + } + + /// + /// Get the display name for the signed-in user, from the . + /// + /// Claims about the user/account. + /// A string containing the display name for the user, as determined by Azure AD (v1.0) and Microsoft identity platform (v2.0) tokens, + /// or null if the claims cannot be found. + /// See https://docs.microsoft.com/azure/active-directory/develop/id-tokens#payload-claims. + public static string? GetDisplayName(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + // Use the claims in a Microsoft identity platform token first + string? displayName = claimsPrincipal.FindFirstValue(ClaimConstants.PreferredUserName); + + if (!string.IsNullOrWhiteSpace(displayName)) + { + return displayName; + } + + // Otherwise fall back to the claims in an Azure AD v1.0 token + displayName = claimsPrincipal.FindFirstValue(ClaimsIdentity.DefaultNameClaimType); + + if (!string.IsNullOrWhiteSpace(displayName)) + { + return displayName; + } + + // Finally falling back to name + return claimsPrincipal.FindFirstValue(ClaimConstants.Name); + } + + /// + /// Gets the user flow ID associated with the . + /// + /// The from which to retrieve the user flow ID. + /// User flow ID of the identity, or null if it cannot be found. + public static string? GetUserFlowId(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + string? userFlowId = claimsPrincipal.FindFirstValue(ClaimConstants.Tfp); + if (string.IsNullOrEmpty(userFlowId)) + { + return claimsPrincipal.FindFirstValue(ClaimConstants.UserFlow); + } + + return userFlowId; + } + + /// + /// Gets the Home Object ID associated with the . + /// + /// The from which to retrieve the sub claim. + /// Home Object ID (sub) of the identity, or null if it cannot be found. + public static string? GetHomeObjectId(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + return claimsPrincipal.FindFirstValue(ClaimConstants.UniqueObjectIdentifier); + } + + /// + /// Gets the Home Tenant ID associated with the . + /// + /// The from which to retrieve the sub claim. + /// Home Tenant ID (sub) of the identity, or null if it cannot be found. + public static string? GetHomeTenantId(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + return claimsPrincipal.FindFirstValue(ClaimConstants.UniqueTenantIdentifier); + } + + /// + /// Gets the NameIdentifierId associated with the . + /// + /// The from which to retrieve the NameIdentifierId claim. + /// Name identifier ID of the identity, or null if it cannot be found. + public static string? GetNameIdentifierId(this ClaimsPrincipal claimsPrincipal) + { + if (claimsPrincipal == null) + { + throw new ArgumentNullException(nameof(claimsPrincipal)); + } + + return claimsPrincipal.FindFirstValue(ClaimConstants.NameIdentifierId); + } #if NET472 - private static string? FindFirstValue(this ClaimsPrincipal claimsPrincipal, string type) - { - return claimsPrincipal.FindFirst(type)?.Value; - } + private static string? FindFirstValue(this ClaimsPrincipal claimsPrincipal, string type) + { + return claimsPrincipal.FindFirst(type)?.Value; + } #endif - } + } } diff --git a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs index 2894d9e50..593fd7e79 100644 --- a/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs +++ b/src/Microsoft.Identity.Web/ClaimsPrincipalFactory.cs @@ -5,42 +5,42 @@ namespace Microsoft.Identity.Web { - /// - /// Factory class to create objects. - /// - public static class ClaimsPrincipalFactory - { - /// - /// 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) - /// { - /// foreach (var notification in notifications) - /// { - /// SubscriptionStore subscription = - /// subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); - /// HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, - /// subscription.UserId); - /// string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); - /// - /// - public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string objectId) - { - return new ClaimsPrincipal( - new ClaimsIdentity(new Claim[] - { - new Claim(ClaimConstants.UniqueTenantIdentifier, tenantId), - new Claim(ClaimConstants.UniqueObjectIdentifier, objectId), - })); - } - } + /// + /// Factory class to create objects. + /// + public static class ClaimsPrincipalFactory + { + /// + /// 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) + /// { + /// foreach (var notification in notifications) + /// { + /// SubscriptionStore subscription = + /// subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); + /// HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, + /// subscription.UserId); + /// string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); + /// + /// + public static ClaimsPrincipal FromTenantIdAndObjectId(string tenantId, string objectId) + { + return new ClaimsPrincipal( + new ClaimsIdentity(new Claim[] + { + new Claim(ClaimConstants.UniqueTenantIdentifier, tenantId), + new Claim(ClaimConstants.UniqueObjectIdentifier, objectId), + })); + } + } } diff --git a/src/Microsoft.Identity.Web/ClientInfo.cs b/src/Microsoft.Identity.Web/ClientInfo.cs index dbfc80795..ff2683334 100644 --- a/src/Microsoft.Identity.Web/ClientInfo.cs +++ b/src/Microsoft.Identity.Web/ClientInfo.cs @@ -7,37 +7,37 @@ namespace Microsoft.Identity.Web { - internal class ClientInfo - { - [JsonPropertyName(ClaimConstants.UniqueObjectIdentifier)] - public string UniqueObjectIdentifier { get; set; } = null!; - - [JsonPropertyName(ClaimConstants.UniqueTenantIdentifier)] - public string UniqueTenantIdentifier { get; set; } = null!; - - public static ClientInfo? CreateFromJson(string clientInfo) - { - if (string.IsNullOrEmpty(clientInfo)) - { - throw new ArgumentNullException(nameof(clientInfo), IDWebErrorMessage.ClientInfoReturnedFromServerIsNull); - } - - return DeserializeFromJson(Base64UrlHelpers.DecodeToBytes(clientInfo)); - } - - internal static ClientInfo? DeserializeFromJson(byte[] jsonByteArray) - { - if (jsonByteArray == null || jsonByteArray.Length == 0) - { - return default; - } - - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - }; - - return JsonSerializer.Deserialize(jsonByteArray, options); - } - } + internal class ClientInfo + { + [JsonPropertyName(ClaimConstants.UniqueObjectIdentifier)] + public string UniqueObjectIdentifier { get; set; } = null!; + + [JsonPropertyName(ClaimConstants.UniqueTenantIdentifier)] + public string UniqueTenantIdentifier { get; set; } = null!; + + public static ClientInfo? CreateFromJson(string clientInfo) + { + if (string.IsNullOrEmpty(clientInfo)) + { + throw new ArgumentNullException(nameof(clientInfo), IDWebErrorMessage.ClientInfoReturnedFromServerIsNull); + } + + return DeserializeFromJson(Base64UrlHelpers.DecodeToBytes(clientInfo)); + } + + internal static ClientInfo? DeserializeFromJson(byte[] jsonByteArray) + { + if (jsonByteArray == null || jsonByteArray.Length == 0) + { + return default; + } + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; + + return JsonSerializer.Deserialize(jsonByteArray, options); + } + } } diff --git a/src/Microsoft.Identity.Web/Constants/ClaimConstants.cs b/src/Microsoft.Identity.Web/Constants/ClaimConstants.cs index 87357b93e..7e3d3ea4d 100644 --- a/src/Microsoft.Identity.Web/Constants/ClaimConstants.cs +++ b/src/Microsoft.Identity.Web/Constants/ClaimConstants.cs @@ -3,101 +3,101 @@ namespace Microsoft.Identity.Web { - /// - /// Constants for claim types. - /// - public static class ClaimConstants - { - /// - /// Name claim: "name". - /// - public const string Name = "name"; - - /// - /// Old Object Id claim: http://schemas.microsoft.com/identity/claims/objectidentifier. - /// - public const string ObjectId = "http://schemas.microsoft.com/identity/claims/objectidentifier"; - - /// - /// New Object id claim: "oid". - /// - public const string Oid = "oid"; - - /// - /// PreferredUserName: "preferred_username". - /// - public const string PreferredUserName = "preferred_username"; - - /// - /// Old TenantId claim: "http://schemas.microsoft.com/identity/claims/tenantid". - /// - public const string TenantId = "http://schemas.microsoft.com/identity/claims/tenantid"; - - /// - /// New Tenant Id claim: "tid". - /// - public const string Tid = "tid"; - - /// - /// ClientInfo claim: "client_info". - /// - public const string ClientInfo = "client_info"; - - /// - /// UniqueObjectIdentifier: "uid". - /// Home Object Id. - /// - public const string UniqueObjectIdentifier = "uid"; - - /// - /// UniqueTenantIdentifier: "utid". - /// Home Tenant Id. - /// - public const string UniqueTenantIdentifier = "utid"; - - /// - /// Older scope claim: "http://schemas.microsoft.com/identity/claims/scope". - /// - public const string Scope = "http://schemas.microsoft.com/identity/claims/scope"; - - /// - /// Newer scope claim: "scp". - /// - public const string Scp = "scp"; - - /// - /// New Roles claim = "roles". - /// - public const string Roles = "roles"; - - /// - /// Old Role claim: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". - /// - public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"; - - /// - /// Subject claim: "sub". - /// - public const string Sub = "sub"; - - /// - /// Acr claim: "acr". - /// - public const string Acr = "acr"; - - /// - /// UserFlow claim: "http://schemas.microsoft.com/claims/authnclassreference". - /// - public const string UserFlow = "http://schemas.microsoft.com/claims/authnclassreference"; - - /// - /// Tfp claim: "tfp". - /// - public const string Tfp = "tfp"; - - /// - /// 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"; - } + /// + /// Constants for claim types. + /// + public static class ClaimConstants + { + /// + /// Name claim: "name". + /// + public const string Name = "name"; + + /// + /// Old Object Id claim: http://schemas.microsoft.com/identity/claims/objectidentifier. + /// + public const string ObjectId = "http://schemas.microsoft.com/identity/claims/objectidentifier"; + + /// + /// New Object id claim: "oid". + /// + public const string Oid = "oid"; + + /// + /// PreferredUserName: "preferred_username". + /// + public const string PreferredUserName = "preferred_username"; + + /// + /// Old TenantId claim: "http://schemas.microsoft.com/identity/claims/tenantid". + /// + public const string TenantId = "http://schemas.microsoft.com/identity/claims/tenantid"; + + /// + /// New Tenant Id claim: "tid". + /// + public const string Tid = "tid"; + + /// + /// ClientInfo claim: "client_info". + /// + public const string ClientInfo = "client_info"; + + /// + /// UniqueObjectIdentifier: "uid". + /// Home Object Id. + /// + public const string UniqueObjectIdentifier = "uid"; + + /// + /// UniqueTenantIdentifier: "utid". + /// Home Tenant Id. + /// + public const string UniqueTenantIdentifier = "utid"; + + /// + /// Older scope claim: "http://schemas.microsoft.com/identity/claims/scope". + /// + public const string Scope = "http://schemas.microsoft.com/identity/claims/scope"; + + /// + /// Newer scope claim: "scp". + /// + public const string Scp = "scp"; + + /// + /// New Roles claim = "roles". + /// + public const string Roles = "roles"; + + /// + /// Old Role claim: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". + /// + public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"; + + /// + /// Subject claim: "sub". + /// + public const string Sub = "sub"; + + /// + /// Acr claim: "acr". + /// + public const string Acr = "acr"; + + /// + /// UserFlow claim: "http://schemas.microsoft.com/claims/authnclassreference". + /// + public const string UserFlow = "http://schemas.microsoft.com/claims/authnclassreference"; + + /// + /// Tfp claim: "tfp". + /// + public const string Tfp = "tfp"; + + /// + /// 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"; + } } diff --git a/src/Microsoft.Identity.Web/Constants/Constants.cs b/src/Microsoft.Identity.Web/Constants/Constants.cs index ad8f54a4e..a248f4de8 100644 --- a/src/Microsoft.Identity.Web/Constants/Constants.cs +++ b/src/Microsoft.Identity.Web/Constants/Constants.cs @@ -3,127 +3,127 @@ namespace Microsoft.Identity.Web { - /// - /// General constants for Microsoft Identity Web. - /// - public static class Constants - { - /// - /// LoginHint. - /// Represents the preferred_username claim in the ID token. - /// - public const string LoginHint = "login_hint"; - - /// - /// DomainHint. - /// Determined by the tenant Id. - /// - public const string DomainHint = "domain_hint"; - - /// - /// Claims. - /// Determined from the signed-in user. - /// - public const string Claims = "claims"; - - /// - /// Bearer. - /// Predominant type of access token used with OAuth 2.0. - /// - public const string Bearer = "Bearer"; - - /// - /// AzureAd. - /// Configuration section name for AzureAd. - /// - public const string AzureAd = "AzureAd"; - - /// - /// AzureAdB2C. - /// Configuration section name for AzureAdB2C. - /// - public const string AzureAdB2C = "AzureAdB2C"; - - /// - /// Scope. - /// - public const string Scope = "scope"; - - /// - /// Policy for B2C user flows. - /// The name of the policy to check against a specific user flow. - /// - public const string Policy = "policy"; - - // IssuerMetadata - internal const string TenantDiscoveryEndpoint = "tenant_discovery_endpoint"; - internal const string ApiVersion = "api-version"; - internal const string Metadata = "metadata"; - - // Metadata - internal const string PreferredNetwork = "preferred_network"; - internal const string PreferredCache = "preferred_cache"; - internal const string Aliases = "aliases"; - - // AadIssuerValidator + /// + /// General constants for Microsoft Identity Web. + /// + public static class Constants + { + /// + /// LoginHint. + /// Represents the preferred_username claim in the ID token. + /// + public const string LoginHint = "login_hint"; + + /// + /// DomainHint. + /// Determined by the tenant Id. + /// + public const string DomainHint = "domain_hint"; + + /// + /// Claims. + /// Determined from the signed-in user. + /// + public const string Claims = "claims"; + + /// + /// Bearer. + /// Predominant type of access token used with OAuth 2.0. + /// + public const string Bearer = "Bearer"; + + /// + /// AzureAd. + /// Configuration section name for AzureAd. + /// + public const string AzureAd = "AzureAd"; + + /// + /// AzureAdB2C. + /// Configuration section name for AzureAdB2C. + /// + public const string AzureAdB2C = "AzureAdB2C"; + + /// + /// Scope. + /// + public const string Scope = "scope"; + + /// + /// Policy for B2C user flows. + /// The name of the policy to check against a specific user flow. + /// + public const string Policy = "policy"; + + // IssuerMetadata + internal const string TenantDiscoveryEndpoint = "tenant_discovery_endpoint"; + internal const string ApiVersion = "api-version"; + internal const string Metadata = "metadata"; + + // Metadata + internal const string PreferredNetwork = "preferred_network"; + internal const string PreferredCache = "preferred_cache"; + internal const string Aliases = "aliases"; + + // AadIssuerValidator #pragma warning disable S1075 // URIs should not be hardcoded - internal const string AzureADIssuerMetadataUrl = "https://login.microsoftonline.com/common/discovery/instance?authorization_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/authorize&api-version=1.1"; + internal const string AzureADIssuerMetadataUrl = "https://login.microsoftonline.com/common/discovery/instance?authorization_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/authorize&api-version=1.1"; #pragma warning restore S1075 // URIs should not be hardcoded - internal const string FallbackAuthority = "https://login.microsoftonline.com/"; - - // RegisterValidAudience - internal const string Version = "ver"; - internal const string V1 = "1.0"; - internal const string V2 = "2.0"; - - // ClaimsPrincipalExtension - internal const string MsaTenantId = "9188040d-6c67-4c5b-b112-36a304b66dad"; - internal const string Consumers = "consumers"; - internal const string Organizations = "organizations"; - internal const string Common = "common"; - - // ClientInfo - internal const string ClientInfo = "client_info"; - internal const string One = "1"; - - // Certificates - internal const string MediaTypePksc12 = "application/x-pkcs12"; - internal const string PersonalUserCertificateStorePath = "CurrentUser/My"; - - // Miscellaneous - internal const string UserAgent = "User-Agent"; - internal const string JwtSecurityTokenUsedToCallWebApi = "JwtSecurityTokenUsedToCallWebAPI"; - internal const string PreferredUserName = "preferred_username"; - internal const string NameClaim = "name"; - internal const string Consent = "consent"; - internal const string ConsentUrl = "consentUri"; - internal const string Scopes = "scopes"; - internal const string ProposedAction = "proposedAction"; - internal const string Authorization = "Authorization"; - internal const string ApplicationJson = "application/json"; - internal const string ISessionStore = "ISessionStore"; - internal const string True = "True"; - - // Blazor challenge URI - internal const string BlazorChallengeUri = "MicrosoftIdentity/Account/Challenge?redirectUri="; - - // Microsoft Graph - internal const string UserReadScope = "user.read"; - internal const string GraphBaseUrlV1 = "https://graph.microsoft.com/v1.0"; - internal const string DefaultGraphScope = "https://graph.microsoft.com/.default"; - - // Telemetry headers - internal const string TelemetryHeaderKey = "x-client-brkrver"; - internal const string IDWebSku = "IDWeb."; - - // Authorize for scopes attributes - internal const string XReturnUrl = "x-ReturnUrl"; - internal const string XRequestedWith = "X-Requested-With"; - internal const string XmlHttpRequest = "XMLHttpRequest"; - internal const string RequiredScopesSetting = "@setting"; - - // AccountController.Challenge parameters - internal const string LoginHintParameter = "loginHint"; - internal const string DomainHintParameter = "domainHint"; - } + internal const string FallbackAuthority = "https://login.microsoftonline.com/"; + + // RegisterValidAudience + internal const string Version = "ver"; + internal const string V1 = "1.0"; + internal const string V2 = "2.0"; + + // ClaimsPrincipalExtension + internal const string MsaTenantId = "9188040d-6c67-4c5b-b112-36a304b66dad"; + internal const string Consumers = "consumers"; + internal const string Organizations = "organizations"; + internal const string Common = "common"; + + // ClientInfo + internal const string ClientInfo = "client_info"; + internal const string One = "1"; + + // Certificates + internal const string MediaTypePksc12 = "application/x-pkcs12"; + internal const string PersonalUserCertificateStorePath = "CurrentUser/My"; + + // Miscellaneous + internal const string UserAgent = "User-Agent"; + internal const string JwtSecurityTokenUsedToCallWebApi = "JwtSecurityTokenUsedToCallWebAPI"; + internal const string PreferredUserName = "preferred_username"; + internal const string NameClaim = "name"; + internal const string Consent = "consent"; + internal const string ConsentUrl = "consentUri"; + internal const string Scopes = "scopes"; + internal const string ProposedAction = "proposedAction"; + internal const string Authorization = "Authorization"; + internal const string ApplicationJson = "application/json"; + internal const string ISessionStore = "ISessionStore"; + internal const string True = "True"; + + // Blazor challenge URI + internal const string BlazorChallengeUri = "MicrosoftIdentity/Account/Challenge?redirectUri="; + + // Microsoft Graph + internal const string UserReadScope = "user.read"; + internal const string GraphBaseUrlV1 = "https://graph.microsoft.com/v1.0"; + internal const string DefaultGraphScope = "https://graph.microsoft.com/.default"; + + // Telemetry headers + internal const string TelemetryHeaderKey = "x-client-brkrver"; + internal const string IDWebSku = "IDWeb."; + + // Authorize for scopes attributes + internal const string XReturnUrl = "x-ReturnUrl"; + internal const string XRequestedWith = "X-Requested-With"; + internal const string XmlHttpRequest = "XMLHttpRequest"; + internal const string RequiredScopesSetting = "@setting"; + + // AccountController.Challenge parameters + internal const string LoginHintParameter = "loginHint"; + internal const string DomainHintParameter = "domainHint"; + } } diff --git a/src/Microsoft.Identity.Web/Constants/ErrorCodes.cs b/src/Microsoft.Identity.Web/Constants/ErrorCodes.cs index 2d0b6f178..188c2560a 100644 --- a/src/Microsoft.Identity.Web/Constants/ErrorCodes.cs +++ b/src/Microsoft.Identity.Web/Constants/ErrorCodes.cs @@ -3,14 +3,14 @@ namespace Microsoft.Identity.Web { - internal static class ErrorCodes - { - public const string MissingClientCredentials = "missing_client_credentials"; - public const string DuplicateClientCredentials = "duplicate_client_credentials"; + internal static class ErrorCodes + { + public const string MissingClientCredentials = "missing_client_credentials"; + public const string DuplicateClientCredentials = "duplicate_client_credentials"; - // AzureAD B2C - public const string B2CPasswordResetErrorCode = "AADSTS50013"; - public const string B2CForgottenPassword = "AADB2C90118"; - public const string AccessDenied = "access_denied"; - } + // AzureAD B2C + public const string B2CPasswordResetErrorCode = "AADSTS50013"; + public const string B2CForgottenPassword = "AADB2C90118"; + public const string AccessDenied = "access_denied"; + } } diff --git a/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs b/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs index 6aadff408..8dcfa4d49 100644 --- a/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs +++ b/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs @@ -3,70 +3,70 @@ namespace Microsoft.Identity.Web { - /// - /// Constants related to the error messages. - /// - internal static class IDWebErrorMessage - { - // General IDW10000 = "IDW10000:" - public const string HttpContextIsNull = "IDW10001: HttpContext is null. "; - public const string HttpContextAndHttpResponseAreNull = "IDW10002: Current HttpContext and HttpResponse arguments are null. Pass an HttpResponse argument. "; + /// + /// Constants related to the error messages. + /// + internal static class IDWebErrorMessage + { + // General IDW10000 = "IDW10000:" + public const string HttpContextIsNull = "IDW10001: HttpContext is null. "; + public const string HttpContextAndHttpResponseAreNull = "IDW10002: Current HttpContext and HttpResponse arguments are null. Pass an HttpResponse argument. "; - // Configuration IDW10100 = "IDW10100:" - public const string ProvideEitherScopeKeySectionOrScopes = "IDW10101: Either provide the '{0}' or the '{1}' to the 'AuthorizeForScopes'. "; - public const string ScopeKeySectionIsProvidedButNotPresentInTheServicesCollection = "IDW10102: The {0} is provided but the IConfiguration instance is not present in the services collection. "; - public const string NoScopesProvided = "IDW10103: No scopes provided in scopes... "; - public const string ClientSecretAndCertficateNull = - "IDW10104: Both client secret and client certificate cannot be null or whitespace, " + - "and only ONE must be included in the configuration of the web app when calling a web API. " + - "For instance, in the appsettings.json file. "; - public const string BothClientSecretAndCertificateProvided = "IDW10105: Both client secret and client certificate, " + - "cannot be included in the configuration of the web app when calling a web API. "; - public const string ConfigurationOptionRequired = "IDW10106: The '{0}' option must be provided. "; - public const string ScopesNotConfiguredInConfigurationOrViaDelegate = "IDW10107: Scopes need to be passed-in either by configuration or by the delegate overriding it. "; - public const string MissingRequiredScopesForAuthorizationFilter = "IDW10108: RequiredScope Attribute does not contain a value. The scopes need to be set on the controller, the page or action. See https://aka.ms/ms-id-web/required-scope-attribute. "; + // Configuration IDW10100 = "IDW10100:" + public const string ProvideEitherScopeKeySectionOrScopes = "IDW10101: Either provide the '{0}' or the '{1}' to the 'AuthorizeForScopes'. "; + public const string ScopeKeySectionIsProvidedButNotPresentInTheServicesCollection = "IDW10102: The {0} is provided but the IConfiguration instance is not present in the services collection. "; + public const string NoScopesProvided = "IDW10103: No scopes provided in scopes... "; + public const string ClientSecretAndCertficateNull = + "IDW10104: Both client secret and client certificate cannot be null or whitespace, " + + "and only ONE must be included in the configuration of the web app when calling a web API. " + + "For instance, in the appsettings.json file. "; + public const string BothClientSecretAndCertificateProvided = "IDW10105: Both client secret and client certificate, " + + "cannot be included in the configuration of the web app when calling a web API. "; + public const string ConfigurationOptionRequired = "IDW10106: The '{0}' option must be provided. "; + public const string ScopesNotConfiguredInConfigurationOrViaDelegate = "IDW10107: Scopes need to be passed-in either by configuration or by the delegate overriding it. "; + public const string MissingRequiredScopesForAuthorizationFilter = "IDW10108: RequiredScope Attribute does not contain a value. The scopes need to be set on the controller, the page or action. See https://aka.ms/ms-id-web/required-scope-attribute. "; - // Authorization IDW10200 = "IDW10200:" - public const string NeitherScopeOrRolesClaimFoundInToken = "IDW10201: Neither scope or roles claim was found in the bearer token. "; - public const string MissingRoles = "IDW10202: The 'roles' or 'role' claim does not contain roles '{0}' or was not found. "; - public const string MissingScopes = "IDW10203: The 'scope' or 'scp' claim does not contain scopes '{0}' or was not found. "; - public const string UnauthenticatedUser = "IDW10204: The user is unauthenticated. The HttpContext does not contain any claims. "; - public const string BlazorServerBaseUriNotSet = "IDW10205: Using Blazor server but the base URI was not properly set. "; - public const string BlazorServerUserNotSet = "IDW10206: Using Blazor server but the user was not properly set. "; - public const string CalledApiScopesAreNull = "IDW10207: The CalledApiScopes cannot be null. "; - public const string ScopesRequiredToCallMicrosoftGraph = "IDW10208: You need to either pass-in scopes to AddMicrosoftGraph, in the appsettings.json file, or with .WithScopes() on the Graph queries. See https://aka.ms/ms-id-web/microsoftGraph. "; + // Authorization IDW10200 = "IDW10200:" + public const string NeitherScopeOrRolesClaimFoundInToken = "IDW10201: Neither scope or roles claim was found in the bearer token. "; + public const string MissingRoles = "IDW10202: The 'roles' or 'role' claim does not contain roles '{0}' or was not found. "; + public const string MissingScopes = "IDW10203: The 'scope' or 'scp' claim does not contain scopes '{0}' or was not found. "; + public const string UnauthenticatedUser = "IDW10204: The user is unauthenticated. The HttpContext does not contain any claims. "; + public const string BlazorServerBaseUriNotSet = "IDW10205: Using Blazor server but the base URI was not properly set. "; + public const string BlazorServerUserNotSet = "IDW10206: Using Blazor server but the user was not properly set. "; + public const string CalledApiScopesAreNull = "IDW10207: The CalledApiScopes cannot be null. "; + public const string ScopesRequiredToCallMicrosoftGraph = "IDW10208: You need to either pass-in scopes to AddMicrosoftGraph, in the appsettings.json file, or with .WithScopes() on the Graph queries. See https://aka.ms/ms-id-web/microsoftGraph. "; - // Token Validation IDW10300 = "IDW10300:" - public const string IssuerMetadataUrlIsRequired = "IDW10301: Azure AD Issuer metadata address URL is required. "; - public const string NoMetadataDocumentRetrieverProvided = "IDW10302: No metadata document retriever is provided. "; - public const string IssuerDoesNotMatchValidIssuers = "IDW10303: Issuer: '{0}', does not match any of the valid issuers provided for this application. "; - public const string B2CTfpIssuerNotSupported = "IDW10304: Microsoft Identity Web does not support a B2C issuer with 'tfp' in the URI. See https://aka.ms/ms-id-web/b2c-issuer for details. "; + // Token Validation IDW10300 = "IDW10300:" + public const string IssuerMetadataUrlIsRequired = "IDW10301: Azure AD Issuer metadata address URL is required. "; + public const string NoMetadataDocumentRetrieverProvided = "IDW10302: No metadata document retriever is provided. "; + public const string IssuerDoesNotMatchValidIssuers = "IDW10303: Issuer: '{0}', does not match any of the valid issuers provided for this application. "; + public const string B2CTfpIssuerNotSupported = "IDW10304: Microsoft Identity Web does not support a B2C issuer with 'tfp' in the URI. See https://aka.ms/ms-id-web/b2c-issuer for details. "; - // Protocol IDW10400 = "IDW10400:" - public const string TenantIdClaimNotPresentInToken = "IDW10401: Neither `tid` nor `tenantId` claim is present in the token obtained from Microsoft identity platform. "; - public const string ClientInfoReturnedFromServerIsNull = "IDW10402: Client info returned from the server is null. "; - public const string TokenIsNotJwtToken = "IDW10403: Token is not a JWT token. "; - public const string ClientCredentialScopeParameterShouldEndInDotDefault = - "IDW10404: 'scope' parameter should be of the form 'AppIdUri/.default'. See https://aka.ms/ms-id-web/daemon-scenarios. "; - public const string ClientCredentialTenantShouldBeTenanted = - "IDW10405: 'tenant' parameter should be a tenant ID or domain name, not 'common', or 'organizations'. See https://aka.ms/ms-id-web/daemon-scenarios. "; + // Protocol IDW10400 = "IDW10400:" + public const string TenantIdClaimNotPresentInToken = "IDW10401: Neither `tid` nor `tenantId` claim is present in the token obtained from Microsoft identity platform. "; + public const string ClientInfoReturnedFromServerIsNull = "IDW10402: Client info returned from the server is null. "; + public const string TokenIsNotJwtToken = "IDW10403: Token is not a JWT token. "; + public const string ClientCredentialScopeParameterShouldEndInDotDefault = + "IDW10404: 'scope' parameter should be of the form 'AppIdUri/.default'. See https://aka.ms/ms-id-web/daemon-scenarios. "; + public const string ClientCredentialTenantShouldBeTenanted = + "IDW10405: 'tenant' parameter should be a tenant ID or domain name, not 'common', or 'organizations'. See https://aka.ms/ms-id-web/daemon-scenarios. "; - // MSAL IDW10500 = "IDW10500:" - public const string ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client. "; - public const string MicrosoftIdentityWebChallengeUserException = "IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. " + - "See https://aka.ms/ms-id-web/ca_incremental-consent. "; + // MSAL IDW10500 = "IDW10500:" + public const string ExceptionAcquiringTokenForConfidentialClient = "IDW10501: Exception acquiring token for a confidential client. "; + public const string MicrosoftIdentityWebChallengeUserException = "IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. " + + "See https://aka.ms/ms-id-web/ca_incremental-consent. "; - // Encoding IDW10600 = "IDW10600:" - public const string InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. "; + // Encoding IDW10600 = "IDW10600:" + public const string InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. "; - // Certificates IDW10700 = "IDW10700:" - public const string OnlyPkcs12IsSupported = "IDW10701: Only PKCS #12 content type is supported. Found Content-Type: {0}. "; - public const string IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. "; - public const string InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. " + - "StoreLocation must be one of 'CurrentUser', 'CurrentMachine'. " + - "StoreName must be empty or one of '{0}'. "; + // Certificates IDW10700 = "IDW10700:" + public const string OnlyPkcs12IsSupported = "IDW10701: Only PKCS #12 content type is supported. Found Content-Type: {0}. "; + public const string IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. "; + public const string InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. " + + "StoreLocation must be one of 'CurrentUser', 'CurrentMachine'. " + + "StoreName must be empty or one of '{0}'. "; - // Obsolete messages IDW10800 = "IDW10800:" - public const string AadIssuerValidatorGetIssuerValidatorIsObsolete = "IDW10800: Use MicrosoftIdentityIssuerValidatorFactory.GetAadIssuerValidator. See https://aka.ms/ms-id-web/1.2.0"; - } + // Obsolete messages IDW10800 = "IDW10800:" + public const string AadIssuerValidatorGetIssuerValidatorIsObsolete = "IDW10800: Use MicrosoftIdentityIssuerValidatorFactory.GetAadIssuerValidator. See https://aka.ms/ms-id-web/1.2.0"; + } } diff --git a/src/Microsoft.Identity.Web/Constants/LogMessages.cs b/src/Microsoft.Identity.Web/Constants/LogMessages.cs index 0c3406835..86524016d 100644 --- a/src/Microsoft.Identity.Web/Constants/LogMessages.cs +++ b/src/Microsoft.Identity.Web/Constants/LogMessages.cs @@ -3,25 +3,25 @@ namespace Microsoft.Identity.Web { - /// - /// Constants related to the log messages. - /// - internal static class LogMessages - { - public const string MissingRoles = "The 'roles' or 'role' claim does not contain roles '{0}' or was not found"; - public const string MissingScopes = "The 'scope' or 'scp' claim does not contain scopes '{0}' or was not found"; - public const string ExceptionOccurredWhenAddingAnAccountToTheCacheFromAuthCode = "Exception occurred while adding an account to the cache from the auth code. "; + /// + /// Constants related to the log messages. + /// + internal static class LogMessages + { + public const string MissingRoles = "The 'roles' or 'role' claim does not contain roles '{0}' or was not found"; + public const string MissingScopes = "The 'scope' or 'scp' claim does not contain scopes '{0}' or was not found"; + public const string ExceptionOccurredWhenAddingAnAccountToTheCacheFromAuthCode = "Exception occurred while adding an account to the cache from the auth code. "; - // Diagnostics - public const string MethodBegin = "Begin {0}. "; - public const string MethodEnd = "End {0}. "; + // Diagnostics + public const string MethodBegin = "Begin {0}. "; + public const string MethodEnd = "End {0}. "; - // Caching - public const string DeserializingSessionCache = "Deserializing session {0}, cache key {1}. "; - public const string SessionCacheKeyNotFound = "Cache key {0} not found in session {1}. "; - public const string SerializingSessionCache = "Serializing session {0}, cache key {1}. "; - public const string ClearingSessionCache = "Clearing session {0}, cache key {1}. "; + // Caching + public const string DeserializingSessionCache = "Deserializing session {0}, cache key {1}. "; + public const string SessionCacheKeyNotFound = "Cache key {0} not found in session {1}. "; + public const string SerializingSessionCache = "Serializing session {0}, cache key {1}. "; + public const string ClearingSessionCache = "Clearing session {0}, cache key {1}. "; - public const string ErrorAcquiringTokenForDownstreamWebApi = "Error acquiring a token for a downstream web API - MsalUiRequiredException message is: {0}. "; - } + public const string ErrorAcquiringTokenForDownstreamWebApi = "Error acquiring a token for a downstream web API - MsalUiRequiredException message is: {0}. "; + } } diff --git a/src/Microsoft.Identity.Web/Constants/OidcConstants.cs b/src/Microsoft.Identity.Web/Constants/OidcConstants.cs index 4446ef381..77ff9f9e8 100644 --- a/src/Microsoft.Identity.Web/Constants/OidcConstants.cs +++ b/src/Microsoft.Identity.Web/Constants/OidcConstants.cs @@ -3,12 +3,12 @@ namespace Microsoft.Identity.Web { - internal static class OidcConstants - { - public const string AdditionalClaims = "claims"; - public const string ScopeOfflineAccess = "offline_access"; - public const string ScopeProfile = "profile"; - public const string ScopeOpenId = "openid"; - public const string PolicyKey = "policy"; - } + internal static class OidcConstants + { + public const string AdditionalClaims = "claims"; + public const string ScopeOfflineAccess = "offline_access"; + public const string ScopeProfile = "profile"; + public const string ScopeOpenId = "openid"; + public const string PolicyKey = "policy"; + } } \ No newline at end of file diff --git a/src/Microsoft.Identity.Web/CookiePolicyOptionsExtensions.cs b/src/Microsoft.Identity.Web/CookiePolicyOptionsExtensions.cs index 6342c722f..6f4a7a59f 100644 --- a/src/Microsoft.Identity.Web/CookiePolicyOptionsExtensions.cs +++ b/src/Microsoft.Identity.Web/CookiePolicyOptionsExtensions.cs @@ -9,175 +9,175 @@ namespace Microsoft.Identity.Web { - /// - /// Extension class containing cookie policies (work around for same site). - /// - public static class CookiePolicyOptionsExtensions - { - /// - /// Handles SameSite cookie issue according to the https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1. - /// The default list of user agents that disallow "SameSite=None", - /// was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. - /// - /// to update. - /// to chain. - public static CookiePolicyOptions HandleSameSiteCookieCompatibility(this CookiePolicyOptions options) - { - return HandleSameSiteCookieCompatibility(options, DisallowsSameSiteNone); - } - - /// - /// Handles SameSite cookie issue according to the docs: https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1 - /// The default list of user agents that disallow "SameSite=None", was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. - /// - /// to update. - /// If you don't want to use the default user agent list implementation, - /// the method sent in this parameter will be run against the user agent and if returned true, SameSite value will be set to Unspecified. - /// The default user agent list used can be found at: https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. - /// to chain. - public static CookiePolicyOptions HandleSameSiteCookieCompatibility(this CookiePolicyOptions options, Func disallowsSameSiteNone) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - options.MinimumSameSitePolicy = SameSiteMode.Unspecified; - options.OnAppendCookie = cookieContext => - CheckSameSite(cookieContext.Context, cookieContext.CookieOptions, disallowsSameSiteNone); - options.OnDeleteCookie = cookieContext => - CheckSameSite(cookieContext.Context, cookieContext.CookieOptions, disallowsSameSiteNone); - - return options; - } - - private static void CheckSameSite(HttpContext httpContext, CookieOptions options, Func disallowsSameSiteNone) - { - if (options.SameSite == SameSiteMode.None) - { - var userAgent = httpContext.Request.Headers[Constants.UserAgent].ToString(); - if (disallowsSameSiteNone(userAgent)) - { - options.SameSite = SameSiteMode.Unspecified; - } - } - } - - /// - /// Checks if the specified user agent supports "SameSite=None" cookies. - /// - /// Browser user agent. - /// - /// Incompatible user agents include: - /// - /// Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends). - /// Versions of UC Browser on Android prior to version 12.13.2. - /// Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12. - /// - /// Reference: https://www.chromium.org/updates/same-site/incompatible-clients. - /// - /// True, if the user agent does not allow "SameSite=None" cookie; otherwise, false. - public static bool DisallowsSameSiteNone(string userAgent) - { - return HasWebKitSameSiteBug() || - DropsUnrecognizedSameSiteCookies(); - - bool HasWebKitSameSiteBug() => - IsIosVersion(12) || - (IsMacosxVersion(10, 14) && - (IsSafari() || IsMacEmbeddedBrowser())); - - bool DropsUnrecognizedSameSiteCookies() - { - if (IsUcBrowser()) - { - return !IsUcBrowserVersionAtLeast(12, 13, 2); - } - - return IsChromiumBased() && - IsChromiumVersionAtLeast(51) && - !IsChromiumVersionAtLeast(67); - } - - bool IsIosVersion(int major) - { - string regex = @"\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\/"; - - // Extract digits from first capturing group. - Match match = Regex.Match(userAgent, regex); - return match.Groups[1].Value == major.ToString(CultureInfo.CurrentCulture); - } - - bool IsMacosxVersion(int major, int minor) - { - string regex = @"\(Macintosh;.*Mac OS X (\d+)_(\d+)[_\d]*.*\) AppleWebKit\/"; - - // Extract digits from first and second capturing groups. - Match match = Regex.Match(userAgent, regex); - return match.Groups[1].Value == major.ToString(CultureInfo.CurrentCulture) && - match.Groups[2].Value == minor.ToString(CultureInfo.CurrentCulture); - } - - bool IsSafari() - { - string regex = @"Version\/.* Safari\/"; - - return Regex.IsMatch(userAgent, regex) && - !IsChromiumBased(); - } - - bool IsMacEmbeddedBrowser() - { - string regex = @"^Mozilla\/[\.\d]+ \(Macintosh;.*Mac OS X [_\d]+\) AppleWebKit\/[\.\d]+ \(KHTML, like Gecko\)$"; - - return Regex.IsMatch(userAgent, regex); - } - - bool IsChromiumBased() - { - string regex = "Chrom(e|ium)"; - - return Regex.IsMatch(userAgent, regex); - } - - bool IsChromiumVersionAtLeast(int major) - { - string regex = @"Chrom[^ \/]+\/(\d+)[\.\d]* "; - - // Extract digits from first capturing group. - Match match = Regex.Match(userAgent, regex); - int version = Convert.ToInt32(match.Groups[1].Value, CultureInfo.CurrentCulture); - return version >= major; - } - - bool IsUcBrowser() - { - string regex = @"UCBrowser\/"; - - return Regex.IsMatch(userAgent, regex); - } - - bool IsUcBrowserVersionAtLeast(int major, int minor, int build) - { - string regex = @"UCBrowser\/(\d+)\.(\d+)\.(\d+)[\.\d]* "; - - // Extract digits from three capturing groups. - Match match = Regex.Match(userAgent, regex); - int major_version = Convert.ToInt32(match.Groups[1].Value, CultureInfo.CurrentCulture); - int minor_version = Convert.ToInt32(match.Groups[2].Value, CultureInfo.CurrentCulture); - int build_version = Convert.ToInt32(match.Groups[3].Value, CultureInfo.CurrentCulture); - if (major_version != major) - { - return major_version > major; - } - - if (minor_version != minor) - { - return minor_version > minor; - } - - return build_version >= build; - } - } - } + /// + /// Extension class containing cookie policies (work around for same site). + /// + public static class CookiePolicyOptionsExtensions + { + /// + /// Handles SameSite cookie issue according to the https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1. + /// The default list of user agents that disallow "SameSite=None", + /// was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + /// + /// to update. + /// to chain. + public static CookiePolicyOptions HandleSameSiteCookieCompatibility(this CookiePolicyOptions options) + { + return HandleSameSiteCookieCompatibility(options, DisallowsSameSiteNone); + } + + /// + /// Handles SameSite cookie issue according to the docs: https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1 + /// The default list of user agents that disallow "SameSite=None", was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + /// + /// to update. + /// If you don't want to use the default user agent list implementation, + /// the method sent in this parameter will be run against the user agent and if returned true, SameSite value will be set to Unspecified. + /// The default user agent list used can be found at: https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + /// to chain. + public static CookiePolicyOptions HandleSameSiteCookieCompatibility(this CookiePolicyOptions options, Func disallowsSameSiteNone) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.MinimumSameSitePolicy = SameSiteMode.Unspecified; + options.OnAppendCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions, disallowsSameSiteNone); + options.OnDeleteCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions, disallowsSameSiteNone); + + return options; + } + + private static void CheckSameSite(HttpContext httpContext, CookieOptions options, Func disallowsSameSiteNone) + { + if (options.SameSite == SameSiteMode.None) + { + var userAgent = httpContext.Request.Headers[Constants.UserAgent].ToString(); + if (disallowsSameSiteNone(userAgent)) + { + options.SameSite = SameSiteMode.Unspecified; + } + } + } + + /// + /// Checks if the specified user agent supports "SameSite=None" cookies. + /// + /// Browser user agent. + /// + /// Incompatible user agents include: + /// + /// Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends). + /// Versions of UC Browser on Android prior to version 12.13.2. + /// Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12. + /// + /// Reference: https://www.chromium.org/updates/same-site/incompatible-clients. + /// + /// True, if the user agent does not allow "SameSite=None" cookie; otherwise, false. + public static bool DisallowsSameSiteNone(string userAgent) + { + return HasWebKitSameSiteBug() || + DropsUnrecognizedSameSiteCookies(); + + bool HasWebKitSameSiteBug() => + IsIosVersion(12) || + (IsMacosxVersion(10, 14) && + (IsSafari() || IsMacEmbeddedBrowser())); + + bool DropsUnrecognizedSameSiteCookies() + { + if (IsUcBrowser()) + { + return !IsUcBrowserVersionAtLeast(12, 13, 2); + } + + return IsChromiumBased() && + IsChromiumVersionAtLeast(51) && + !IsChromiumVersionAtLeast(67); + } + + bool IsIosVersion(int major) + { + string regex = @"\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\/"; + + // Extract digits from first capturing group. + Match match = Regex.Match(userAgent, regex); + return match.Groups[1].Value == major.ToString(CultureInfo.CurrentCulture); + } + + bool IsMacosxVersion(int major, int minor) + { + string regex = @"\(Macintosh;.*Mac OS X (\d+)_(\d+)[_\d]*.*\) AppleWebKit\/"; + + // Extract digits from first and second capturing groups. + Match match = Regex.Match(userAgent, regex); + return match.Groups[1].Value == major.ToString(CultureInfo.CurrentCulture) && + match.Groups[2].Value == minor.ToString(CultureInfo.CurrentCulture); + } + + bool IsSafari() + { + string regex = @"Version\/.* Safari\/"; + + return Regex.IsMatch(userAgent, regex) && + !IsChromiumBased(); + } + + bool IsMacEmbeddedBrowser() + { + string regex = @"^Mozilla\/[\.\d]+ \(Macintosh;.*Mac OS X [_\d]+\) AppleWebKit\/[\.\d]+ \(KHTML, like Gecko\)$"; + + return Regex.IsMatch(userAgent, regex); + } + + bool IsChromiumBased() + { + string regex = "Chrom(e|ium)"; + + return Regex.IsMatch(userAgent, regex); + } + + bool IsChromiumVersionAtLeast(int major) + { + string regex = @"Chrom[^ \/]+\/(\d+)[\.\d]* "; + + // Extract digits from first capturing group. + Match match = Regex.Match(userAgent, regex); + int version = Convert.ToInt32(match.Groups[1].Value, CultureInfo.CurrentCulture); + return version >= major; + } + + bool IsUcBrowser() + { + string regex = @"UCBrowser\/"; + + return Regex.IsMatch(userAgent, regex); + } + + bool IsUcBrowserVersionAtLeast(int major, int minor, int build) + { + string regex = @"UCBrowser\/(\d+)\.(\d+)\.(\d+)[\.\d]* "; + + // Extract digits from three capturing groups. + Match match = Regex.Match(userAgent, regex); + int major_version = Convert.ToInt32(match.Groups[1].Value, CultureInfo.CurrentCulture); + int minor_version = Convert.ToInt32(match.Groups[2].Value, CultureInfo.CurrentCulture); + int build_version = Convert.ToInt32(match.Groups[3].Value, CultureInfo.CurrentCulture); + if (major_version != major) + { + return major_version > major; + } + + if (minor_version != minor) + { + return minor_version > minor; + } + + return build_version >= build; + } + } + } } diff --git a/src/Microsoft.Identity.Web/Diagrams.cd b/src/Microsoft.Identity.Web/Diagrams.cd index 8be324cfa..6a280e67b 100644 --- a/src/Microsoft.Identity.Web/Diagrams.cd +++ b/src/Microsoft.Identity.Web/Diagrams.cd @@ -1,134 +1,134 @@  - - - - - EAAAgEQAAFCAAAAAAACAAAAAAAAAQAAAAAACAAAAAgA= - Resource\AadIssuerValidator.cs - + + + + + EAAAgEQAAFCAAAAAAACAAAAAAAAAQAAAAAACAAAAAgA= + Resource\AadIssuerValidator.cs + - - - - - AAAACAAAAgAAAwgAAAAAAEABMgACAgAAAMDAIIhKAAA= - Resource\OpenIdConnectMiddlewareDiagnostics.cs - - + + + + + AAAACAAAAgAAAwgAAAAAAEABMgACAgAAAMDAIIhKAAA= + Resource\OpenIdConnectMiddlewareDiagnostics.cs + + - - - AAAAAkABAAAAAAABAAAAAAAAAAAAAAAAAAIAAEACAEA= - ClaimsPrincipalExtensions.cs - + + + AAAAAkABAAAAAAABAAAAAAAAAAAAAAAAAAIAAEACAEA= + ClaimsPrincipalExtensions.cs + - - - AAAAAAAAAAAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAAA= - WebAppServiceCollectionExtensions.cs - + + + AAAAAAAAAAAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAAA= + WebAppServiceCollectionExtensions.cs + - - - AAAAAAAAACAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA= - WebApiServiceCollectionExtensions.cs - + + + AAAAAAAAACAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA= + WebApiServiceCollectionExtensions.cs + - - - - - AAAAAAAAAAAAACAAAAAAIAAABAAAAAAAAAAAAgAAAIA= - AuthorizeForScopesAttribute.cs - + + + + + AAAAAAAAAAAAACAAAAAAIAAABAAAAAAAAAAAAgAAAIA= + AuthorizeForScopesAttribute.cs + - - - AAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAA= - AccountExtensions.cs - + + + AAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAA= + AccountExtensions.cs + - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAA= - ClaimsPrincipalFactory.cs - + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAA= + ClaimsPrincipalFactory.cs + - - - AAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - Resource\ScopesRequiredHttpContextExtensions.cs - + + + AAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + Resource\ScopesRequiredHttpContextExtensions.cs + - - - - - AAAAAAAAAgABAAgAAAAAAAABIgACAAABAAAAIABAAAA= - Resource\JwtBearerMiddlewareDiagnostics.cs - - + + + + + AAAAAAAAAgABAAgAAAAAAAABIgACAAABAAAAIABAAAA= + Resource\JwtBearerMiddlewareDiagnostics.cs + + - - - AAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAA= - WebAppAuthenticationBuilderExtensions.cs - + + + AAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAA= + WebAppAuthenticationBuilderExtensions.cs + - - - - - AAAAAAAAAAAACAAAQQAAAAAAAAAAAAAAAAAAAAAAAAA= - WebApiAuthenticationBuilderExtensions.cs - + + + + + AAAAAAAAAAAACAAAQQAAAAAAAAAAAAAAAAAAAAAAAAA= + WebApiAuthenticationBuilderExtensions.cs + - - - AAAAAAIAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - ITokenAcquisition.cs - + + + AAAAAAIAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + ITokenAcquisition.cs + \ No newline at end of file diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs index a621466b1..9385f3716 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs @@ -13,208 +13,208 @@ namespace Microsoft.Identity.Web { - /// - /// Implementation for the downstream web API. - /// - public class DownstreamWebApi : IDownstreamWebApi - { - private readonly ITokenAcquisition _tokenAcquisition; - private readonly HttpClient _httpClient; - private readonly IOptionsMonitor _namedDownstreamWebApiOptions; - private readonly MicrosoftIdentityOptions _microsoftIdentityOptions; - - /// - /// Constructor. - /// - /// Token acquisition service. - /// Named options provider. - /// HTTP client. - /// Configuration options. - public DownstreamWebApi( - ITokenAcquisition tokenAcquisition, - IOptionsMonitor namedDownstreamWebApiOptions, - HttpClient httpClient, - IOptions microsoftIdentityOptions) - { - _tokenAcquisition = tokenAcquisition; - _namedDownstreamWebApiOptions = namedDownstreamWebApiOptions; - _httpClient = httpClient; + /// + /// Implementation for the downstream web API. + /// + public class DownstreamWebApi : IDownstreamWebApi + { + private readonly ITokenAcquisition _tokenAcquisition; + private readonly HttpClient _httpClient; + private readonly IOptionsMonitor _namedDownstreamWebApiOptions; + private readonly MicrosoftIdentityOptions _microsoftIdentityOptions; + + /// + /// Constructor. + /// + /// Token acquisition service. + /// Named options provider. + /// HTTP client. + /// Configuration options. + public DownstreamWebApi( + ITokenAcquisition tokenAcquisition, + IOptionsMonitor namedDownstreamWebApiOptions, + HttpClient httpClient, + IOptions microsoftIdentityOptions) + { + _tokenAcquisition = tokenAcquisition; + _namedDownstreamWebApiOptions = namedDownstreamWebApiOptions; + _httpClient = httpClient; #pragma warning disable CA1062 // Validate arguments of public methods - _microsoftIdentityOptions = microsoftIdentityOptions.Value; + _microsoftIdentityOptions = microsoftIdentityOptions.Value; #pragma warning restore CA1062 // Validate arguments of public methods - } - - /// - public async Task CallWebApiForUserAsync( - string serviceName, - Action? calledDownstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null, - StringContent? content = null) - { - DownstreamWebApiOptions effectiveOptions = MergeOptions(serviceName, calledDownstreamWebApiOptionsOverride); - - if (string.IsNullOrEmpty(effectiveOptions.Scopes)) - { - throw new ArgumentException(IDWebErrorMessage.ScopesNotConfiguredInConfigurationOrViaDelegate); - } - - string apiUrl = effectiveOptions.GetApiUrl(); - - CreateProofOfPossessionConfiguration(effectiveOptions, apiUrl); - - string? userflow; - if (_microsoftIdentityOptions.IsB2C && string.IsNullOrEmpty(effectiveOptions.UserFlow)) - { - userflow = _microsoftIdentityOptions.DefaultUserFlow; - } - else - { - userflow = effectiveOptions.UserFlow; - } - - AuthenticationResult authResult = await _tokenAcquisition.GetAuthenticationResultForUserAsync( - effectiveOptions.GetScopes(), - effectiveOptions.Tenant, - userflow, - user, - effectiveOptions.TokenAcquisitionOptions) - .ConfigureAwait(false); - - HttpResponseMessage response; - using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage( - effectiveOptions.HttpMethod, - apiUrl)) - { - if (content != null) - { - httpRequestMessage.Content = content; - } - - httpRequestMessage.Headers.Add( - Constants.Authorization, - authResult.CreateAuthorizationHeader()); - response = await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); - } - - return response; - } - - /// - public async Task CallWebApiForUserAsync( - string serviceName, - TInput input, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - where TOutput : class - { - HttpResponseMessage response = await CallWebApiForUserAsync( - serviceName, - downstreamWebApiOptionsOverride, - user, - new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json")).ConfigureAwait(false); - - try - { - response.EnsureSuccessStatusCode(); - } - catch - { - string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); - } - - string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - if (string.IsNullOrWhiteSpace(content)) - { - return default; - } - - return JsonSerializer.Deserialize(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - } - - /// - public async Task CallWebApiForAppAsync( - string serviceName, - Action? downstreamWebApiOptionsOverride = null, - StringContent? content = null) - { - DownstreamWebApiOptions effectiveOptions = MergeOptions(serviceName, downstreamWebApiOptionsOverride); - - if (effectiveOptions.Scopes == null) - { - throw new ArgumentException(IDWebErrorMessage.ScopesNotConfiguredInConfigurationOrViaDelegate); - } - - string apiUrl = effectiveOptions.GetApiUrl(); - - CreateProofOfPossessionConfiguration(effectiveOptions, apiUrl); - - AuthenticationResult authResult = await _tokenAcquisition.GetAuthenticationResultForAppAsync( - effectiveOptions.Scopes, - effectiveOptions.Tenant, - effectiveOptions.TokenAcquisitionOptions) - .ConfigureAwait(false); - - HttpResponseMessage response; - using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage( - effectiveOptions.HttpMethod, - apiUrl)) - { - if (content != null) - { - httpRequestMessage.Content = content; - } - - httpRequestMessage.Headers.Add( - Constants.Authorization, - authResult.CreateAuthorizationHeader()); - response = await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); - } - - return response; - } - - /// - /// Merge the options from configuration and override from caller. - /// - /// Named configuration. - /// Delegate to override the configuration. - internal /* for tests */ DownstreamWebApiOptions MergeOptions( - string optionsInstanceName, - Action? calledApiOptionsOverride) - { - // Gets the options from configuration (or default value) - DownstreamWebApiOptions options; - if (optionsInstanceName != null) - { - options = _namedDownstreamWebApiOptions.Get(optionsInstanceName); - } - else - { - options = _namedDownstreamWebApiOptions.CurrentValue; - } - - DownstreamWebApiOptions clonedOptions = options.Clone(); - calledApiOptionsOverride?.Invoke(clonedOptions); - return clonedOptions; - } - - private static void CreateProofOfPossessionConfiguration(DownstreamWebApiOptions effectiveOptions, string apiUrl) - { - if (effectiveOptions.IsProofOfPossessionRequest && effectiveOptions.TokenAcquisitionOptions?.PoPConfiguration != null) - { - if (effectiveOptions.TokenAcquisitionOptions == null) - { - effectiveOptions.TokenAcquisitionOptions = new TokenAcquisitionOptions(); - } - - effectiveOptions.TokenAcquisitionOptions.PoPConfiguration = new PoPAuthenticationConfiguration(new Uri(apiUrl)) - { - HttpMethod = effectiveOptions.HttpMethod, - }; - } - } - } + } + + /// + public async Task CallWebApiForUserAsync( + string serviceName, + Action? calledDownstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null, + StringContent? content = null) + { + DownstreamWebApiOptions effectiveOptions = MergeOptions(serviceName, calledDownstreamWebApiOptionsOverride); + + if (string.IsNullOrEmpty(effectiveOptions.Scopes)) + { + throw new ArgumentException(IDWebErrorMessage.ScopesNotConfiguredInConfigurationOrViaDelegate); + } + + string apiUrl = effectiveOptions.GetApiUrl(); + + CreateProofOfPossessionConfiguration(effectiveOptions, apiUrl); + + string? userflow; + if (_microsoftIdentityOptions.IsB2C && string.IsNullOrEmpty(effectiveOptions.UserFlow)) + { + userflow = _microsoftIdentityOptions.DefaultUserFlow; + } + else + { + userflow = effectiveOptions.UserFlow; + } + + AuthenticationResult authResult = await _tokenAcquisition.GetAuthenticationResultForUserAsync( + effectiveOptions.GetScopes(), + effectiveOptions.Tenant, + userflow, + user, + effectiveOptions.TokenAcquisitionOptions) + .ConfigureAwait(false); + + HttpResponseMessage response; + using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage( + effectiveOptions.HttpMethod, + apiUrl)) + { + if (content != null) + { + httpRequestMessage.Content = content; + } + + httpRequestMessage.Headers.Add( + Constants.Authorization, + authResult.CreateAuthorizationHeader()); + response = await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); + } + + return response; + } + + /// + public async Task CallWebApiForUserAsync( + string serviceName, + TInput input, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + where TOutput : class + { + HttpResponseMessage response = await CallWebApiForUserAsync( + serviceName, + downstreamWebApiOptionsOverride, + user, + new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json")).ConfigureAwait(false); + + try + { + response.EnsureSuccessStatusCode(); + } + catch + { + string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); + } + + string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(content)) + { + return default; + } + + return JsonSerializer.Deserialize(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } + + /// + public async Task CallWebApiForAppAsync( + string serviceName, + Action? downstreamWebApiOptionsOverride = null, + StringContent? content = null) + { + DownstreamWebApiOptions effectiveOptions = MergeOptions(serviceName, downstreamWebApiOptionsOverride); + + if (effectiveOptions.Scopes == null) + { + throw new ArgumentException(IDWebErrorMessage.ScopesNotConfiguredInConfigurationOrViaDelegate); + } + + string apiUrl = effectiveOptions.GetApiUrl(); + + CreateProofOfPossessionConfiguration(effectiveOptions, apiUrl); + + AuthenticationResult authResult = await _tokenAcquisition.GetAuthenticationResultForAppAsync( + effectiveOptions.Scopes, + effectiveOptions.Tenant, + effectiveOptions.TokenAcquisitionOptions) + .ConfigureAwait(false); + + HttpResponseMessage response; + using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage( + effectiveOptions.HttpMethod, + apiUrl)) + { + if (content != null) + { + httpRequestMessage.Content = content; + } + + httpRequestMessage.Headers.Add( + Constants.Authorization, + authResult.CreateAuthorizationHeader()); + response = await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); + } + + return response; + } + + /// + /// Merge the options from configuration and override from caller. + /// + /// Named configuration. + /// Delegate to override the configuration. + internal /* for tests */ DownstreamWebApiOptions MergeOptions( + string optionsInstanceName, + Action? calledApiOptionsOverride) + { + // Gets the options from configuration (or default value) + DownstreamWebApiOptions options; + if (optionsInstanceName != null) + { + options = _namedDownstreamWebApiOptions.Get(optionsInstanceName); + } + else + { + options = _namedDownstreamWebApiOptions.CurrentValue; + } + + DownstreamWebApiOptions clonedOptions = options.Clone(); + calledApiOptionsOverride?.Invoke(clonedOptions); + return clonedOptions; + } + + private static void CreateProofOfPossessionConfiguration(DownstreamWebApiOptions effectiveOptions, string apiUrl) + { + if (effectiveOptions.IsProofOfPossessionRequest && effectiveOptions.TokenAcquisitionOptions?.PoPConfiguration != null) + { + if (effectiveOptions.TokenAcquisitionOptions == null) + { + effectiveOptions.TokenAcquisitionOptions = new TokenAcquisitionOptions(); + } + + effectiveOptions.TokenAcquisitionOptions.PoPConfiguration = new PoPAuthenticationConfiguration(new Uri(apiUrl)) + { + HttpMethod = effectiveOptions.HttpMethod, + }; + } + } + } } diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiExtensions.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiExtensions.cs index e29de9f20..6cae4b54a 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiExtensions.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiExtensions.cs @@ -7,58 +7,58 @@ namespace Microsoft.Identity.Web { - /// - /// Extension methods to support downstream web API services. - /// - public static class DownstreamWebApiExtensions - { - /// - /// Adds a named downstream web API service related to a specific configuration section. - /// - /// Builder. - /// Name of the configuration for the service. - /// This is the name used when calling the service from controller/pages. - /// Configuration. - /// The builder for chaining. - public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddDownstreamWebApi( - this MicrosoftIdentityAppCallsWebApiAuthenticationBuilder builder, - string serviceName, - IConfiguration configuration) - { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + /// + /// Extension methods to support downstream web API services. + /// + public static class DownstreamWebApiExtensions + { + /// + /// Adds a named downstream web API service related to a specific configuration section. + /// + /// Builder. + /// Name of the configuration for the service. + /// This is the name used when calling the service from controller/pages. + /// Configuration. + /// The builder for chaining. + public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddDownstreamWebApi( + this MicrosoftIdentityAppCallsWebApiAuthenticationBuilder builder, + string serviceName, + IConfiguration configuration) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.Configure(serviceName, configuration); - builder.Services.AddHttpClient(); - return builder; - } + builder.Services.Configure(serviceName, configuration); + builder.Services.AddHttpClient(); + return builder; + } - /// - /// Adds a named downstream web API service initialized with delegates. - /// - /// Builder. - /// Name of the configuration for the service. - /// This is the name which will be used when calling the service from controller/pages. - /// Action to configure the options. - /// The builder for chaining. - public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddDownstreamWebApi( - this MicrosoftIdentityAppCallsWebApiAuthenticationBuilder builder, - string serviceName, - Action configureOptions) - { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + /// + /// Adds a named downstream web API service initialized with delegates. + /// + /// Builder. + /// Name of the configuration for the service. + /// This is the name which will be used when calling the service from controller/pages. + /// Action to configure the options. + /// The builder for chaining. + public static MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddDownstreamWebApi( + this MicrosoftIdentityAppCallsWebApiAuthenticationBuilder builder, + string serviceName, + Action configureOptions) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.Configure(serviceName, configureOptions); + builder.Services.Configure(serviceName, configureOptions); - // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests - builder.Services.AddHttpClient(); - builder.Services.Configure(serviceName, configureOptions); - return builder; - } - } + // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests + builder.Services.AddHttpClient(); + builder.Services.Configure(serviceName, configureOptions); + return builder; + } + } } diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs index 8a8b2b233..981a5e7d3 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiGenericExtensions.cs @@ -10,305 +10,305 @@ namespace Microsoft.Identity.Web { - /// - /// Extensions for the downstream web API. - /// - public static class DownstreamWebApiGenericExtensions - { - /// - /// Get a strongly typed response from the web API. - /// - /// Output type. - /// The downstream web API. - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Path to the API endpoint relative to the base URL specified in the configuration. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful in platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// A strongly typed response from the web API. - public static async Task GetForUserAsync( - this IDownstreamWebApi downstreamWebApi, - string serviceName, - string relativePath, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - where TOutput : class - { - if (downstreamWebApi is null) - { - throw new ArgumentNullException(nameof(downstreamWebApi)); - } + /// + /// Extensions for the downstream web API. + /// + public static class DownstreamWebApiGenericExtensions + { + /// + /// Get a strongly typed response from the web API. + /// + /// Output type. + /// The downstream web API. + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Path to the API endpoint relative to the base URL specified in the configuration. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful in platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// A strongly typed response from the web API. + public static async Task GetForUserAsync( + this IDownstreamWebApi downstreamWebApi, + string serviceName, + string relativePath, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + where TOutput : class + { + if (downstreamWebApi is null) + { + throw new ArgumentNullException(nameof(downstreamWebApi)); + } - HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( - serviceName, - PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Get), - user, - null).ConfigureAwait(false); + HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( + serviceName, + PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Get), + user, + null).ConfigureAwait(false); - return await ConvertToOutput(response).ConfigureAwait(false); - } + return await ConvertToOutput(response).ConfigureAwait(false); + } - /// - /// Calls the web API with an HttpPost, providing strongly typed input and getting - /// strongly typed output. - /// - /// Output type. - /// Input type. - /// The downstream web API. - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Path to the API endpoint relative to the base URL specified in the configuration. - /// Input data sent to the API. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful in platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// A strongly typed response from the web API. - public static async Task PostForUserAsync( - this IDownstreamWebApi downstreamWebApi, - string serviceName, - string relativePath, - TInput inputData, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - where TOutput : class - { - if (downstreamWebApi is null) - { - throw new ArgumentNullException(nameof(downstreamWebApi)); - } + /// + /// Calls the web API with an HttpPost, providing strongly typed input and getting + /// strongly typed output. + /// + /// Output type. + /// Input type. + /// The downstream web API. + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Path to the API endpoint relative to the base URL specified in the configuration. + /// Input data sent to the API. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful in platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// A strongly typed response from the web API. + public static async Task PostForUserAsync( + this IDownstreamWebApi downstreamWebApi, + string serviceName, + string relativePath, + TInput inputData, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + where TOutput : class + { + if (downstreamWebApi is null) + { + throw new ArgumentNullException(nameof(downstreamWebApi)); + } - using StringContent? input = ConvertFromInput(inputData); + using StringContent? input = ConvertFromInput(inputData); - HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( - serviceName, - PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Post), - user, - input).ConfigureAwait(false); + HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( + serviceName, + PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Post), + user, + input).ConfigureAwait(false); - return await ConvertToOutput(response).ConfigureAwait(false); - } + return await ConvertToOutput(response).ConfigureAwait(false); + } - /// - /// Calls the web API endpoint with an HttpPut, providing strongly typed input data. - /// - /// Input type. - /// The downstream web API. - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Path to the API endpoint relative to the base URL specified in the configuration. - /// Input data sent to the API. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful in platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// The value returned by the downstream web API. - public static async Task PutForUserAsync( - this IDownstreamWebApi downstreamWebApi, - string serviceName, - string relativePath, - TInput inputData, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - { - if (downstreamWebApi is null) - { - throw new ArgumentNullException(nameof(downstreamWebApi)); - } + /// + /// Calls the web API endpoint with an HttpPut, providing strongly typed input data. + /// + /// Input type. + /// The downstream web API. + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Path to the API endpoint relative to the base URL specified in the configuration. + /// Input data sent to the API. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful in platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// The value returned by the downstream web API. + public static async Task PutForUserAsync( + this IDownstreamWebApi downstreamWebApi, + string serviceName, + string relativePath, + TInput inputData, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + { + if (downstreamWebApi is null) + { + throw new ArgumentNullException(nameof(downstreamWebApi)); + } - using StringContent? input = ConvertFromInput(inputData); + using StringContent? input = ConvertFromInput(inputData); - await downstreamWebApi.CallWebApiForUserAsync( - serviceName, - PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Put), - user, - input).ConfigureAwait(false); - } + await downstreamWebApi.CallWebApiForUserAsync( + serviceName, + PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Put), + user, + input).ConfigureAwait(false); + } - /// - /// Calls the web API endpoint with an HttpPut, provinding strongly typed input data - /// and getting back strongly typed data. - /// - /// Output type. - /// Input type. - /// The downstream web API. - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Path to the API endpoint relative to the base URL specified in the configuration. - /// Input data sent to the API. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful in platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// A strongly typed response from the web API. - public static async Task PutForUserAsync( - this IDownstreamWebApi downstreamWebApi, - string serviceName, - string relativePath, - TInput inputData, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - where TOutput : class - { - if (downstreamWebApi is null) - { - throw new ArgumentNullException(nameof(downstreamWebApi)); - } + /// + /// Calls the web API endpoint with an HttpPut, provinding strongly typed input data + /// and getting back strongly typed data. + /// + /// Output type. + /// Input type. + /// The downstream web API. + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Path to the API endpoint relative to the base URL specified in the configuration. + /// Input data sent to the API. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful in platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// A strongly typed response from the web API. + public static async Task PutForUserAsync( + this IDownstreamWebApi downstreamWebApi, + string serviceName, + string relativePath, + TInput inputData, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + where TOutput : class + { + if (downstreamWebApi is null) + { + throw new ArgumentNullException(nameof(downstreamWebApi)); + } - using StringContent? input = ConvertFromInput(inputData); + using StringContent? input = ConvertFromInput(inputData); - HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( - serviceName, - PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Put), - user, - input).ConfigureAwait(false); + HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( + serviceName, + PrepareOptions(relativePath, downstreamWebApiOptionsOverride, HttpMethod.Put), + user, + input).ConfigureAwait(false); - return await ConvertToOutput(response).ConfigureAwait(false); - } + return await ConvertToOutput(response).ConfigureAwait(false); + } - /// - /// Call a web API endpoint with an HttpGet, - /// and return strongly typed data. - /// - /// Output type. - /// The downstream web API. - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful in platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// The value returned by the downstream web API. - public static async Task CallWebApiForUserAsync( - this IDownstreamWebApi downstreamWebApi, - string serviceName, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - where TOutput : class - { - if (downstreamWebApi is null) - { - throw new ArgumentNullException(nameof(downstreamWebApi)); - } + /// + /// Call a web API endpoint with an HttpGet, + /// and return strongly typed data. + /// + /// Output type. + /// The downstream web API. + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful in platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// The value returned by the downstream web API. + public static async Task CallWebApiForUserAsync( + this IDownstreamWebApi downstreamWebApi, + string serviceName, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + where TOutput : class + { + if (downstreamWebApi is null) + { + throw new ArgumentNullException(nameof(downstreamWebApi)); + } - HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( - serviceName, - downstreamWebApiOptionsOverride, - user, - null).ConfigureAwait(false); + HttpResponseMessage response = await downstreamWebApi.CallWebApiForUserAsync( + serviceName, + downstreamWebApiOptionsOverride, + user, + null).ConfigureAwait(false); - return await ConvertToOutput(response).ConfigureAwait(false); - } + return await ConvertToOutput(response).ConfigureAwait(false); + } - /// - /// Call a web API with a strongly typed input, with an HttpGet. - /// - /// Input type. - /// The downstream web API. - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Input data. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful in platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// The value returned by the downstream web API. - public static async Task GetForUserAsync( - this IDownstreamWebApi downstreamWebApi, - string serviceName, - TInput inputData, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - { - if (downstreamWebApi is null) - { - throw new ArgumentNullException(nameof(downstreamWebApi)); - } + /// + /// Call a web API with a strongly typed input, with an HttpGet. + /// + /// Input type. + /// The downstream web API. + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Input data. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful in platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// The value returned by the downstream web API. + public static async Task GetForUserAsync( + this IDownstreamWebApi downstreamWebApi, + string serviceName, + TInput inputData, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + { + if (downstreamWebApi is null) + { + throw new ArgumentNullException(nameof(downstreamWebApi)); + } - using StringContent? input = ConvertFromInput(inputData); + using StringContent? input = ConvertFromInput(inputData); - await downstreamWebApi.CallWebApiForUserAsync( - serviceName, - downstreamWebApiOptionsOverride, - user, - input).ConfigureAwait(false); - } + await downstreamWebApi.CallWebApiForUserAsync( + serviceName, + downstreamWebApiOptionsOverride, + user, + input).ConfigureAwait(false); + } - private static StringContent ConvertFromInput(TInput input) - { - return new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json"); - } + private static StringContent ConvertFromInput(TInput input) + { + return new StringContent(JsonSerializer.Serialize(input), Encoding.UTF8, "application/json"); + } - private static async Task ConvertToOutput(HttpResponseMessage response) - where TOutput : class - { - try - { - response.EnsureSuccessStatusCode(); - } - catch - { - string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + private static async Task ConvertToOutput(HttpResponseMessage response) + where TOutput : class + { + try + { + response.EnsureSuccessStatusCode(); + } + catch + { + string error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); - } + throw new HttpRequestException($"{(int)response.StatusCode} {response.StatusCode} {error}"); + } - string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(content)) - { - return default; - } + if (string.IsNullOrWhiteSpace(content)) + { + return default; + } - return JsonSerializer.Deserialize(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - } + return JsonSerializer.Deserialize(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } - private static Action PrepareOptions( - string relativePath, - Action? downstreamWebApiOptionsOverride, - HttpMethod httpMethod) - { - Action downstreamWebApiOptions; + private static Action PrepareOptions( + string relativePath, + Action? downstreamWebApiOptionsOverride, + HttpMethod httpMethod) + { + Action downstreamWebApiOptions; - if (downstreamWebApiOptionsOverride == null) - { - downstreamWebApiOptions = options => - { - options.HttpMethod = httpMethod; - options.RelativePath = relativePath; - }; - } - else - { - downstreamWebApiOptions = options => - { - downstreamWebApiOptionsOverride(options); - options.HttpMethod = httpMethod; - options.RelativePath = relativePath; - }; - } + if (downstreamWebApiOptionsOverride == null) + { + downstreamWebApiOptions = options => + { + options.HttpMethod = httpMethod; + options.RelativePath = relativePath; + }; + } + else + { + downstreamWebApiOptions = options => + { + downstreamWebApiOptionsOverride(options); + options.HttpMethod = httpMethod; + options.RelativePath = relativePath; + }; + } - return downstreamWebApiOptions; - } - } + return downstreamWebApiOptions; + } + } } diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs index 1271d781f..34df37985 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs @@ -5,97 +5,97 @@ namespace Microsoft.Identity.Web { - /// - /// Options passed-in to call downstream web APIs. To call Microsoft Graph, see rather - /// MicrosoftGraphOptions in the Microsoft.Identity.Web.MicrosoftGraph assembly. - /// - public class DownstreamWebApiOptions - { - /// - /// Base URL for the called downstream web API. For instance "https://graph.microsoft.com/beta/".. - /// - public string BaseUrl { get; set; } = Constants.GraphBaseUrlV1; + /// + /// Options passed-in to call downstream web APIs. To call Microsoft Graph, see rather + /// MicrosoftGraphOptions in the Microsoft.Identity.Web.MicrosoftGraph assembly. + /// + public class DownstreamWebApiOptions + { + /// + /// Base URL for the called downstream web API. For instance "https://graph.microsoft.com/beta/".. + /// + public string BaseUrl { get; set; } = Constants.GraphBaseUrlV1; - /// - /// Path relative to the (for instance "me"). - /// - public string RelativePath { get; set; } = string.Empty; + /// + /// Path relative to the (for instance "me"). + /// + public string RelativePath { get; set; } = string.Empty; - /// - /// Space separated scopes required to call the downstream web API. - /// For instance "user.read mail.read". - /// - public string? Scopes { get; set; } + /// + /// Space separated scopes required to call the downstream web API. + /// For instance "user.read mail.read". + /// + public string? Scopes { get; set; } - /// - /// [Optional] tenant ID. This is used for specific scenarios where - /// the application needs to call a downstream web API on behalf of a user in several tenants. - /// It would mostly be used from code, not from the configuration. - /// - public string? Tenant { get; set; } + /// + /// [Optional] tenant ID. This is used for specific scenarios where + /// the application needs to call a downstream web API on behalf of a user in several tenants. + /// It would mostly be used from code, not from the configuration. + /// + public string? Tenant { get; set; } - /// - /// [Optional]. User flow (in the case of a B2C downstream web API). If not - /// specified, the B2C downstream web API will be called with the default user flow from - /// . - /// - public string? UserFlow { get; set; } + /// + /// [Optional]. User flow (in the case of a B2C downstream web API). If not + /// specified, the B2C downstream web API will be called with the default user flow from + /// . + /// + public string? UserFlow { get; set; } - /// - /// HTTP method used to call this downstream web API (by default Get). - /// - public HttpMethod HttpMethod { get; set; } = HttpMethod.Get; + /// + /// HTTP method used to call this downstream web API (by default Get). + /// + public HttpMethod HttpMethod { get; set; } = HttpMethod.Get; - /// - /// Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), - /// rather than a Bearer token. - /// PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, - /// which MSAL can manage. See https://aka.ms/msal-net-pop. - /// Set to true to enable PoP tokens automatically. - /// - public bool IsProofOfPossessionRequest { get; set; } + /// + /// Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), + /// rather than a Bearer token. + /// PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, + /// which MSAL can manage. See https://aka.ms/msal-net-pop. + /// Set to true to enable PoP tokens automatically. + /// + public bool IsProofOfPossessionRequest { get; set; } - /// - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// - public TokenAcquisitionOptions TokenAcquisitionOptions { get; set; } = new TokenAcquisitionOptions(); + /// + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// + public TokenAcquisitionOptions TokenAcquisitionOptions { get; set; } = new TokenAcquisitionOptions(); - /// - /// Clone the options (to be able to override them). - /// - /// A clone of the options. - public DownstreamWebApiOptions Clone() - { - return new DownstreamWebApiOptions - { - BaseUrl = BaseUrl, - RelativePath = RelativePath, - Scopes = Scopes, - Tenant = Tenant, - UserFlow = UserFlow, - HttpMethod = HttpMethod, - TokenAcquisitionOptions = TokenAcquisitionOptions.Clone(), - }; - } + /// + /// Clone the options (to be able to override them). + /// + /// A clone of the options. + public DownstreamWebApiOptions Clone() + { + return new DownstreamWebApiOptions + { + BaseUrl = BaseUrl, + RelativePath = RelativePath, + Scopes = Scopes, + Tenant = Tenant, + UserFlow = UserFlow, + HttpMethod = HttpMethod, + TokenAcquisitionOptions = TokenAcquisitionOptions.Clone(), + }; + } - /// - /// Return the downstream web API URL. - /// - /// URL of the downstream web API. + /// + /// Return the downstream web API URL. + /// + /// URL of the downstream web API. #pragma warning disable CA1055 // Uri return values should not be strings - public string GetApiUrl() + public string GetApiUrl() #pragma warning restore CA1055 // Uri return values should not be strings - { - return BaseUrl?.TrimEnd('/') + $"/{RelativePath}"; - } + { + return BaseUrl?.TrimEnd('/') + $"/{RelativePath}"; + } - /// - /// Returns the scopes. - /// - /// Scopes. - public string[] GetScopes() - { - return string.IsNullOrWhiteSpace(Scopes) ? new string[0] : Scopes.Split(' '); - } - } + /// + /// Returns the scopes. + /// + /// Scopes. + public string[] GetScopes() + { + return string.IsNullOrWhiteSpace(Scopes) ? new string[0] : Scopes.Split(' '); + } + } } diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs index c771bc0c5..2a4bb4f9a 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs @@ -8,101 +8,101 @@ namespace Microsoft.Identity.Web { - /// - /// Interface used to call a downstream web API, for instance from controllers. - /// - public interface IDownstreamWebApi - { - /// - /// Calls the downstream web API for the user, based on a description of the - /// downstream web API in the configuration. - /// - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful on platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// HTTP context in the case where is - /// , , . - /// An that the application will process. - public Task CallWebApiForUserAsync( - string serviceName, - Action? calledDownstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null, - StringContent? content = null); + /// + /// Interface used to call a downstream web API, for instance from controllers. + /// + public interface IDownstreamWebApi + { + /// + /// Calls the downstream web API for the user, based on a description of the + /// downstream web API in the configuration. + /// + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful on platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// HTTP context in the case where is + /// , , . + /// An that the application will process. + public Task CallWebApiForUserAsync( + string serviceName, + Action? calledDownstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null, + StringContent? content = null); - /// - /// Calls a downstream web API consuming JSON with some data and returns data. - /// - /// Input type. - /// Output type. - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Input parameter to the downstream web API. - /// Overrides the options proposed in the configuration described - /// by . - /// [Optional] Claims representing a user. This is useful in platforms like Blazor - /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library - /// will find the user from the HttpContext. - /// The value returned by the downstream web API. - /// - /// A list method that returns an IEnumerable<MyItem>>. - /// - /// public async Task<IEnumerable<MyItem>> GetAsync() - /// { - /// return await _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( - /// ServiceName, - /// null, - /// options => - /// { - /// options.RelativePath = $"api/todolist"; - /// }); - /// } - /// - /// - /// Example of editing. - /// - /// public async Task<MyItem> EditAsync(MyItem myItem) - /// { - /// return await _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( - /// ServiceName, - /// nyItem, - /// options => - /// { - /// options.HttpMethod = HttpMethod.Patch; - /// options.RelativePath = $"api/todolist/{myItem.Id}"; - /// }); - /// } - /// - /// - public Task CallWebApiForUserAsync( - string serviceName, - TInput input, - Action? downstreamWebApiOptionsOverride = null, - ClaimsPrincipal? user = null) - where TOutput : class; + /// + /// Calls a downstream web API consuming JSON with some data and returns data. + /// + /// Input type. + /// Output type. + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Input parameter to the downstream web API. + /// Overrides the options proposed in the configuration described + /// by . + /// [Optional] Claims representing a user. This is useful in platforms like Blazor + /// or Azure Signal R, where the HttpContext is not available. In other platforms, the library + /// will find the user from the HttpContext. + /// The value returned by the downstream web API. + /// + /// A list method that returns an IEnumerable<MyItem>>. + /// + /// public async Task<IEnumerable<MyItem>> GetAsync() + /// { + /// return await _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( + /// ServiceName, + /// null, + /// options => + /// { + /// options.RelativePath = $"api/todolist"; + /// }); + /// } + /// + /// + /// Example of editing. + /// + /// public async Task<MyItem> EditAsync(MyItem myItem) + /// { + /// return await _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( + /// ServiceName, + /// nyItem, + /// options => + /// { + /// options.HttpMethod = HttpMethod.Patch; + /// options.RelativePath = $"api/todolist/{myItem.Id}"; + /// }); + /// } + /// + /// + public Task CallWebApiForUserAsync( + string serviceName, + TInput input, + Action? downstreamWebApiOptionsOverride = null, + ClaimsPrincipal? user = null) + where TOutput : class; - /// - /// Calls the downstream web API for the app, with the required scopes. - /// - /// Name of the service describing the downstream web API. There can - /// be several configuration named sections mapped to a , - /// each for one downstream web API. You can pass-in null, but in that case - /// needs to be set. - /// Overrides the options proposed in the configuration described - /// by . - /// HTTP content in the case where is - /// , , . - /// An that the application will process. - public Task CallWebApiForAppAsync( - string serviceName, - Action? downstreamWebApiOptionsOverride = null, - StringContent? content = null); - } + /// + /// Calls the downstream web API for the app, with the required scopes. + /// + /// Name of the service describing the downstream web API. There can + /// be several configuration named sections mapped to a , + /// each for one downstream web API. You can pass-in null, but in that case + /// needs to be set. + /// Overrides the options proposed in the configuration described + /// by . + /// HTTP content in the case where is + /// , , . + /// An that the application will process. + public Task CallWebApiForAppAsync( + string serviceName, + Action? downstreamWebApiOptionsOverride = null, + StringContent? content = null); + } } diff --git a/src/Microsoft.Identity.Web/Extensions.cs b/src/Microsoft.Identity.Web/Extensions.cs index 38ad95b03..efe042c97 100644 --- a/src/Microsoft.Identity.Web/Extensions.cs +++ b/src/Microsoft.Identity.Web/Extensions.cs @@ -3,27 +3,27 @@ namespace Microsoft.Identity.Web { - /// - /// Extension methods. - /// - internal static class Extensions - { - /// Determines whether the specified string collection contains any. - /// The search for. - /// The string collection. - /// - /// true if the specified string collection contains any; otherwise, false. - public static bool ContainsAny(this string searchFor, params string[] stringCollection) - { - foreach (string str in stringCollection) - { - if (searchFor.Contains(str)) - { - return true; - } - } + /// + /// Extension methods. + /// + internal static class Extensions + { + /// Determines whether the specified string collection contains any. + /// The search for. + /// The string collection. + /// + /// true if the specified string collection contains any; otherwise, false. + public static bool ContainsAny(this string searchFor, params string[] stringCollection) + { + foreach (string str in stringCollection) + { + if (searchFor.Contains(str)) + { + return true; + } + } - return false; - } - } + return false; + } + } } diff --git a/src/Microsoft.Identity.Web/HttpContextExtensions.cs b/src/Microsoft.Identity.Web/HttpContextExtensions.cs index cc7a5c220..0f7be1a1e 100644 --- a/src/Microsoft.Identity.Web/HttpContextExtensions.cs +++ b/src/Microsoft.Identity.Web/HttpContextExtensions.cs @@ -6,27 +6,27 @@ namespace Microsoft.Identity.Web { - internal static class HttpContextExtensions - { - /// - /// Keep the validated token associated with the HTTP request. - /// - /// HTTP context. - /// Token to preserve after the token is validated so that - /// it can be used in the actions. - internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, JwtSecurityToken? token) - { - httpContext.Items.Add(Constants.JwtSecurityTokenUsedToCallWebApi, token); - } + internal static class HttpContextExtensions + { + /// + /// Keep the validated token associated with the HTTP request. + /// + /// HTTP context. + /// Token to preserve after the token is validated so that + /// it can be used in the actions. + internal static void StoreTokenUsedToCallWebAPI(this HttpContext httpContext, JwtSecurityToken? token) + { + httpContext.Items.Add(Constants.JwtSecurityTokenUsedToCallWebApi, token); + } - /// - /// Get the parsed information about the token used to call the web API. - /// - /// HTTP context associated with the current request. - /// used to call the web API. - internal static JwtSecurityToken? GetTokenUsedToCallWebAPI(this HttpContext httpContext) - { - return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as JwtSecurityToken; - } - } + /// + /// Get the parsed information about the token used to call the web API. + /// + /// HTTP context associated with the current request. + /// used to call the web API. + internal static JwtSecurityToken? GetTokenUsedToCallWebAPI(this HttpContext httpContext) + { + return httpContext.Items[Constants.JwtSecurityTokenUsedToCallWebApi] as JwtSecurityToken; + } + } } diff --git a/src/Microsoft.Identity.Web/ILoginErrorAccessor.cs b/src/Microsoft.Identity.Web/ILoginErrorAccessor.cs index 06d8ba0ed..455f784a6 100644 --- a/src/Microsoft.Identity.Web/ILoginErrorAccessor.cs +++ b/src/Microsoft.Identity.Web/ILoginErrorAccessor.cs @@ -5,29 +5,29 @@ namespace Microsoft.Identity.Web { - /// - /// Provides access to get or set the current error status. - /// The default implementation will use TempData and be enabled when run under Development. - /// - public interface ILoginErrorAccessor - { - /// - /// Gets the error message for the current request. - /// - /// Current . - /// The current error message if available. - string? GetMessage(HttpContext context); + /// + /// Provides access to get or set the current error status. + /// The default implementation will use TempData and be enabled when run under Development. + /// + public interface ILoginErrorAccessor + { + /// + /// Gets the error message for the current request. + /// + /// Current . + /// The current error message if available. + string? GetMessage(HttpContext context); - /// - /// Sets the error message for the current request. - /// - /// Current . - /// Error message to set. - void SetMessage(HttpContext context, string? message); + /// + /// Sets the error message for the current request. + /// + /// Current . + /// Error message to set. + void SetMessage(HttpContext context, string? message); - /// - /// Gets whether error messages should be displayed. - /// - bool IsEnabled { get; } - } + /// + /// Gets whether error messages should be displayed. + /// + bool IsEnabled { get; } + } } diff --git a/src/Microsoft.Identity.Web/ITokenAcquisition.cs b/src/Microsoft.Identity.Web/ITokenAcquisition.cs index 3028c76aa..88de5c1b4 100644 --- a/src/Microsoft.Identity.Web/ITokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/ITokenAcquisition.cs @@ -9,101 +9,101 @@ namespace Microsoft.Identity.Web { - /// - /// Interface for the token acquisition service (encapsulating MSAL.NET). - /// - public interface ITokenAcquisition - { - /// - /// Typically used from an ASP.NET Core web app or web API controller, this method gets an access token - /// for a downstream API on behalf of the user account which claims are provided in the - /// member of the controller's parameter. - /// - /// Scopes to request for the downstream API to call. - /// Enables to override the tenant/account for the same identity. This is useful in the - /// cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant. - /// Azure AD B2C UserFlow to target. - /// Optional claims principal representing the user. If not provided, will use the signed-in - /// user (in a web app), or the user for which the token was received (in a web API) - /// cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// An access token to call on behalf of the user, the downstream API characterized by its scopes. - Task GetAccessTokenForUserAsync( - IEnumerable scopes, - string? tenantId = null, - string? userFlow = null, - ClaimsPrincipal? user = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null); + /// + /// Interface for the token acquisition service (encapsulating MSAL.NET). + /// + public interface ITokenAcquisition + { + /// + /// Typically used from an ASP.NET Core web app or web API controller, this method gets an access token + /// for a downstream API on behalf of the user account which claims are provided in the + /// member of the controller's parameter. + /// + /// Scopes to request for the downstream API to call. + /// Enables to override the tenant/account for the same identity. This is useful in the + /// cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant. + /// Azure AD B2C UserFlow to target. + /// Optional claims principal representing the user. If not provided, will use the signed-in + /// user (in a web app), or the user for which the token was received (in a web API) + /// cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// An access token to call on behalf of the user, the downstream API characterized by its scopes. + Task GetAccessTokenForUserAsync( + IEnumerable scopes, + string? tenantId = null, + string? userFlow = null, + ClaimsPrincipal? user = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null); - /// - /// Typically used from an ASP.NET Core web app or web API controller, this method gets an access token - /// for a downstream API on behalf of the user account which claims are provided in the - /// member of the controller's parameter. - /// - /// Scopes to request for the downstream API to call. - /// Enables to override the tenant/account for the same identity. This is useful in the - /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - /// Azure AD B2C UserFlow to target. - /// Optional claims principal representing the user. If not provided, will use the signed-in - /// user (in a web app), or the user for which the token was received (in a web API) - /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// An to call on behalf of the user, the downstream API characterized by its scopes. - Task GetAuthenticationResultForUserAsync( - IEnumerable scopes, - string? tenantId = null, - string? userFlow = null, - ClaimsPrincipal? user = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null); + /// + /// Typically used from an ASP.NET Core web app or web API controller, this method gets an access token + /// for a downstream API on behalf of the user account which claims are provided in the + /// member of the controller's parameter. + /// + /// Scopes to request for the downstream API to call. + /// Enables to override the tenant/account for the same identity. This is useful in the + /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + /// Azure AD B2C UserFlow to target. + /// Optional claims principal representing the user. If not provided, will use the signed-in + /// user (in a web app), or the user for which the token was received (in a web API) + /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// An to call on behalf of the user, the downstream API characterized by its scopes. + Task GetAuthenticationResultForUserAsync( + IEnumerable scopes, + string? tenantId = null, + string? userFlow = null, + ClaimsPrincipal? user = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null); - /// - /// Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) - /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - /// - /// The scope requested to access a protected API. For this flow (client credentials), the scope - /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - /// in the portal, cannot be overridden in the application, as you can request a token for only one resource at a time (use - /// several calls to get tokens for other resources). - /// Enables overriding of the tenant/account for the same identity. This is useful in the - /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// An access token for the app itself, based on its scopes. - Task GetAccessTokenForAppAsync( - string scope, - string? tenant = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null); + /// + /// Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) + /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + /// + /// The scope requested to access a protected API. For this flow (client credentials), the scope + /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + /// in the portal, cannot be overridden in the application, as you can request a token for only one resource at a time (use + /// several calls to get tokens for other resources). + /// Enables overriding of the tenant/account for the same identity. This is useful in the + /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// An access token for the app itself, based on its scopes. + Task GetAccessTokenForAppAsync( + string scope, + string? tenant = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null); - /// - /// Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) - /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - /// - /// The scope requested to access a protected API. For this flow (client credentials), the scope - /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use - /// several calls to get tokens for other resources). - /// Enables overriding of the tenant/account for the same identity. This is useful - /// for multi tenant apps or daemons. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// An authentication result for the app itself, based on its scopes. - Task GetAuthenticationResultForAppAsync( - string scope, - string? tenant = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null); + /// + /// Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) + /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + /// + /// The scope requested to access a protected API. For this flow (client credentials), the scope + /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + /// several calls to get tokens for other resources). + /// Enables overriding of the tenant/account for the same identity. This is useful + /// for multi tenant apps or daemons. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// An authentication result for the app itself, based on its scopes. + Task GetAuthenticationResultForAppAsync( + string scope, + string? tenant = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null); - /// - /// Used in web APIs (which therefore cannot have an interaction with the user). - /// Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that - /// the client can trigger an interaction with the user so the user can consent to more scopes. - /// - /// Scopes to consent to. - /// triggering the challenge. - /// The to update. - /// A representing the asynchronous operation. - Task ReplyForbiddenWithWwwAuthenticateHeaderAsync( - IEnumerable scopes, - MsalUiRequiredException msalServiceException, - HttpResponse? httpResponse = null); - } + /// + /// Used in web APIs (which therefore cannot have an interaction with the user). + /// Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that + /// the client can trigger an interaction with the user so the user can consent to more scopes. + /// + /// Scopes to consent to. + /// triggering the challenge. + /// The to update. + /// A representing the asynchronous operation. + Task ReplyForbiddenWithWwwAuthenticateHeaderAsync( + IEnumerable scopes, + MsalUiRequiredException msalServiceException, + HttpResponse? httpResponse = null); + } } diff --git a/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs b/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs index c48fc2d48..6292383cc 100644 --- a/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs +++ b/src/Microsoft.Identity.Web/ITokenAcquisitionInternal.cs @@ -7,48 +7,48 @@ namespace Microsoft.Identity.Web { - /// - /// Interface for the internal operations of token acquisition service (encapsulating MSAL.NET). - /// - internal interface ITokenAcquisitionInternal : ITokenAcquisition - { - /// - /// In a web app, adds, to the MSAL.NET cache, the account of the user authenticating to the web app, when the authorization code is received (after the user - /// signed-in and consented) - /// An On-behalf-of token contained in the is added to the cache, so that it can then be used to acquire another token on-behalf-of the - /// same user in order to call to downstream APIs. - /// - /// The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. - /// Scopes to request. - /// A that represents a completed add to cache operation. - /// - /// From the configuration of the Authentication of the ASP.NET Core web API: - /// OpenIdConnectOptions options; - /// - /// Subscribe to the authorization code received event: - /// - /// options.Events = new OpenIdConnectEvents(); - /// options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; - /// } - /// - /// - /// And then in the OnAuthorizationCodeRecieved method, call : - /// - /// private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) - /// { - /// var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); - /// await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); - /// } - /// - /// - Task AddAccountToCacheFromAuthorizationCodeAsync(AuthorizationCodeReceivedContext context, IEnumerable scopes); + /// + /// Interface for the internal operations of token acquisition service (encapsulating MSAL.NET). + /// + internal interface ITokenAcquisitionInternal : ITokenAcquisition + { + /// + /// In a web app, adds, to the MSAL.NET cache, the account of the user authenticating to the web app, when the authorization code is received (after the user + /// signed-in and consented) + /// An On-behalf-of token contained in the is added to the cache, so that it can then be used to acquire another token on-behalf-of the + /// same user in order to call to downstream APIs. + /// + /// The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. + /// Scopes to request. + /// A that represents a completed add to cache operation. + /// + /// From the configuration of the Authentication of the ASP.NET Core web API: + /// OpenIdConnectOptions options; + /// + /// Subscribe to the authorization code received event: + /// + /// options.Events = new OpenIdConnectEvents(); + /// options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; + /// } + /// + /// + /// And then in the OnAuthorizationCodeRecieved method, call : + /// + /// private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + /// { + /// var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); + /// await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); + /// } + /// + /// + Task AddAccountToCacheFromAuthorizationCodeAsync(AuthorizationCodeReceivedContext context, IEnumerable scopes); - /// - /// Removes the account associated with context.HttpContext.User from the MSAL.NET cache. - /// - /// RedirectContext passed-in to a - /// OpenID Connect event. - /// A that represents a completed remove from cache operation. - Task RemoveAccountAsync(RedirectContext context); - } + /// + /// Removes the account associated with context.HttpContext.User from the MSAL.NET cache. + /// + /// RedirectContext passed-in to a + /// OpenID Connect event. + /// A that represents a completed remove from cache operation. + Task RemoveAccountAsync(RedirectContext context); + } } diff --git a/src/Microsoft.Identity.Web/IdHelper.cs b/src/Microsoft.Identity.Web/IdHelper.cs index b8601e59e..1c8d021f2 100644 --- a/src/Microsoft.Identity.Web/IdHelper.cs +++ b/src/Microsoft.Identity.Web/IdHelper.cs @@ -8,35 +8,35 @@ namespace Microsoft.Identity.Web { - internal static class IdHelper - { - private static readonly Lazy s_idWebVersion = new Lazy( - () => - { + internal static class IdHelper + { + private static readonly Lazy s_idWebVersion = new Lazy( + () => + { #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - string fullVersion = typeof(IdHelper).GetTypeInfo().Assembly.FullName; + string fullVersion = typeof(IdHelper).GetTypeInfo().Assembly.FullName; #pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. - var regex = new Regex(@"Version=[\d]+.[\d+]+.[\d]+.[\d]+"); + var regex = new Regex(@"Version=[\d]+.[\d+]+.[\d]+.[\d]+"); #pragma warning disable CS8604 - Match? match = regex.Match(fullVersion); + Match? match = regex.Match(fullVersion); #pragma warning restore CS8604 - if (!match.Success) - { - return string.Empty; - } + if (!match.Success) + { + return string.Empty; + } - string[] version = match.Groups[0].Value.Split( - new[] - { - '=', - }, - StringSplitOptions.None); - return version[1]; - }); + string[] version = match.Groups[0].Value.Split( + new[] + { + '=', + }, + StringSplitOptions.None); + return version[1]; + }); - public static string CreateTelemetryInfo() - { - return string.Format(CultureInfo.InvariantCulture, Constants.IDWebSku + s_idWebVersion.Value); - } - } + public static string CreateTelemetryInfo() + { + return string.Format(CultureInfo.InvariantCulture, Constants.IDWebSku + s_idWebVersion.Value); + } + } } diff --git a/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs b/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs index 230046588..d748ccaa9 100644 --- a/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs +++ b/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs @@ -11,90 +11,90 @@ namespace Microsoft.Identity.Web { - /// - /// Helper methods to handle incremental consent and conditional access in - /// a web app. - /// - internal static class IncrementalConsentAndConditionalAccessHelper - { - /// - /// Can the exception be solved by re-signing-in the user?. - /// - /// Exception from which the decision will be made. - /// Returns true if the issue can be solved by signing-in - /// the user, and false, otherwise. - public static bool CanBeSolvedByReSignInOfUser(MsalUiRequiredException ex) - { - if (ex == null) - { - throw new ArgumentNullException(nameof(ex)); - } + /// + /// Helper methods to handle incremental consent and conditional access in + /// a web app. + /// + internal static class IncrementalConsentAndConditionalAccessHelper + { + /// + /// Can the exception be solved by re-signing-in the user?. + /// + /// Exception from which the decision will be made. + /// Returns true if the issue can be solved by signing-in + /// the user, and false, otherwise. + public static bool CanBeSolvedByReSignInOfUser(MsalUiRequiredException ex) + { + if (ex == null) + { + throw new ArgumentNullException(nameof(ex)); + } - // ex.ErrorCode != MsalUiRequiredException.UserNullError indicates a cache problem. - // When calling an [Authenticate]-decorated controller we expect an authenticated - // user and therefore its account should be in the cache. However in the case of an - // InMemoryCache, the cache could be empty if the server was restarted. This is why - // the null_user exception is thrown. - return ex.ErrorCode.ContainsAny(new[] { MsalError.UserNullError, MsalError.InvalidGrantError }); - } + // ex.ErrorCode != MsalUiRequiredException.UserNullError indicates a cache problem. + // When calling an [Authenticate]-decorated controller we expect an authenticated + // user and therefore its account should be in the cache. However in the case of an + // InMemoryCache, the cache could be empty if the server was restarted. This is why + // the null_user exception is thrown. + return ex.ErrorCode.ContainsAny(new[] { MsalError.UserNullError, MsalError.InvalidGrantError }); + } - /// - /// Build authentication properties needed for incremental consent. - /// - /// Scopes to request. - /// instance. - /// User. - /// Userflow being invoked for AAD B2C. - /// AuthenticationProperties. - public static AuthenticationProperties BuildAuthenticationProperties( - string[]? scopes, - MsalUiRequiredException ex, - ClaimsPrincipal user, - string? userflow = null) - { - if (ex == null) - { - throw new ArgumentNullException(nameof(ex)); - } + /// + /// Build authentication properties needed for incremental consent. + /// + /// Scopes to request. + /// instance. + /// User. + /// Userflow being invoked for AAD B2C. + /// AuthenticationProperties. + public static AuthenticationProperties BuildAuthenticationProperties( + string[]? scopes, + MsalUiRequiredException ex, + ClaimsPrincipal user, + string? userflow = null) + { + if (ex == null) + { + throw new ArgumentNullException(nameof(ex)); + } - scopes ??= new string[0]; - var properties = new AuthenticationProperties(); + scopes ??= new string[0]; + var properties = new AuthenticationProperties(); - // Set the scopes, including the scopes that ADAL.NET / MSAL.NET need for the token cache - string[] additionalBuiltInScopes = - { - OidcConstants.ScopeOpenId, - OidcConstants.ScopeOfflineAccess, - OidcConstants.ScopeProfile, - }; + // Set the scopes, including the scopes that ADAL.NET / MSAL.NET need for the token cache + string[] additionalBuiltInScopes = + { + OidcConstants.ScopeOpenId, + OidcConstants.ScopeOfflineAccess, + OidcConstants.ScopeProfile, + }; - properties.SetParameter>( - OpenIdConnectParameterNames.Scope, - scopes.Union(additionalBuiltInScopes).ToList()); + properties.SetParameter>( + OpenIdConnectParameterNames.Scope, + scopes.Union(additionalBuiltInScopes).ToList()); - // Attempts to set the login_hint to avoid the logged-in user to be presented with an account selection dialog - var loginHint = user.GetLoginHint(); - if (!string.IsNullOrWhiteSpace(loginHint)) - { - properties.SetParameter(OpenIdConnectParameterNames.LoginHint, loginHint); + // Attempts to set the login_hint to avoid the logged-in user to be presented with an account selection dialog + var loginHint = user.GetLoginHint(); + if (!string.IsNullOrWhiteSpace(loginHint)) + { + properties.SetParameter(OpenIdConnectParameterNames.LoginHint, loginHint); - var domainHint = user.GetDomainHint(); - properties.SetParameter(OpenIdConnectParameterNames.DomainHint, domainHint); - } + var domainHint = user.GetDomainHint(); + properties.SetParameter(OpenIdConnectParameterNames.DomainHint, domainHint); + } - // Additional claims required (for instance MFA) - if (!string.IsNullOrEmpty(ex.Claims)) - { - properties.Items.Add(OidcConstants.AdditionalClaims, ex.Claims); - } + // Additional claims required (for instance MFA) + if (!string.IsNullOrEmpty(ex.Claims)) + { + properties.Items.Add(OidcConstants.AdditionalClaims, ex.Claims); + } - // Include current userflow for B2C - if (!string.IsNullOrEmpty(userflow)) - { - properties.Items.Add(OidcConstants.PolicyKey, userflow); - } + // Include current userflow for B2C + if (!string.IsNullOrEmpty(userflow)) + { + properties.Items.Add(OidcConstants.PolicyKey, userflow); + } - return properties; - } - } + return properties; + } + } } diff --git a/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerConfigurationRetriever.cs b/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerConfigurationRetriever.cs index a48f6017e..f2642090e 100644 --- a/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerConfigurationRetriever.cs +++ b/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerConfigurationRetriever.cs @@ -9,39 +9,39 @@ namespace Microsoft.Identity.Web.InstanceDiscovery { - /// - /// An implementation of IConfigurationRetriever geared towards Azure AD issuers metadata. - /// - internal class IssuerConfigurationRetriever : IConfigurationRetriever - { - /// Retrieves a populated configuration given an address and an . - /// Address of the discovery document. - /// The to use to read the discovery document. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. . - /// - /// A that, when completed, returns from the configuration. - /// - /// address - Azure AD Issuer metadata address URL is required - /// or retriever - No metadata document retriever is provided. - public async Task GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel) - { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentNullException(nameof(address), IDWebErrorMessage.IssuerMetadataUrlIsRequired); - } + /// + /// An implementation of IConfigurationRetriever geared towards Azure AD issuers metadata. + /// + internal class IssuerConfigurationRetriever : IConfigurationRetriever + { + /// Retrieves a populated configuration given an address and an . + /// Address of the discovery document. + /// The to use to read the discovery document. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. . + /// + /// A that, when completed, returns from the configuration. + /// + /// address - Azure AD Issuer metadata address URL is required + /// or retriever - No metadata document retriever is provided. + public async Task GetConfigurationAsync(string address, IDocumentRetriever retriever, CancellationToken cancel) + { + if (string.IsNullOrEmpty(address)) + { + throw new ArgumentNullException(nameof(address), IDWebErrorMessage.IssuerMetadataUrlIsRequired); + } - if (retriever == null) - { - throw new ArgumentNullException(nameof(retriever), IDWebErrorMessage.NoMetadataDocumentRetrieverProvided); - } + if (retriever == null) + { + throw new ArgumentNullException(nameof(retriever), IDWebErrorMessage.NoMetadataDocumentRetrieverProvided); + } - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - }; + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }; - string doc = await retriever.GetDocumentAsync(address, cancel).ConfigureAwait(false); - return JsonSerializer.Deserialize(doc, options)!; // Note: The analyzer says Deserialize can return null, but the method comment says it just throws exceptions. - } - } + string doc = await retriever.GetDocumentAsync(address, cancel).ConfigureAwait(false); + return JsonSerializer.Deserialize(doc, options)!; // Note: The analyzer says Deserialize can return null, but the method comment says it just throws exceptions. + } + } } diff --git a/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerMetadata.cs b/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerMetadata.cs index 8a76ba6fb..812c99a86 100644 --- a/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerMetadata.cs +++ b/src/Microsoft.Identity.Web/InstanceDiscovery/IssuerMetadata.cs @@ -6,27 +6,27 @@ namespace Microsoft.Identity.Web.InstanceDiscovery { - /// - /// Model class to hold information parsed from the Azure AD issuer endpoint. - /// - internal class IssuerMetadata - { - /// - /// Tenant discovery endpoint. - /// - [JsonPropertyName(Constants.TenantDiscoveryEndpoint)] - public string? TenantDiscoveryEndpoint { get; set; } + /// + /// Model class to hold information parsed from the Azure AD issuer endpoint. + /// + internal class IssuerMetadata + { + /// + /// Tenant discovery endpoint. + /// + [JsonPropertyName(Constants.TenantDiscoveryEndpoint)] + public string? TenantDiscoveryEndpoint { get; set; } - /// - /// API Version. - /// - [JsonPropertyName(Constants.ApiVersion)] - public string? ApiVersion { get; set; } + /// + /// API Version. + /// + [JsonPropertyName(Constants.ApiVersion)] + public string? ApiVersion { get; set; } - /// - /// List of metadata associated with the endpoint. - /// - [JsonPropertyName(Constants.Metadata)] - public List Metadata { get; set; } = new List(); - } + /// + /// List of metadata associated with the endpoint. + /// + [JsonPropertyName(Constants.Metadata)] + public List Metadata { get; set; } = new List(); + } } diff --git a/src/Microsoft.Identity.Web/InstanceDiscovery/Metadata.cs b/src/Microsoft.Identity.Web/InstanceDiscovery/Metadata.cs index f52049afa..acf938893 100644 --- a/src/Microsoft.Identity.Web/InstanceDiscovery/Metadata.cs +++ b/src/Microsoft.Identity.Web/InstanceDiscovery/Metadata.cs @@ -6,28 +6,28 @@ namespace Microsoft.Identity.Web.InstanceDiscovery { - /// - /// Model child class to hold alias information parsed from the Azure AD issuer endpoint. - /// - internal class Metadata - { - /// - /// Preferred alias. - /// - [JsonPropertyName(Constants.PreferredNetwork)] - public string? PreferredNetwork { get; set; } + /// + /// Model child class to hold alias information parsed from the Azure AD issuer endpoint. + /// + internal class Metadata + { + /// + /// Preferred alias. + /// + [JsonPropertyName(Constants.PreferredNetwork)] + public string? PreferredNetwork { get; set; } - /// - /// Preferred alias to cache tokens emitted by one of the aliases (to avoid - /// SSO islands). - /// - [JsonPropertyName(Constants.PreferredCache)] - public string? PreferredCache { get; set; } + /// + /// Preferred alias to cache tokens emitted by one of the aliases (to avoid + /// SSO islands). + /// + [JsonPropertyName(Constants.PreferredCache)] + public string? PreferredCache { get; set; } - /// - /// Aliases of issuer URLs which are equivalent. - /// - [JsonPropertyName(Constants.Aliases)] - public List Aliases { get; set; } = new List(); - } + /// + /// Aliases of issuer URLs which are equivalent. + /// + [JsonPropertyName(Constants.Aliases)] + public List Aliases { get; set; } = new List(); + } } diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj index 2e5ae5555..d51acf253 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.csproj @@ -1,122 +1,122 @@  - - 1.0.0-localbuild - - $(ClientSemVer) - - $(DefineConstants);WEB - true - - Microsoft Identity Web - Microsoft - Microsoft Corporation - Microsoft Identity Web - - This package enables ASP.NET Core web apps and web APIs to use the Microsoft identity platform (formerly Azure AD v2.0). - This package is specifically used for web applications, which sign-in users, and protected web APIs, which optionally call downstream web APIs. - - © Microsoft Corporation. All rights reserved. - MIT - https://github.com/AzureAD/microsoft-identity-web - https://github.com/AzureAD/microsoft-identity-web - The release notes are available at https://github.com/AzureAD/microsoft-identity-web/releases and the roadmap at https://github.com/AzureAD/microsoft-identity-web/wiki#roadmap - Microsoft Identity Web;Microsoft identity platform;Microsoft.Identity.Web;.NET;ASP.NET Core;Web App;Web API;B2C;Azure Active Directory;AAD;Identity;Authentication;Authorization - {FD55C071-48D1-4FE8-8B1D-773E067FEC91} + + 1.0.0-localbuild + + $(ClientSemVer) + + $(DefineConstants);WEB + true + + Microsoft Identity Web + Microsoft + Microsoft Corporation + Microsoft Identity Web + + This package enables ASP.NET Core web apps and web APIs to use the Microsoft identity platform (formerly Azure AD v2.0). + This package is specifically used for web applications, which sign-in users, and protected web APIs, which optionally call downstream web APIs. + + © Microsoft Corporation. All rights reserved. + MIT + https://github.com/AzureAD/microsoft-identity-web + https://github.com/AzureAD/microsoft-identity-web + The release notes are available at https://github.com/AzureAD/microsoft-identity-web/releases and the roadmap at https://github.com/AzureAD/microsoft-identity-web/wiki#roadmap + Microsoft Identity Web;Microsoft identity platform;Microsoft.Identity.Web;.NET;ASP.NET Core;Web App;Web API;B2C;Azure Active Directory;AAD;Identity;Authentication;Authorization + {FD55C071-48D1-4FE8-8B1D-773E067FEC91} - true - true - - true - snupkg + true + true + + true + snupkg - + - + - - True - - + + True + + - + - netcoreapp3.1; net472; net5.0 - true - ../../build/MSAL.snk - true - enable + netcoreapp3.1; net472; net5.0 + true + ../../build/MSAL.snk + true + enable - $(DefineConstants);DOTNET_50_AND_ABOVE + $(DefineConstants);DOTNET_50_AND_ABOVE - $(DefineConstants);DOTNET_472 - 8.0 + $(DefineConstants);DOTNET_472 + 8.0 - - false - ..\..\.sonarlint\azuread_microsoft-identity-webcsharp.ruleset + + false + ..\..\.sonarlint\azuread_microsoft-identity-webcsharp.ruleset - Microsoft.Identity.Web.xml + Microsoft.Identity.Web.xml - - + + - - + + - - - - - - - - - - - + + + + + + + + + + + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index 7742308cc..6a31d128f 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -1,2840 +1,2840 @@ - - Microsoft.Identity.Web - - - - - Extension methods for . - - - - - Creates the from the values found - in an . - - The instance. - A built from . - - - - Extension methods related to App Services authentication (Easy Auth). - - - - - Add authentication with App Services. - - Authentication builder. - The builder, to chain commands. - - - - Default values related to AppServiceAuthentication handler. - - - - - The default value used for AppServiceAuthenticationOptions.AuthenticationScheme. - - - - - App service authentication handler. - - - - - Constructor for the AppServiceAuthenticationHandler. - Note the parameters are required by the base class. - - App service authentication options. - Logger factory. - URL encoder. - System clock. - - - - - - - Information about the App Services configuration on the host. - - - - - Is App Services authentication enabled?. - - - - - Logout URL for App Services Auth web sites. - - - - - ClientID of the App Services Auth web site. - - - - - Client secret of the App Services Auth web site. - - - - - Issuer of the App Services Auth web site. - - - - - Get headers from environment to help debugging App Services authentication. - - - - - Get the ID token from the headers send by App services authentication. - - Headers. - the ID Token. - - - - Get the IDP token from the headers send by App services authentication. - - Headers. - the IDP. - - - - Get the user claims from the headers and environment variables - - Headers - User claims - - - - Options for Azure App Services authentication. - - - - - Implementation of ITokenAcquisition for App Services authentication (EasyAuth). - - - - - Constructor of the AppServicesAuthenticationTokenAcquisition. - - The App token cache provider. - Access to the HttpContext of the request. - HTTP client factory. - - - - - - - - - - - - - - - - - - - Filter used on a controller action to trigger incremental consent. - - - The following controller action will trigger. - - [AuthorizeForScopes(Scopes = new[] {"Mail.Send"})] - public async Task<IActionResult> SendEmail() - { - } - - - - - - Scopes to request. - - - - - Key section on the configuration file that holds the scope value. - - - - - Azure AD B2C user flow. - - - - - Allows specifying an AuthenticationScheme if OpenIdConnect is not the default challenge scheme. - - - - - Handles the . - - Context provided by ASP.NET Core. - - - - Finds an MsalUiRequiredException in one of the inner exceptions. - - Exception from which we look for an MsalUiRequiredException. - The MsalUiRequiredException if there is one, null, otherwise. - - - - Extensions for . - - - - - Enables an Azure Function to act as/expose a protected web API, enabling bearer token authentication. Calling this method from your Azure function validates the token and exposes the identity of the user or app on behalf of which your function is called, in the HttpContext.User member, where your function can make use of it. - - The current HTTP Context, such as req.HttpContext. - A task indicating success or failure. In case of failure . - - - - Description of a certificate. - - - - - Creates a certificate description from a certificate (by code). - - Certificate. - A certificate description. - - - - Creates a certificate description from Key Vault. - - The Key Vault URL. - The name of the certificate in Key Vault. - A certificate description. - - - - Creates a certificate description from a Base64 encoded value. - - Base64 encoded certificate value. - A certificate description. - - - - Creates a certificate description from path on disk. - - Path where to find the certificate file. - Certificate password. - A certificate description. - - - - Creates a certificate description from a thumbprint and store location (Certificate Manager on Windows, for instance). - - Certificate thumbprint. - Store location where to find the certificate. - Store name where to find the certificate. - A certificate description. - - - - Creates a certificate description from a certificate distinguished name (such as CN=name) - and store location (Certificate Manager on Windows, for instance). - - Certificate distinguished named. - Store location where to find the certificate. - Store name where to find the certificate. - A certificate description. - - - - Type of the source of the certificate. - - - - - Container in which to find the certificate. - - If equals , then - the container is the Key Vault base URL. - If equals , then - this value is not used. - If equals , then - this value is the path on disk where to find the certificate. - If equals , - or , then - this value is the path to the certificate in the cert store, for instance CurrentUser/My. - - - - - - URL of the Key Vault, for instance https://msidentitywebsamples.vault.azure.net. - - - - - Certificate store path, for instance "CurrentUser/My". - - This property should only be used in conjunction with DistinguishedName or Thumbprint. - - - - Certificate distinguished name. - - - - - Name of the certificate in Key Vault. - - - - - Certificate thumbprint. - - - - - Path on disk to the certificate. - - - - - Path on disk to the certificate password. - - - - - Base64 encoded certificate value. - - - - - Defines where and how to import the private key of an X.509 certificate. - - - - - Reference to the certificate or value. - - - If equals , then - the reference is the name of the certificate in Key Vault (maybe the version?). - If equals , then - this value is the base 64 encoded certificate itself. - If equals , then - this value is the password to access the certificate (if needed). - If equals , - this value is the distinguished name. - If equals , - this value is the thumbprint. - - - - - The certificate, either provided directly in code - or loaded from the description. - - - - - Source for a certificate. - - - - - Certificate itself. - - - - - From an Azure Key Vault. - - - - - Base64 encoded string directly from the configuration. - - - - - From local path on disk. - - - - - From the certificate store, described by its thumbprint. - - - - - From the certificate store, described by its distinguished name. - - - - - Certificate Loader. - Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. - For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. - - IConfidentialClientApplication app; - ICertificateLoader certificateLoader = new DefaultCertificateLoader(); - certificateLoader.LoadIfNeeded(config.CertificateDescription); - - app = ConfidentialClientApplicationBuilder.Create(config.ClientId) - .WithCertificate(config.CertificateDescription.Certificate) - .WithAuthority(new Uri(config.Authority)) - .Build(); - - - - - - User assigned managed identity client ID (as opposed to system assigned managed identity) - See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. - - - - - Load the certificate from the description, if needed. - - Description of the certificate. - - - - Load a certificate from Key Vault, including the private key. - - URL of Key Vault. - Name of the certificate. - Defines where and how to import the private key of an X.509 certificate. - An certificate. - This code is inspired by Heath Stewart's code in: - https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs#L46-L82. - - - - - Find a certificate by criteria. - - - - - Interface to implement loading of a certificate. - Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. - For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. - - IConfidentialClientApplication app; - ICertificateLoader certificateLoader = new DefaultCertificateLoader(); - certificateLoader.LoadIfNeeded(config.CertificateDescription); - - app = ConfidentialClientApplicationBuilder.Create(config.ClientId) - .WithCertificate(config.CertificateDescription.Certificate) - .WithAuthority(new Uri(config.Authority)) - .Build(); - - - - - - Load the certificate from the description, if needed. - - Description of the certificate. - - - - Extensions for . - - - - - Gets the account identifier for an MSAL.NET account from a . - - Claims principal. - A string corresponding to an account identifier as defined in . - - - - Gets the unique object ID associated with the . - - The from which to retrieve the unique object ID. - This method returns the object ID both in case the developer has enabled or not claims mapping. - Unique object ID of the identity, or null if it cannot be found. - - - - Gets the Tenant ID associated with the . - - The from which to retrieve the tenant ID. - Tenant ID of the identity, or null if it cannot be found. - This method returns the tenant ID both in case the developer has enabled or not claims mapping. - - - - Gets the login-hint associated with a . - - Identity for which to complete the login-hint. - The login hint for the identity, or null if it cannot be found. - - - - Gets the domain-hint associated with an identity. - - Identity for which to compute the domain-hint. - The domain hint for the identity, or null if it cannot be found. - - - - Get the display name for the signed-in user, from the . - - Claims about the user/account. - A string containing the display name for the user, as determined by Azure AD (v1.0) and Microsoft identity platform (v2.0) tokens, - or null if the claims cannot be found. - See https://docs.microsoft.com/azure/active-directory/develop/id-tokens#payload-claims. - - - - Gets the user flow ID associated with the . - - The from which to retrieve the user flow ID. - User flow ID of the identity, or null if it cannot be found. - - - - Gets the Home Object ID associated with the . - - The from which to retrieve the sub claim. - Home Object ID (sub) of the identity, or null if it cannot be found. - - - - Gets the Home Tenant ID associated with the . - - The from which to retrieve the sub claim. - Home Tenant ID (sub) of the identity, or null if it cannot be found. - - - - Gets the NameIdentifierId associated with the . - - The from which to retrieve the NameIdentifierId claim. - Name identifier ID of the identity, or null if it cannot be found. - - - - Factory class to create objects. - - - - - 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) - { - foreach (var notification in notifications) - { - SubscriptionStore subscription = - subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); - HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, - subscription.UserId); - string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); - - - - - - Constants for claim types. - - - - - Name claim: "name". - - - - - Old Object Id claim: http://schemas.microsoft.com/identity/claims/objectidentifier. - - - - - New Object id claim: "oid". - - - - - PreferredUserName: "preferred_username". - - - - - Old TenantId claim: "http://schemas.microsoft.com/identity/claims/tenantid". - - - - - New Tenant Id claim: "tid". - - - - - ClientInfo claim: "client_info". - - - - - UniqueObjectIdentifier: "uid". - Home Object Id. - - - - - UniqueTenantIdentifier: "utid". - Home Tenant Id. - - - - - Older scope claim: "http://schemas.microsoft.com/identity/claims/scope". - - - - - Newer scope claim: "scp". - - - - - New Roles claim = "roles". - - - - - Old Role claim: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". - - - - - Subject claim: "sub". - - - - - Acr claim: "acr". - - - - - UserFlow claim: "http://schemas.microsoft.com/claims/authnclassreference". - - - - - Tfp claim: "tfp". - - - - - Name Identifier ID claim: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier". - - - - - General constants for Microsoft Identity Web. - - - - - LoginHint. - Represents the preferred_username claim in the ID token. - - - - - DomainHint. - Determined by the tenant Id. - - - - - Claims. - Determined from the signed-in user. - - - - - Bearer. - Predominant type of access token used with OAuth 2.0. - - - - - AzureAd. - Configuration section name for AzureAd. - - - - - AzureAdB2C. - Configuration section name for AzureAdB2C. - - - - - Scope. - - - - - Policy for B2C user flows. - The name of the policy to check against a specific user flow. - - - - - Constants related to the error messages. - - - - - Constants related to the log messages. - - - - - Extension class containing cookie policies (work around for same site). - - - - - Handles SameSite cookie issue according to the https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1. - The default list of user agents that disallow "SameSite=None", - was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. - - to update. - to chain. - - - - Handles SameSite cookie issue according to the docs: https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1 - The default list of user agents that disallow "SameSite=None", was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. - - to update. - If you don't want to use the default user agent list implementation, - the method sent in this parameter will be run against the user agent and if returned true, SameSite value will be set to Unspecified. - The default user agent list used can be found at: https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. - to chain. - - - - Checks if the specified user agent supports "SameSite=None" cookies. - - Browser user agent. - - Incompatible user agents include: - - Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends). - Versions of UC Browser on Android prior to version 12.13.2. - Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12. - - Reference: https://www.chromium.org/updates/same-site/incompatible-clients. - - True, if the user agent does not allow "SameSite=None" cookie; otherwise, false. - - - - Implementation for the downstream web API. - - - - - Constructor. - - Token acquisition service. - Named options provider. - HTTP client. - Configuration options. - - - - - - - - - - - - - Merge the options from configuration and override from caller. - - Named configuration. - Delegate to override the configuration. - - - - Extension methods to support downstream web API services. - - - - - Adds a named downstream web API service related to a specific configuration section. - - Builder. - Name of the configuration for the service. - This is the name used when calling the service from controller/pages. - Configuration. - The builder for chaining. - - - - Adds a named downstream web API service initialized with delegates. - - Builder. - Name of the configuration for the service. - This is the name which will be used when calling the service from controller/pages. - Action to configure the options. - The builder for chaining. - - - - Extensions for the downstream web API. - - - - - Get a strongly typed response from the web API. - - Output type. - The downstream web API. - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Path to the API endpoint relative to the base URL specified in the configuration. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful in platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - A strongly typed response from the web API. - - - - Calls the web API with an HttpPost, providing strongly typed input and getting - strongly typed output. - - Output type. - Input type. - The downstream web API. - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Path to the API endpoint relative to the base URL specified in the configuration. - Input data sent to the API. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful in platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - A strongly typed response from the web API. - - - - Calls the web API endpoint with an HttpPut, providing strongly typed input data. - - Input type. - The downstream web API. - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Path to the API endpoint relative to the base URL specified in the configuration. - Input data sent to the API. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful in platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - The value returned by the downstream web API. - - - - Calls the web API endpoint with an HttpPut, provinding strongly typed input data - and getting back strongly typed data. - - Output type. - Input type. - The downstream web API. - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Path to the API endpoint relative to the base URL specified in the configuration. - Input data sent to the API. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful in platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - A strongly typed response from the web API. - - - - Call a web API endpoint with an HttpGet, - and return strongly typed data. - - Output type. - The downstream web API. - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful in platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - The value returned by the downstream web API. - - - - Call a web API with a strongly typed input, with an HttpGet. - - Input type. - The downstream web API. - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Input data. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful in platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - The value returned by the downstream web API. - - - - Options passed-in to call downstream web APIs. To call Microsoft Graph, see rather - MicrosoftGraphOptions in the Microsoft.Identity.Web.MicrosoftGraph assembly. - - - - - Base URL for the called downstream web API. For instance "https://graph.microsoft.com/beta/".. - - - - - Path relative to the (for instance "me"). - - - - - Space separated scopes required to call the downstream web API. - For instance "user.read mail.read". - - - - - [Optional] tenant ID. This is used for specific scenarios where - the application needs to call a downstream web API on behalf of a user in several tenants. - It would mostly be used from code, not from the configuration. - - - - - [Optional]. User flow (in the case of a B2C downstream web API). If not - specified, the B2C downstream web API will be called with the default user flow from - . - - - - - HTTP method used to call this downstream web API (by default Get). - - - - - Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), - rather than a Bearer token. - PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, - which MSAL can manage. See https://aka.ms/msal-net-pop. - Set to true to enable PoP tokens automatically. - - - - - Options passed-in to create the token acquisition object which calls into MSAL .NET. - - - - - Clone the options (to be able to override them). - - A clone of the options. - - - - Return the downstream web API URL. - - URL of the downstream web API. - - - - Returns the scopes. - - Scopes. - - - - Interface used to call a downstream web API, for instance from controllers. - - - - - Calls the downstream web API for the user, based on a description of the - downstream web API in the configuration. - - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful on platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - HTTP context in the case where is - , , . - An that the application will process. - - - - Calls a downstream web API consuming JSON with some data and returns data. - - Input type. - Output type. - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Input parameter to the downstream web API. - Overrides the options proposed in the configuration described - by . - [Optional] Claims representing a user. This is useful in platforms like Blazor - or Azure Signal R, where the HttpContext is not available. In other platforms, the library - will find the user from the HttpContext. - The value returned by the downstream web API. - - A list method that returns an IEnumerable<MyItem>>. - - public async Task<IEnumerable<MyItem>> GetAsync() - { - return await _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( - ServiceName, - null, - options => - { - options.RelativePath = $"api/todolist"; - }); - } - - - Example of editing. - - public async Task<MyItem> EditAsync(MyItem myItem) - { - return await _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( - ServiceName, - nyItem, - options => - { - options.HttpMethod = HttpMethod.Patch; - options.RelativePath = $"api/todolist/{myItem.Id}"; - }); - } - - - - - - Calls the downstream web API for the app, with the required scopes. - - Name of the service describing the downstream web API. There can - be several configuration named sections mapped to a , - each for one downstream web API. You can pass-in null, but in that case - needs to be set. - Overrides the options proposed in the configuration described - by . - HTTP content in the case where is - , , . - An that the application will process. - - - - Extension methods. - - - - Determines whether the specified string collection contains any. - The search for. - The string collection. - - true if the specified string collection contains any; otherwise, false. - - - - Keep the validated token associated with the HTTP request. - - HTTP context. - Token to preserve after the token is validated so that - it can be used in the actions. - - - - Get the parsed information about the token used to call the web API. - - HTTP context associated with the current request. - used to call the web API. - - - - Provides access to get or set the current error status. - The default implementation will use TempData and be enabled when run under Development. - - - - - Gets the error message for the current request. - - Current . - The current error message if available. - - - - Sets the error message for the current request. - - Current . - Error message to set. - - - - Gets whether error messages should be displayed. - - - - - Helper methods to handle incremental consent and conditional access in - a web app. - - - - - Can the exception be solved by re-signing-in the user?. - - Exception from which the decision will be made. - Returns true if the issue can be solved by signing-in - the user, and false, otherwise. - - - - Build authentication properties needed for incremental consent. - - Scopes to request. - instance. - User. - Userflow being invoked for AAD B2C. - AuthenticationProperties. - - - - An implementation of IConfigurationRetriever geared towards Azure AD issuers metadata. - - - - Retrieves a populated configuration given an address and an . - Address of the discovery document. - The to use to read the discovery document. - A cancellation token that can be used by other objects or threads to receive notice of cancellation. . - - A that, when completed, returns from the configuration. - - address - Azure AD Issuer metadata address URL is required - or retriever - No metadata document retriever is provided. - - - - Model class to hold information parsed from the Azure AD issuer endpoint. - - - - - Tenant discovery endpoint. - - - - - API Version. - - - - - List of metadata associated with the endpoint. - - - - - Model child class to hold alias information parsed from the Azure AD issuer endpoint. - - - - - Preferred alias. - - - - - Preferred alias to cache tokens emitted by one of the aliases (to avoid - SSO islands). - - - - - Aliases of issuer URLs which are equivalent. - - - - - Interface for the token acquisition service (encapsulating MSAL.NET). - - - - - Typically used from an ASP.NET Core web app or web API controller, this method gets an access token - for a downstream API on behalf of the user account which claims are provided in the - member of the controller's parameter. - - Scopes to request for the downstream API to call. - Enables to override the tenant/account for the same identity. This is useful in the - cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant. - Azure AD B2C UserFlow to target. - Optional claims principal representing the user. If not provided, will use the signed-in - user (in a web app), or the user for which the token was received (in a web API) - cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - An access token to call on behalf of the user, the downstream API characterized by its scopes. - - - - Typically used from an ASP.NET Core web app or web API controller, this method gets an access token - for a downstream API on behalf of the user account which claims are provided in the - member of the controller's parameter. - - Scopes to request for the downstream API to call. - Enables to override the tenant/account for the same identity. This is useful in the - cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - Azure AD B2C UserFlow to target. - Optional claims principal representing the user. If not provided, will use the signed-in - user (in a web app), or the user for which the token was received (in a web API) - cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - An to call on behalf of the user, the downstream API characterized by its scopes. - - - - Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) - using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - - The scope requested to access a protected API. For this flow (client credentials), the scope - should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - in the portal, cannot be overridden in the application, as you can request a token for only one resource at a time (use - several calls to get tokens for other resources). - Enables overriding of the tenant/account for the same identity. This is useful in the - cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - An access token for the app itself, based on its scopes. - - - - Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) - using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - - The scope requested to access a protected API. For this flow (client credentials), the scope - should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use - several calls to get tokens for other resources). - Enables overriding of the tenant/account for the same identity. This is useful - for multi tenant apps or daemons. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - An authentication result for the app itself, based on its scopes. - - - - Used in web APIs (which therefore cannot have an interaction with the user). - Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that - the client can trigger an interaction with the user so the user can consent to more scopes. - - Scopes to consent to. - triggering the challenge. - The to update. - A representing the asynchronous operation. - - - - Interface for the internal operations of token acquisition service (encapsulating MSAL.NET). - - - - - In a web app, adds, to the MSAL.NET cache, the account of the user authenticating to the web app, when the authorization code is received (after the user - signed-in and consented) - An On-behalf-of token contained in the is added to the cache, so that it can then be used to acquire another token on-behalf-of the - same user in order to call to downstream APIs. - - The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. - Scopes to request. - A that represents a completed add to cache operation. - - From the configuration of the Authentication of the ASP.NET Core web API: - OpenIdConnectOptions options; - - Subscribe to the authorization code received event: - - options.Events = new OpenIdConnectEvents(); - options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; - } - - - And then in the OnAuthorizationCodeRecieved method, call : - - private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) - { - var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); - await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); - } - - - - - - Removes the account associated with context.HttpContext.User from the MSAL.NET cache. - - RedirectContext passed-in to a - OpenID Connect event. - A that represents a completed remove from cache operation. - - - - Base class for web app and web API Microsoft Identity authentication - builders. - - - - - Constructor. - - The services being configured. - Optional configuration section. - - - - The services being configured. - - - - - Configuration section from which to bind options. - - It can be null if the configuration happens with delegates - rather than configuration. - - - - Extensions for IServerSideBlazorBuilder for startup initialization of web APIs. - - - - - Add the incremental consent and conditional access handler for Blazor - server side pages. - - Service side blazor builder. - The builder. - - - - Add the incremental consent and conditional access handler for - web app pages, Razor pages, controllers, views, etc... - - Service collection. - The service collection. - - - - Handler for Blazor specific APIs to handle incremental consent - and conditional access. - - - - - Initializes a new instance of the class. - - Service provider to get the HttpContextAccessor for the current HttpContext, when available. - - - - Boolean to determine if server is Blazor. - - - - - Current user. - - - - - Base URI to use in forming the redirect. - - - - - For Blazor/Razor pages to process the exception from - a user challenge. - - Exception. - - - - Forces the user to consent to specific scopes and perform - Conditional Access to get specific claims. Use on a Razor/Blazor - page or controller to proactively ensure the scopes and/or claims - before acquiring a token. The other mechanism - ensures claims and scopes requested by Azure AD after a failed token acquisition attempt. - See https://aka.ms/ms-id-web/ca_incremental-consent for details. - - Scopes to request. - Claims to ensure. - Userflow being invoked for AAD B2C. - - - - Options for configuring authentication using Azure Active Directory. It has both AAD and B2C configuration attributes. - - - - - Gets or sets the Azure Active Directory instance, e.g. "https://login.microsoftonline.com". - - - - - Gets or sets the tenant ID. - - - - - Gets or sets the domain of the Azure Active Directory tenant, e.g. contoso.onmicrosoft.com. - - - - - Gets or sets the edit profile user flow name for B2C, e.g. b2c_1_edit_profile. - - - - - Gets or sets the sign up or sign in user flow name for B2C, e.g. b2c_1_susi. - - - - - Gets or sets the reset password user flow name for B2C, e.g. B2C_1_password_reset. - - - - - Gets the default user flow (which is signUpsignIn). - - - - - Enables legacy ADAL cache serialization and deserialization. - Performance improvements when working with MSAL only apps. - Set to true if you have a shared cache with ADAL apps. - - The default is false. - - - - Is considered B2C if the attribute SignUpSignInPolicyId is defined. - - - - - Is considered to have client credentials if the attribute ClientCertificates - or ClientSecret is defined. - - - - - Description of the certificates used to prove the identity of the web app or web API. - For the moment only the first certificate is considered. - - An example in the appsetting.json: - - "ClientCertificates": [ - { - "SourceType": "StoreWithDistinguishedName", - "CertificateStorePath": "CurrentUser/My", - "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" - } - ] - - See also https://aka.ms/ms-id-web-certificates. - - - - - Description of the certificates used to decrypt an encrypted token in a web API. - For the moment only the first certificate is considered. - - An example in the appsetting.json: - - "TokenDecryptionCertificates": [ - { - "SourceType": "StoreWithDistinguishedName", - "CertificateStorePath": "CurrentUser/My", - "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" - } - ] - - See also https://aka.ms/ms-id-web-certificates. - - - - - Specifies if the x5c claim (public key of the certificate) should be sent to the STS. - Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: - this method will send the public certificate to Azure AD along with the token request, - so that Azure AD can use it to validate the subject name based on a trusted issuer policy. - This saves the application admin from the need to explicitly manage the certificate rollover - (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni. - - The default is false. - - - - Daemon applications can validate a token based on roles, or using the ACL-based authorization - pattern to control tokens without a roles claim. If using ACL-based authorization, - Microsoft Identity Web will not throw if roles or scopes are not in the Claims. - For details see https://aka.ms/ms-identity-web/daemon-ACL. - - The default is false. - - - - Used, when deployed to Azure, to specify explicitly a user assigned managed identity. - See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. - - - - - Microsoft Identity Web specific exception class for - use in Blazor or Razor pages to process the user challenge. - Handles the . - - - - - Exception thrown by MSAL when a user challenge is encountered. - - - - - Scopes to request. - - - - - Specified userflow. - - - - - Handles the user challenge for Blazor or Razor pages. - - Exception thrown by MSAL when a user challenge is encountered. - Scopes to request. - Userflow used in B2C. - - - - Generic class that validates token issuer from the provided Azure AD authority. - - - - - A list of all Issuers across the various Azure AD instances. - - - - - Validate the issuer for multi-tenant applications of various audiences (Work and School accounts, or Work and School accounts + - Personal accounts). - - Issuer to validate (will be tenanted). - Received security token. - Token validation parameters. - The issuer is considered as valid if it has the same HTTP scheme and authority as the - authority from the configuration file, has a tenant ID, and optionally v2.0 (this web API - accepts both V1 and V2 tokens). - Authority aliasing is also taken into account. - The issuer if it's valid, or otherwise SecurityTokenInvalidIssuerException is thrown. - if is null. - if is null. - if the issuer is invalid. - - - Gets the tenant ID from a token. - A JWT token. - A string containing the tenant ID, if found or . - Only and are acceptable types. - - - - This method is now Obsolete. - - Aad authority. - NotImplementedException. - - - - Interface implemented by diagnostics for the JWT Bearer middleware. - - - - - Called to subscribe to . - - JWT Bearer events. - The events (for chaining). - - - - Diagnostics used in the OpenID Connect middleware - (used in web apps). - - - - - Method to subscribe to . - - OpenID Connect events. - - - - Diagnostics for the JwtBearer middleware (used in web APIs). - - - - - Constructor for a . This constructor - is used by dependency injection. - - Logger. - - - - Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. - - - - - Invoked when a protocol message is first received. - - - - - Invoked after the security token has passed validation and a ClaimsIdentity has been generated. - - - - - Invoked before a challenge is sent back to the caller. - - - - - Subscribes to all the JwtBearer events, to help debugging, while - preserving the previous handlers (which are called). - - Events to subscribe to. - for chaining. - - - - Factory class for creating the IssuerValidator per authority. - - - - - Initializes a new instance of the class. - - Options passed-in to create the AadIssuerValidator object. - HttpClientFactory. - - - - Gets an for an authority. - - The authority to create the validator for, e.g. https://login.microsoftonline.com/. - A for the aadAuthority. - if is null or empty. - - - - Diagnostics used in the OpenID Connect middleware - (used in web apps). - - - - - Constructor of the , used - by dependency injection. - - Logger used to log the diagnostics. - - - - Invoked before redirecting to the identity provider to authenticate. This can - be used to set ProtocolMessage.State that will be persisted through the authentication - process. The ProtocolMessage can also be used to add or customize parameters - sent to the identity provider. - - - - - Invoked when a protocol message is first received. - - - - - Invoked after security token validation if an authorization code is present - in the protocol message. - - - - - Invoked after "authorization code" is redeemed for tokens at the token endpoint. - - - - - Invoked when an IdToken has been validated and produced an AuthenticationTicket. - - - - - Invoked when user information is retrieved from the UserInfoEndpoint. - - - - - Invoked if exceptions are thrown during request processing. The exceptions will - be re-thrown after this event unless suppressed. - - - - - Invoked when a request is received on the RemoteSignOutPath. - - - - - Invoked before redirecting to the identity provider to sign out. - - - - - Invoked before redirecting to the Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutRedirectUri - at the end of a remote sign-out flow. - - - - - Subscribes to all the OpenIdConnect events, to help debugging, while - preserving the previous handlers (which are called). - - Events to subscribe to. - - - - Generic class that registers the token audience from the provided Azure AD authority. - - - - - Default validation of the audience: - - when registering an Azure AD web API in the app registration portal (and adding a scope) - the default App ID URI generated by the portal is api://{clientID} - - However, the audience (aud) of the token acquired to access this web API is different depending - on the "accepted access token version" for the web API: - - if accepted token version is 1.0, the audience provided in the token - by the Microsoft identity platform (formerly Azure AD v2.0) endpoint is: api://{ClientID} - - if the accepted token version is 2.0, the audience provided by Azure AD v2.0 in the token - is {CliendID} - When getting an access token for an Azure AD B2C web API the audience in the token is - api://{ClientID}. - - When web API developers don't provide the "Audience" in the configuration, Microsoft.Identity.Web - considers that this is the default App ID URI as explained above. When developer provides the - "Audience" member, it's available in the TokenValidationParameter.ValidAudience. - - Audiences in the security token. - Security token from which to validate the audiences. - Token validation parameters. - True if the token is valid; false, otherwise. - - - - This attribute is used on a controller, pages, or controller actions - to declare (and validate) the scopes required by a web API. These scopes can be declared - in two ways: hardcoding them, or declaring them in the configuration. Depending on your - choice, use either one or the other of the constructors. - For details, see https://aka.ms/ms-id-web/required-scope-attribute. - - - - - Fully qualified name of the configuration key containing the required scopes (separated - by spaces). - - - If the appsettings.json file contains a section named "AzureAd", in which - a property named "Scopes" contains the required scopes, the attribute on the - controller/page/action to protect should be set to the following: - - [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] - - - - - - Verifies that the web API is called with the right scopes. - If the token obtained for this API is on behalf of the authenticated user does not have - any of these in its scope claim, the - method updates the HTTP response providing a status code 403 (Forbidden) - and writes to the response body a message telling which scopes are expected in the token. - - Scopes accepted by this web API. - When the scopes don't match, the response is a 403 (Forbidden), - because the user is authenticated (hence not 401), but not authorized. - - Add the following attribute on the controller/page/action to protect: - - - [RequiredScope("access_as_user")] - - - and - if you want to express the required scopes from the configuration. - - - - Default constructor, to be used along with the - property when you want to get the scopes to validate from the configuration, instead - of hardcoding them in the code. - - - - - If the authenticated user does not have any of these , the - method updates the HTTP response providing a status code 403 (Forbidden) - and writes to the response body a message telling which scopes are expected in the token. - - Scopes accepted by this web API. - When the scopes don't match, the response is a 403 (Forbidden), - because the user is authenticated (hence not 401), but not authorized. - - - - Extension class providing the extension methods for that - can be used in web APIs to validate the roles in controller actions. - - - - - When applied to an , verifies that the application - has the expected roles. - - HttpContext (from the controller). - Roles accepted by this web API. - When the roles don't match, the response is a 403 (Forbidden), - because the app does not have the expected roles. - - - - Extension class providing the extension - methods for that - can be used in web APIs to validate scopes in controller actions. - We recommend using instead the RequiredScope Attribute on the controller, the page or the action. - See https://aka.ms/ms-id-web/required-scope-attribute. - - - - - When applied to an , verifies that the user authenticated in the - web API has any of the accepted scopes. - If there is no authenticated user, the response is a 401 (Unauthenticated). - If the authenticated user does not have any of these , the - method updates the HTTP response providing a status code 403 (Forbidden) - and writes to the response body a message telling which scopes are expected in the token. - We recommend using instead the RequiredScope Attribute on the controller, the page or the action. - See https://aka.ms/ms-id-web/required-scope-attribute. - - HttpContext (from the controller). - Scopes accepted by this web API. - - - - Options passed-in to create the AadIssuerValidator object. - - - - - Sets the name of the HttpClient to get from the IHttpClientFactory for use with the configuration manager. - Needed when customizing the client such as configuring a proxy. - - - - - Extensions for IServiceCollection for startup initialization of web APIs. - - - - - Add the token acquisition service. - - Service collection. - Specifies if an instance of should be a singleton. - The service collection. - - This method is typically called from the ConfigureServices(IServiceCollection services) in Startup.cs. - Note that the implementation of the token cache can be chosen separately. - - - // Token acquisition service and its cache implementation as a session cache - services.AddTokenAcquisition() - .AddDistributedMemoryCache() - .AddSession() - .AddSessionBasedTokenCache(); - - - - - - An implementation of that uses to track error messages. - - - - - Token acquisition service. - - - - - Constructor of the TokenAcquisition service. This requires the Azure AD Options to - configure the confidential client application and a token cache provider. - This constructor is called by ASP.NET Core dependency injection. - - The App token cache provider. - Access to the HttpContext of the request. - Configuration options. - MSAL.NET configuration options. - HTTP client factory. - Logger. - Service provider. - - - - Scopes which are already requested by MSAL.NET. They should not be re-requested;. - - - - - Meta-tenant identifiers which are not allowed in client credentials. - - - - - This handler is executed after the authorization code is received (once the user signs-in and consents) during the - authorization code flow in a web app. - It uses the code to request an access token from the Microsoft identity platform and caches the tokens and an entry about the signed-in user's account in the MSAL's token cache. - The access token (and refresh token) provided in the , once added to the cache, are then used to acquire more tokens using the - on-behalf-of flow for the signed-in user's account, - in order to call to downstream APIs. - - The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. - scopes to request access to. - - From the configuration of the Authentication of the ASP.NET Core web API: - OpenIdConnectOptions options; - - Subscribe to the authorization code received event: - - options.Events = new OpenIdConnectEvents(); - options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; - } - - - And then in the OnAuthorizationCodeRecieved method, call : - - private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) - { - var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); - await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); - } - - - - - - Typically used from a web app or web API controller, this method retrieves an access token - for a downstream API using; - 1) the token cache (for web apps and web APIs) if a token exists in the cache - 2) or the on-behalf-of flow - in web APIs, for the user account that is ascertained from claims provided in the - instance of the current HttpContext. - - Scopes to request for the downstream API to call. - Enables overriding of the tenant/account for the same identity. This is useful in the - cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. - Azure AD B2C user flow to target. - Optional claims principal representing the user. If not provided, will use the signed-in - user (in a web app), or the user for which the token was received (in a web API) - cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. - Options passed-in to create the token acquisition options object which calls into MSAL .NET. - An access token to call the downstream API and populated with this downstream API's scopes. - Calling this method from a web API supposes that you have previously called, - in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method - passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that - you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by - OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. - - - - Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) - using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - - The scope requested to access a protected API. For this flow (client credentials), the scope - should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use - several calls to get tokens for other resources). - Enables overriding of the tenant/account for the same identity. This is useful - for multi tenant apps or daemons. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - An authentication result for the app itself, based on its scopes. - - - - Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) - using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - - The scope requested to access a protected API. For this flow (client credentials), the scope - should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use - several calls to get tokens for other resources). - Enables overriding of the tenant/account for the same identity. This is useful - for multi tenant apps or daemons. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - An access token for the app itself, based on its scopes. - - - - Typically used from a web app or web API controller, this method retrieves an access token - for a downstream API using; - 1) the token cache (for web apps and web APIs) if a token exists in the cache - 2) or the on-behalf-of flow - in web APIs, for the user account that is ascertained from the claims provided in the - instance of the current HttpContext. - - Scopes to request for the downstream API to call. - Enables overriding of the tenant/account for the same identity. This is useful in the - cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - Azure AD B2C user flow to target. - Optional claims principal representing the user. If not provided, will use the signed-in - user (in a web app), or the user for which the token was received (in a web API) - cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - An access token to call the downstream API and populated with this downstream API's scopes. - Calling this method from a web API supposes that you have previously called, - in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method - passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that - you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by - OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. - - - - Used in web APIs (no user interaction). - Replies to the client through the HTTP response by sending a 403 (forbidden) and populating the 'WWW-Authenticate' header so that - the client, in turn, can trigger a user interaction so that the user consents to more scopes. - - Scopes to consent to. - The that triggered the challenge. - The to update. - A representing the asynchronous operation. - - - - Removes the account associated with context.HttpContext.User from the MSAL.NET cache. - - RedirectContext passed-in to a - OpenID Connect event. - A that represents a completed account removal operation. - - - - Creates an MSAL confidential client application, if needed. - - - - - Creates an MSAL confidential client application. - - - - - Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal. - - . - Claims principal for the user on behalf of whom to get a token. - Scopes for the downstream API to call. - (optional) Authority based on a specific tenant for which to acquire a token to access the scopes - on behalf of the user described in the claimsPrincipal. - Azure AD B2C user flow to target. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - - - - Gets an access token for a downstream API on behalf of the user whose account is passed as an argument. - - . - User IAccount for which to acquire a token. - See . - Scopes for the downstream API to call. - Authority based on a specific tenant for which to acquire a token to access the scopes - on behalf of the user. - Azure AD B2C user flow. - Options passed-in to create the token acquisition object which calls into MSAL .NET. - - - - Options passed-in to create the token acquisition object which calls into MSAL .NET. - - - - - Sets the correlation id to be used in the authentication request - to the /token endpoint. - - - - - Sets Extra Query Parameters for the query string in the HTTP authentication request. - - - - - A string with one or multiple claims to request. - Normally used with Conditional Access. - - - - - Specifies if the token request will ignore the access token in the token cache - and will attempt to acquire a new access token. - If true, the request will ignore the token cache. The default is false. - Use this option with care and only when needed, for instance, if you know that conditional access policies have changed, - for it induces performance degradation, as the token cache is not utilized. - - - - - Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), - rather than a Bearer token. - PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, - which MSAL can manage. See https://aka.ms/msal-net-pop. - - - - - Clone the options (to be able to override them). - - A clone of the options. - - - - Extension class used to add distributed token cache serializer to MSAL. - See https://aka.ms/ms-id-web/token-cache-serialization for details. - - - - Adds the .NET Core distributed cache based app token cache to the service collection. - The services collection to add to. - A to chain. - - - - An implementation of the token cache for both Confidential and Public clients backed by a Distributed Cache. - The Distributed Cache (L2), by default creates a Memory Cache (L1), for faster look up, resulting in a two level cache. - - https://aka.ms/msal-net-token-cache-serialization - - - - .NET Core Memory cache. - - - - - MSAL distributed token cache options. - - - - - Initializes a new instance of the class. - - Distributed cache instance to use. - Options for the token cache. - MsalDistributedTokenCacheAdapter logger. - - - - Removes a specific token cache, described by its cache key - from the distributed cache. - - Key of the cache to remove. - A that completes when key removal has completed. - - - - Read a specific token cache, described by its cache key, from the - distributed cache. - - Key of the cache item to retrieve. - Read blob representing a token cache for the cache key - (account or app). - - - - Writes a token cache blob to the serialization cache (by key). - - Cache key. - blob to write. - A that completes when a write operation has completed. - - - - Options for the MSAL token cache serialization adapter, - which delegates the serialization to the IDistributedCache implementations - available with .NET Core. - - - - - Options of the In Memory (L1) cache. - - - - - Callback offered to the app to be notified when the L2 cache fails. - This way the app is given the possibility to act on the L2 cache, - for instance, in the case of Redis, to reconnect. This is left to the application as it's - the only one that knows about the real implementation of the L2 cache. - The handler should return true if the cache should try again the operation, and - false otherwise. When true is passed and the retry fails, an exception - will be thrown. - - - - - Value more than 0, less than 1, to set the In Memory (L1) cache - expiration time values relative to the Distributed (L2) cache. - Default is 1. - - - - - MSAL token cache provider interface. - - - - - Initializes a token cache (which can be a user token cache or an app token cache). - - Token cache for which to initialize the serialization. - A that represents a completed initialization operation. - - - - Clear the user token cache. - - HomeAccountId for a user account in the cache. - A that represents a completed clear operation. - - - - Extension class used to add an in-memory token cache serializer to MSAL. - - - - Adds both the app and per-user in-memory token caches. - The services collection to add to. - the services (for chaining). - - - - MSAL's in-memory token cache options. - - - - Initializes a new instance of the class. - By default, the sliding expiration is set for 14 days. - - - - Gets or sets the value of the duration after which the cache entry will expire unless it's used - This is the duration the tokens are kept in memory cache. - In production, a higher value, up-to 90 days is recommended. - - - The AbsoluteExpirationRelativeToNow value. - - - - - An implementation of token cache for both Confidential and Public clients backed by MemoryCache. - - https://aka.ms/msal-net-token-cache-serialization - - - - .NET Core Memory cache. - - - - - MSAL memory token cache options. - - - - - Constructor. - - serialization cache. - Memory cache options. - - - - Removes a token cache identified by its key, from the serialization - cache. - - token cache key. - A that completes when key removal has completed. - - - - Reads a blob from the serialization cache (identified by its key). - - Token cache key. - Read Bytes. - - - - Writes a token cache blob to the serialization cache (identified by its key). - - Token cache key. - Bytes to write. - A that completes when a write operation has completed. - - - - Token cache provider with default implementation. - - - - - - Initializes the token cache serialization. - - Token cache to serialize/deserialize. - A that represents a completed initialization operation. - - - - Raised AFTER MSAL added the new token in its in-memory copy of the cache. - This notification is called every time MSAL accesses the cache, not just when a write takes place: - If MSAL's current operation resulted in a cache change, the property TokenCacheNotificationArgs.HasStateChanged will be set to true. - If that is the case, we call the TokenCache.SerializeMsalV3() to get a binary blob representing the latest cache content – and persist it. - - Contains parameters used by the MSAL call accessing the cache. - - - - if you want to ensure that no concurrent write takes place, use this notification to place a lock on the entry. - - Token cache notification arguments. - A that represents a completed operation. - - - - Clear the cache. - - HomeAccountId for a user account in the cache. - A that represents a completed clear operation. - - - - Method to be implemented by concrete cache serializers to write the cache bytes. - - Cache key. - Bytes to write. - A that represents a completed write operation. - - - - Method to be implemented by concrete cache serializers to Read the cache bytes. - - Cache key. - Read bytes. - - - - Method to be implemented by concrete cache serializers to remove an entry from the cache. - - Cache key. - A that represents a completed remove key operation. - - - - An implementation of token cache for confidential clients backed by an HTTP session. - - - For this session cache to work effectively, the ASP.NET Core session has to be configured properly. - The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state - - In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: - - services.AddSession(option => - { - option.Cookie.IsEssential = true; - }); - - In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: - - app.UseSession(); // Before UseMvc() - - - https://aka.ms/msal-net-token-cache-serialization - - - - MSAL Token cache provider constructor. - - Session for the current user. - Logger. - - - - Read a blob representing the token cache from its key. - - Key representing the token cache - (account or app). - Read blob. - - - - Writes the token cache identified by its key to the serialization mechanism. - - Key for the cache (account ID or app ID). - Blob to write to the cache. - A that completes when a write operation has completed. - - - - Removes a cache described by its key. - - Key of the token cache (user account or app ID). - A that completes when key removal has completed. - - - - Extension class to add a session token cache serializer to MSAL. - - - - - Adds an HTTP session-based application token cache to the service collection. - - - For this session cache to work effectively the ASP.NET Core session has to be configured properly. - The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. - - In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: - - services.AddSession(option => - { - option.Cookie.IsEssential = true; - }); - - In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: - - app.UseSession(); // Before UseMvc() - - Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). - - The services collection to add to. - The service collection. - - - - Adds an HTTP session-based per-user token cache to the service collection. - - - For this session cache to work effectively the ASP.NET Core session has to be configured properly. - The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. - - In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: - - services.AddSession(option => - { - option.Cookie.IsEssential = true; - }); - - In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: - - app.UseSession(); // Before UseMvc() - - Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). - - The services collection to add to. - The service collection. - - - - Utility methods used by L1/L2 cache. - - - - - Authentication builder for a web API. - - - - - Constructor. - - The services being configured. - Default scheme used for OpenIdConnect. - ACtion called to configure the JwtBearer options. - Action called to configure - the Microsoft identity options. - Configuration section from which to - get parameters. - - - - Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - - The action to configure . - The authentication builder to chain. - - - - Extensions for for startup initialization of web APIs. - - - - - Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - - The to which to add this configuration. - The configuration instance. - The configuration section with the necessary settings to initialize authentication options. - The JWT bearer scheme name to be used. By default it uses "Bearer". - - Set to true if you want to debug, or just understand the JWT bearer events. - - The authentication builder to chain. - - - - Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - - The to which to add this configuration. - The configuration second from which to fill-in the options. - The JWT bearer scheme name to be used. By default it uses "Bearer". - - Set to true if you want to debug, or just understand the JWT bearer events. - - The authentication builder to chain. - - - - Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - - The to which to add this configuration. - The action to configure . - The action to configure the . - The JWT bearer scheme name to be used. By default it uses "Bearer". - - Set to true if you want to debug, or just understand the JWT bearer events. - The authentication builder to chain. - - - - Builder for web API authentication with configuration. - - - - - Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - - The authentication builder to chain. - - - - Extension for IServiceCollection for startup initialization of web APIs. - - - - - Protects the web API with Microsoft identity platform (formerly Azure AD v2.0) - This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - - Service collection to which to add authentication. - The Configuration object. - The configuration section with the necessary settings to initialize authentication options. - The JwtBearer scheme name to be used. By default it uses "Bearer". - - Set to true if you want to debug, or just understand the JwtBearer events. - The authentication builder to chain extension methods. - - - - Authentication builder returned by the EnableTokenAcquisitionToCallDownstreamApi methods - enabling you to decide token cache implementations. - - - - - Add in memory token caches. - - to configure. - to configure. - the service collection. - - - - Add distributed token caches. - - the service collection. - - - - Add session token caches. - - the service collection. - - - - Authentication builder specific for Microsoft identity platform. - - - - - Constructor. - - The services being configured. - Default scheme used for OpenIdConnect. - Action called to configure - the Microsoft identity options. - Optional configuration section. - - - - The web app calls a web API. - - Initial scopes. - The builder itself for chaining. - - - - The web app calls a web API. This override enables you to specify the - ConfidentialClientApplicationOptions (from MSAL.NET) programmatically. - - Action to configure the - MSAL.NET confidential client application options. - Initial scopes. - The builder itself for chaining. - - - - Extensions for the for startup initialization. - - - - - Add authentication to a web app with Microsoft identity platform. - This method expects the configuration file will have a section, named "AzureAd" as default, - with the necessary settings to initialize authentication options. - - The to which to add this configuration. - The configuration instance. - The configuration section with the necessary settings to initialize authentication options. - The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - The cookie-based scheme name to be used. By default it uses "Cookies". - - Set to true if you want to debug, or just understand the OpenID Connect events. - - The builder for chaining. - - - - Add authentication with Microsoft identity platform. - This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - - The to which to add this configuration. - The configuration section from which to get the options. - The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - The cookie-based scheme name to be used. By default it uses "Cookies". - - Set to true if you want to debug, or just understand the OpenID Connect events. - - The authentication builder for chaining. - - - - Add authentication with Microsoft identity platform. - - The to which to add this configuration. - The action to configure . - The action to configure . - The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - The cookie-based scheme name to be used. By default it uses "Cookies". - - Set to true if you want to debug, or just understand the OpenID Connect events. - - The authentication builder for chaining. - - - - Add authentication with Microsoft identity platform. - - The to which to add this configuration. - The action to configure . - The action to configure . - The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - The cookie-based scheme name to be used. By default it uses "Cookies". - - Set to true if you want to debug, or just understand the OpenID Connect events. - - Configuration section. - The authentication builder for chaining. - - - - Add authentication with Microsoft identity platform. - - The to which to add this configuration. - The action to configure . - The action to configure . - The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - The cookie-based scheme name to be used. By default it uses "Cookies". - - Set to true if you want to debug, or just understand the OpenID Connect events. - - The authentication builder for chaining. - - - - Builder for a Microsoft identity web app authentication where configuration is - available for EnableTokenAcquisitionToCallDownstreamApi. - - - - - Constructor. - - The services being configured. - Default scheme used for OpenIdConnect. - Action called to configure - the Microsoft identity options. - Optional configuration section. - - - - Add support for the web app to acquire tokens to call an API. - - Optional initial scopes to request. - The authentication builder for chaining. - - - - Extension for IServiceCollection for startup initialization. - - - - - Add authentication with Microsoft identity platform. - This method expects the configuration file will have a section, (by default named "AzureAd"), with the necessary settings to - initialize the authentication options. - - Service collection to which to add authentication. - The IConfiguration object. - The name of the configuration section with the necessary - settings to initialize authentication options. - Optional name for the open id connect authentication scheme - (by default OpenIdConnectDefaults.AuthenticationScheme). This can be specified when you want to support - several OpenIdConnect identity providers. - Optional name for the cookie authentication scheme - (by default OpenIdConnectDefaults.AuthenticationScheme). - - Set to true if you want to debug, or just understand the OpenIdConnect events. - - The authentication builder to chain extension methods. - - + + Microsoft.Identity.Web + + + + + Extension methods for . + + + + + Creates the from the values found + in an . + + The instance. + A built from . + + + + Extension methods related to App Services authentication (Easy Auth). + + + + + Add authentication with App Services. + + Authentication builder. + The builder, to chain commands. + + + + Default values related to AppServiceAuthentication handler. + + + + + The default value used for AppServiceAuthenticationOptions.AuthenticationScheme. + + + + + App service authentication handler. + + + + + Constructor for the AppServiceAuthenticationHandler. + Note the parameters are required by the base class. + + App service authentication options. + Logger factory. + URL encoder. + System clock. + + + + + + + Information about the App Services configuration on the host. + + + + + Is App Services authentication enabled?. + + + + + Logout URL for App Services Auth web sites. + + + + + ClientID of the App Services Auth web site. + + + + + Client secret of the App Services Auth web site. + + + + + Issuer of the App Services Auth web site. + + + + + Get headers from environment to help debugging App Services authentication. + + + + + Get the ID token from the headers send by App services authentication. + + Headers. + the ID Token. + + + + Get the IDP token from the headers send by App services authentication. + + Headers. + the IDP. + + + + Get the user claims from the headers and environment variables + + Headers + User claims + + + + Options for Azure App Services authentication. + + + + + Implementation of ITokenAcquisition for App Services authentication (EasyAuth). + + + + + Constructor of the AppServicesAuthenticationTokenAcquisition. + + The App token cache provider. + Access to the HttpContext of the request. + HTTP client factory. + + + + + + + + + + + + + + + + + + + Filter used on a controller action to trigger incremental consent. + + + The following controller action will trigger. + + [AuthorizeForScopes(Scopes = new[] {"Mail.Send"})] + public async Task<IActionResult> SendEmail() + { + } + + + + + + Scopes to request. + + + + + Key section on the configuration file that holds the scope value. + + + + + Azure AD B2C user flow. + + + + + Allows specifying an AuthenticationScheme if OpenIdConnect is not the default challenge scheme. + + + + + Handles the . + + Context provided by ASP.NET Core. + + + + Finds an MsalUiRequiredException in one of the inner exceptions. + + Exception from which we look for an MsalUiRequiredException. + The MsalUiRequiredException if there is one, null, otherwise. + + + + Extensions for . + + + + + Enables an Azure Function to act as/expose a protected web API, enabling bearer token authentication. Calling this method from your Azure function validates the token and exposes the identity of the user or app on behalf of which your function is called, in the HttpContext.User member, where your function can make use of it. + + The current HTTP Context, such as req.HttpContext. + A task indicating success or failure. In case of failure . + + + + Description of a certificate. + + + + + Creates a certificate description from a certificate (by code). + + Certificate. + A certificate description. + + + + Creates a certificate description from Key Vault. + + The Key Vault URL. + The name of the certificate in Key Vault. + A certificate description. + + + + Creates a certificate description from a Base64 encoded value. + + Base64 encoded certificate value. + A certificate description. + + + + Creates a certificate description from path on disk. + + Path where to find the certificate file. + Certificate password. + A certificate description. + + + + Creates a certificate description from a thumbprint and store location (Certificate Manager on Windows, for instance). + + Certificate thumbprint. + Store location where to find the certificate. + Store name where to find the certificate. + A certificate description. + + + + Creates a certificate description from a certificate distinguished name (such as CN=name) + and store location (Certificate Manager on Windows, for instance). + + Certificate distinguished named. + Store location where to find the certificate. + Store name where to find the certificate. + A certificate description. + + + + Type of the source of the certificate. + + + + + Container in which to find the certificate. + + If equals , then + the container is the Key Vault base URL. + If equals , then + this value is not used. + If equals , then + this value is the path on disk where to find the certificate. + If equals , + or , then + this value is the path to the certificate in the cert store, for instance CurrentUser/My. + + + + + + URL of the Key Vault, for instance https://msidentitywebsamples.vault.azure.net. + + + + + Certificate store path, for instance "CurrentUser/My". + + This property should only be used in conjunction with DistinguishedName or Thumbprint. + + + + Certificate distinguished name. + + + + + Name of the certificate in Key Vault. + + + + + Certificate thumbprint. + + + + + Path on disk to the certificate. + + + + + Path on disk to the certificate password. + + + + + Base64 encoded certificate value. + + + + + Defines where and how to import the private key of an X.509 certificate. + + + + + Reference to the certificate or value. + + + If equals , then + the reference is the name of the certificate in Key Vault (maybe the version?). + If equals , then + this value is the base 64 encoded certificate itself. + If equals , then + this value is the password to access the certificate (if needed). + If equals , + this value is the distinguished name. + If equals , + this value is the thumbprint. + + + + + The certificate, either provided directly in code + or loaded from the description. + + + + + Source for a certificate. + + + + + Certificate itself. + + + + + From an Azure Key Vault. + + + + + Base64 encoded string directly from the configuration. + + + + + From local path on disk. + + + + + From the certificate store, described by its thumbprint. + + + + + From the certificate store, described by its distinguished name. + + + + + Certificate Loader. + Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. + For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. + + IConfidentialClientApplication app; + ICertificateLoader certificateLoader = new DefaultCertificateLoader(); + certificateLoader.LoadIfNeeded(config.CertificateDescription); + + app = ConfidentialClientApplicationBuilder.Create(config.ClientId) + .WithCertificate(config.CertificateDescription.Certificate) + .WithAuthority(new Uri(config.Authority)) + .Build(); + + + + + + User assigned managed identity client ID (as opposed to system assigned managed identity) + See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. + + + + + Load the certificate from the description, if needed. + + Description of the certificate. + + + + Load a certificate from Key Vault, including the private key. + + URL of Key Vault. + Name of the certificate. + Defines where and how to import the private key of an X.509 certificate. + An certificate. + This code is inspired by Heath Stewart's code in: + https://github.com/heaths/azsdk-sample-getcert/blob/master/Program.cs#L46-L82. + + + + + Find a certificate by criteria. + + + + + Interface to implement loading of a certificate. + Only use when loading a certificate from a daemon application, or an ASP NET app, using MSAL .NET directly. + For an ASP NET Core app, Microsoft Identity Web will handle the certificate loading for you. + + IConfidentialClientApplication app; + ICertificateLoader certificateLoader = new DefaultCertificateLoader(); + certificateLoader.LoadIfNeeded(config.CertificateDescription); + + app = ConfidentialClientApplicationBuilder.Create(config.ClientId) + .WithCertificate(config.CertificateDescription.Certificate) + .WithAuthority(new Uri(config.Authority)) + .Build(); + + + + + + Load the certificate from the description, if needed. + + Description of the certificate. + + + + Extensions for . + + + + + Gets the account identifier for an MSAL.NET account from a . + + Claims principal. + A string corresponding to an account identifier as defined in . + + + + Gets the unique object ID associated with the . + + The from which to retrieve the unique object ID. + This method returns the object ID both in case the developer has enabled or not claims mapping. + Unique object ID of the identity, or null if it cannot be found. + + + + Gets the Tenant ID associated with the . + + The from which to retrieve the tenant ID. + Tenant ID of the identity, or null if it cannot be found. + This method returns the tenant ID both in case the developer has enabled or not claims mapping. + + + + Gets the login-hint associated with a . + + Identity for which to complete the login-hint. + The login hint for the identity, or null if it cannot be found. + + + + Gets the domain-hint associated with an identity. + + Identity for which to compute the domain-hint. + The domain hint for the identity, or null if it cannot be found. + + + + Get the display name for the signed-in user, from the . + + Claims about the user/account. + A string containing the display name for the user, as determined by Azure AD (v1.0) and Microsoft identity platform (v2.0) tokens, + or null if the claims cannot be found. + See https://docs.microsoft.com/azure/active-directory/develop/id-tokens#payload-claims. + + + + Gets the user flow ID associated with the . + + The from which to retrieve the user flow ID. + User flow ID of the identity, or null if it cannot be found. + + + + Gets the Home Object ID associated with the . + + The from which to retrieve the sub claim. + Home Object ID (sub) of the identity, or null if it cannot be found. + + + + Gets the Home Tenant ID associated with the . + + The from which to retrieve the sub claim. + Home Tenant ID (sub) of the identity, or null if it cannot be found. + + + + Gets the NameIdentifierId associated with the . + + The from which to retrieve the NameIdentifierId claim. + Name identifier ID of the identity, or null if it cannot be found. + + + + Factory class to create objects. + + + + + 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) + { + foreach (var notification in notifications) + { + SubscriptionStore subscription = + subscriptionStore.GetSubscriptionInfo(notification.SubscriptionId); + HttpContext.User = ClaimsPrincipalExtension.FromTenantIdAndObjectId(subscription.TenantId, + subscription.UserId); + string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes); + + + + + + Constants for claim types. + + + + + Name claim: "name". + + + + + Old Object Id claim: http://schemas.microsoft.com/identity/claims/objectidentifier. + + + + + New Object id claim: "oid". + + + + + PreferredUserName: "preferred_username". + + + + + Old TenantId claim: "http://schemas.microsoft.com/identity/claims/tenantid". + + + + + New Tenant Id claim: "tid". + + + + + ClientInfo claim: "client_info". + + + + + UniqueObjectIdentifier: "uid". + Home Object Id. + + + + + UniqueTenantIdentifier: "utid". + Home Tenant Id. + + + + + Older scope claim: "http://schemas.microsoft.com/identity/claims/scope". + + + + + Newer scope claim: "scp". + + + + + New Roles claim = "roles". + + + + + Old Role claim: "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". + + + + + Subject claim: "sub". + + + + + Acr claim: "acr". + + + + + UserFlow claim: "http://schemas.microsoft.com/claims/authnclassreference". + + + + + Tfp claim: "tfp". + + + + + Name Identifier ID claim: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier". + + + + + General constants for Microsoft Identity Web. + + + + + LoginHint. + Represents the preferred_username claim in the ID token. + + + + + DomainHint. + Determined by the tenant Id. + + + + + Claims. + Determined from the signed-in user. + + + + + Bearer. + Predominant type of access token used with OAuth 2.0. + + + + + AzureAd. + Configuration section name for AzureAd. + + + + + AzureAdB2C. + Configuration section name for AzureAdB2C. + + + + + Scope. + + + + + Policy for B2C user flows. + The name of the policy to check against a specific user flow. + + + + + Constants related to the error messages. + + + + + Constants related to the log messages. + + + + + Extension class containing cookie policies (work around for same site). + + + + + Handles SameSite cookie issue according to the https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1. + The default list of user agents that disallow "SameSite=None", + was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + + to update. + to chain. + + + + Handles SameSite cookie issue according to the docs: https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1 + The default list of user agents that disallow "SameSite=None", was taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + + to update. + If you don't want to use the default user agent list implementation, + the method sent in this parameter will be run against the user agent and if returned true, SameSite value will be set to Unspecified. + The default user agent list used can be found at: https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/. + to chain. + + + + Checks if the specified user agent supports "SameSite=None" cookies. + + Browser user agent. + + Incompatible user agents include: + + Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends). + Versions of UC Browser on Android prior to version 12.13.2. + Versions of Safari and embedded browsers on MacOS 10.14 and all browsers on iOS 12. + + Reference: https://www.chromium.org/updates/same-site/incompatible-clients. + + True, if the user agent does not allow "SameSite=None" cookie; otherwise, false. + + + + Implementation for the downstream web API. + + + + + Constructor. + + Token acquisition service. + Named options provider. + HTTP client. + Configuration options. + + + + + + + + + + + + + Merge the options from configuration and override from caller. + + Named configuration. + Delegate to override the configuration. + + + + Extension methods to support downstream web API services. + + + + + Adds a named downstream web API service related to a specific configuration section. + + Builder. + Name of the configuration for the service. + This is the name used when calling the service from controller/pages. + Configuration. + The builder for chaining. + + + + Adds a named downstream web API service initialized with delegates. + + Builder. + Name of the configuration for the service. + This is the name which will be used when calling the service from controller/pages. + Action to configure the options. + The builder for chaining. + + + + Extensions for the downstream web API. + + + + + Get a strongly typed response from the web API. + + Output type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + A strongly typed response from the web API. + + + + Calls the web API with an HttpPost, providing strongly typed input and getting + strongly typed output. + + Output type. + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Input data sent to the API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + A strongly typed response from the web API. + + + + Calls the web API endpoint with an HttpPut, providing strongly typed input data. + + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Input data sent to the API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + The value returned by the downstream web API. + + + + Calls the web API endpoint with an HttpPut, provinding strongly typed input data + and getting back strongly typed data. + + Output type. + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Path to the API endpoint relative to the base URL specified in the configuration. + Input data sent to the API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + A strongly typed response from the web API. + + + + Call a web API endpoint with an HttpGet, + and return strongly typed data. + + Output type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + The value returned by the downstream web API. + + + + Call a web API with a strongly typed input, with an HttpGet. + + Input type. + The downstream web API. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Input data. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + The value returned by the downstream web API. + + + + Options passed-in to call downstream web APIs. To call Microsoft Graph, see rather + MicrosoftGraphOptions in the Microsoft.Identity.Web.MicrosoftGraph assembly. + + + + + Base URL for the called downstream web API. For instance "https://graph.microsoft.com/beta/".. + + + + + Path relative to the (for instance "me"). + + + + + Space separated scopes required to call the downstream web API. + For instance "user.read mail.read". + + + + + [Optional] tenant ID. This is used for specific scenarios where + the application needs to call a downstream web API on behalf of a user in several tenants. + It would mostly be used from code, not from the configuration. + + + + + [Optional]. User flow (in the case of a B2C downstream web API). If not + specified, the B2C downstream web API will be called with the default user flow from + . + + + + + HTTP method used to call this downstream web API (by default Get). + + + + + Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), + rather than a Bearer token. + PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, + which MSAL can manage. See https://aka.ms/msal-net-pop. + Set to true to enable PoP tokens automatically. + + + + + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + + Clone the options (to be able to override them). + + A clone of the options. + + + + Return the downstream web API URL. + + URL of the downstream web API. + + + + Returns the scopes. + + Scopes. + + + + Interface used to call a downstream web API, for instance from controllers. + + + + + Calls the downstream web API for the user, based on a description of the + downstream web API in the configuration. + + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful on platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + HTTP context in the case where is + , , . + An that the application will process. + + + + Calls a downstream web API consuming JSON with some data and returns data. + + Input type. + Output type. + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Input parameter to the downstream web API. + Overrides the options proposed in the configuration described + by . + [Optional] Claims representing a user. This is useful in platforms like Blazor + or Azure Signal R, where the HttpContext is not available. In other platforms, the library + will find the user from the HttpContext. + The value returned by the downstream web API. + + A list method that returns an IEnumerable<MyItem>>. + + public async Task<IEnumerable<MyItem>> GetAsync() + { + return await _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( + ServiceName, + null, + options => + { + options.RelativePath = $"api/todolist"; + }); + } + + + Example of editing. + + public async Task<MyItem> EditAsync(MyItem myItem) + { + return await _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( + ServiceName, + nyItem, + options => + { + options.HttpMethod = HttpMethod.Patch; + options.RelativePath = $"api/todolist/{myItem.Id}"; + }); + } + + + + + + Calls the downstream web API for the app, with the required scopes. + + Name of the service describing the downstream web API. There can + be several configuration named sections mapped to a , + each for one downstream web API. You can pass-in null, but in that case + needs to be set. + Overrides the options proposed in the configuration described + by . + HTTP content in the case where is + , , . + An that the application will process. + + + + Extension methods. + + + + Determines whether the specified string collection contains any. + The search for. + The string collection. + + true if the specified string collection contains any; otherwise, false. + + + + Keep the validated token associated with the HTTP request. + + HTTP context. + Token to preserve after the token is validated so that + it can be used in the actions. + + + + Get the parsed information about the token used to call the web API. + + HTTP context associated with the current request. + used to call the web API. + + + + Provides access to get or set the current error status. + The default implementation will use TempData and be enabled when run under Development. + + + + + Gets the error message for the current request. + + Current . + The current error message if available. + + + + Sets the error message for the current request. + + Current . + Error message to set. + + + + Gets whether error messages should be displayed. + + + + + Helper methods to handle incremental consent and conditional access in + a web app. + + + + + Can the exception be solved by re-signing-in the user?. + + Exception from which the decision will be made. + Returns true if the issue can be solved by signing-in + the user, and false, otherwise. + + + + Build authentication properties needed for incremental consent. + + Scopes to request. + instance. + User. + Userflow being invoked for AAD B2C. + AuthenticationProperties. + + + + An implementation of IConfigurationRetriever geared towards Azure AD issuers metadata. + + + + Retrieves a populated configuration given an address and an . + Address of the discovery document. + The to use to read the discovery document. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. . + + A that, when completed, returns from the configuration. + + address - Azure AD Issuer metadata address URL is required + or retriever - No metadata document retriever is provided. + + + + Model class to hold information parsed from the Azure AD issuer endpoint. + + + + + Tenant discovery endpoint. + + + + + API Version. + + + + + List of metadata associated with the endpoint. + + + + + Model child class to hold alias information parsed from the Azure AD issuer endpoint. + + + + + Preferred alias. + + + + + Preferred alias to cache tokens emitted by one of the aliases (to avoid + SSO islands). + + + + + Aliases of issuer URLs which are equivalent. + + + + + Interface for the token acquisition service (encapsulating MSAL.NET). + + + + + Typically used from an ASP.NET Core web app or web API controller, this method gets an access token + for a downstream API on behalf of the user account which claims are provided in the + member of the controller's parameter. + + Scopes to request for the downstream API to call. + Enables to override the tenant/account for the same identity. This is useful in the + cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C UserFlow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token to call on behalf of the user, the downstream API characterized by its scopes. + + + + Typically used from an ASP.NET Core web app or web API controller, this method gets an access token + for a downstream API on behalf of the user account which claims are provided in the + member of the controller's parameter. + + Scopes to request for the downstream API to call. + Enables to override the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C UserFlow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest in. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An to call on behalf of the user, the downstream API characterized by its scopes. + + + + Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Enables overriding of the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token for the app itself, based on its scopes. + + + + Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Enables overriding of the tenant/account for the same identity. This is useful + for multi tenant apps or daemons. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An authentication result for the app itself, based on its scopes. + + + + Used in web APIs (which therefore cannot have an interaction with the user). + Replies to the client through the HttpResponse by sending a 403 (forbidden) and populating wwwAuthenticateHeaders so that + the client can trigger an interaction with the user so the user can consent to more scopes. + + Scopes to consent to. + triggering the challenge. + The to update. + A representing the asynchronous operation. + + + + Interface for the internal operations of token acquisition service (encapsulating MSAL.NET). + + + + + In a web app, adds, to the MSAL.NET cache, the account of the user authenticating to the web app, when the authorization code is received (after the user + signed-in and consented) + An On-behalf-of token contained in the is added to the cache, so that it can then be used to acquire another token on-behalf-of the + same user in order to call to downstream APIs. + + The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. + Scopes to request. + A that represents a completed add to cache operation. + + From the configuration of the Authentication of the ASP.NET Core web API: + OpenIdConnectOptions options; + + Subscribe to the authorization code received event: + + options.Events = new OpenIdConnectEvents(); + options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; + } + + + And then in the OnAuthorizationCodeRecieved method, call : + + private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + { + var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); + await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); + } + + + + + + Removes the account associated with context.HttpContext.User from the MSAL.NET cache. + + RedirectContext passed-in to a + OpenID Connect event. + A that represents a completed remove from cache operation. + + + + Base class for web app and web API Microsoft Identity authentication + builders. + + + + + Constructor. + + The services being configured. + Optional configuration section. + + + + The services being configured. + + + + + Configuration section from which to bind options. + + It can be null if the configuration happens with delegates + rather than configuration. + + + + Extensions for IServerSideBlazorBuilder for startup initialization of web APIs. + + + + + Add the incremental consent and conditional access handler for Blazor + server side pages. + + Service side blazor builder. + The builder. + + + + Add the incremental consent and conditional access handler for + web app pages, Razor pages, controllers, views, etc... + + Service collection. + The service collection. + + + + Handler for Blazor specific APIs to handle incremental consent + and conditional access. + + + + + Initializes a new instance of the class. + + Service provider to get the HttpContextAccessor for the current HttpContext, when available. + + + + Boolean to determine if server is Blazor. + + + + + Current user. + + + + + Base URI to use in forming the redirect. + + + + + For Blazor/Razor pages to process the exception from + a user challenge. + + Exception. + + + + Forces the user to consent to specific scopes and perform + Conditional Access to get specific claims. Use on a Razor/Blazor + page or controller to proactively ensure the scopes and/or claims + before acquiring a token. The other mechanism + ensures claims and scopes requested by Azure AD after a failed token acquisition attempt. + See https://aka.ms/ms-id-web/ca_incremental-consent for details. + + Scopes to request. + Claims to ensure. + Userflow being invoked for AAD B2C. + + + + Options for configuring authentication using Azure Active Directory. It has both AAD and B2C configuration attributes. + + + + + Gets or sets the Azure Active Directory instance, e.g. "https://login.microsoftonline.com". + + + + + Gets or sets the tenant ID. + + + + + Gets or sets the domain of the Azure Active Directory tenant, e.g. contoso.onmicrosoft.com. + + + + + Gets or sets the edit profile user flow name for B2C, e.g. b2c_1_edit_profile. + + + + + Gets or sets the sign up or sign in user flow name for B2C, e.g. b2c_1_susi. + + + + + Gets or sets the reset password user flow name for B2C, e.g. B2C_1_password_reset. + + + + + Gets the default user flow (which is signUpsignIn). + + + + + Enables legacy ADAL cache serialization and deserialization. + Performance improvements when working with MSAL only apps. + Set to true if you have a shared cache with ADAL apps. + + The default is false. + + + + Is considered B2C if the attribute SignUpSignInPolicyId is defined. + + + + + Is considered to have client credentials if the attribute ClientCertificates + or ClientSecret is defined. + + + + + Description of the certificates used to prove the identity of the web app or web API. + For the moment only the first certificate is considered. + + An example in the appsetting.json: + + "ClientCertificates": [ + { + "SourceType": "StoreWithDistinguishedName", + "CertificateStorePath": "CurrentUser/My", + "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" + } + ] + + See also https://aka.ms/ms-id-web-certificates. + + + + + Description of the certificates used to decrypt an encrypted token in a web API. + For the moment only the first certificate is considered. + + An example in the appsetting.json: + + "TokenDecryptionCertificates": [ + { + "SourceType": "StoreWithDistinguishedName", + "CertificateStorePath": "CurrentUser/My", + "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" + } + ] + + See also https://aka.ms/ms-id-web-certificates. + + + + + Specifies if the x5c claim (public key of the certificate) should be sent to the STS. + Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: + this method will send the public certificate to Azure AD along with the token request, + so that Azure AD can use it to validate the subject name based on a trusted issuer policy. + This saves the application admin from the need to explicitly manage the certificate rollover + (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni. + + The default is false. + + + + Daemon applications can validate a token based on roles, or using the ACL-based authorization + pattern to control tokens without a roles claim. If using ACL-based authorization, + Microsoft Identity Web will not throw if roles or scopes are not in the Claims. + For details see https://aka.ms/ms-identity-web/daemon-ACL. + + The default is false. + + + + Used, when deployed to Azure, to specify explicitly a user assigned managed identity. + See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. + + + + + Microsoft Identity Web specific exception class for + use in Blazor or Razor pages to process the user challenge. + Handles the . + + + + + Exception thrown by MSAL when a user challenge is encountered. + + + + + Scopes to request. + + + + + Specified userflow. + + + + + Handles the user challenge for Blazor or Razor pages. + + Exception thrown by MSAL when a user challenge is encountered. + Scopes to request. + Userflow used in B2C. + + + + Generic class that validates token issuer from the provided Azure AD authority. + + + + + A list of all Issuers across the various Azure AD instances. + + + + + Validate the issuer for multi-tenant applications of various audiences (Work and School accounts, or Work and School accounts + + Personal accounts). + + Issuer to validate (will be tenanted). + Received security token. + Token validation parameters. + The issuer is considered as valid if it has the same HTTP scheme and authority as the + authority from the configuration file, has a tenant ID, and optionally v2.0 (this web API + accepts both V1 and V2 tokens). + Authority aliasing is also taken into account. + The issuer if it's valid, or otherwise SecurityTokenInvalidIssuerException is thrown. + if is null. + if is null. + if the issuer is invalid. + + + Gets the tenant ID from a token. + A JWT token. + A string containing the tenant ID, if found or . + Only and are acceptable types. + + + + This method is now Obsolete. + + Aad authority. + NotImplementedException. + + + + Interface implemented by diagnostics for the JWT Bearer middleware. + + + + + Called to subscribe to . + + JWT Bearer events. + The events (for chaining). + + + + Diagnostics used in the OpenID Connect middleware + (used in web apps). + + + + + Method to subscribe to . + + OpenID Connect events. + + + + Diagnostics for the JwtBearer middleware (used in web APIs). + + + + + Constructor for a . This constructor + is used by dependency injection. + + Logger. + + + + Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. + + + + + Invoked when a protocol message is first received. + + + + + Invoked after the security token has passed validation and a ClaimsIdentity has been generated. + + + + + Invoked before a challenge is sent back to the caller. + + + + + Subscribes to all the JwtBearer events, to help debugging, while + preserving the previous handlers (which are called). + + Events to subscribe to. + for chaining. + + + + Factory class for creating the IssuerValidator per authority. + + + + + Initializes a new instance of the class. + + Options passed-in to create the AadIssuerValidator object. + HttpClientFactory. + + + + Gets an for an authority. + + The authority to create the validator for, e.g. https://login.microsoftonline.com/. + A for the aadAuthority. + if is null or empty. + + + + Diagnostics used in the OpenID Connect middleware + (used in web apps). + + + + + Constructor of the , used + by dependency injection. + + Logger used to log the diagnostics. + + + + Invoked before redirecting to the identity provider to authenticate. This can + be used to set ProtocolMessage.State that will be persisted through the authentication + process. The ProtocolMessage can also be used to add or customize parameters + sent to the identity provider. + + + + + Invoked when a protocol message is first received. + + + + + Invoked after security token validation if an authorization code is present + in the protocol message. + + + + + Invoked after "authorization code" is redeemed for tokens at the token endpoint. + + + + + Invoked when an IdToken has been validated and produced an AuthenticationTicket. + + + + + Invoked when user information is retrieved from the UserInfoEndpoint. + + + + + Invoked if exceptions are thrown during request processing. The exceptions will + be re-thrown after this event unless suppressed. + + + + + Invoked when a request is received on the RemoteSignOutPath. + + + + + Invoked before redirecting to the identity provider to sign out. + + + + + Invoked before redirecting to the Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutRedirectUri + at the end of a remote sign-out flow. + + + + + Subscribes to all the OpenIdConnect events, to help debugging, while + preserving the previous handlers (which are called). + + Events to subscribe to. + + + + Generic class that registers the token audience from the provided Azure AD authority. + + + + + Default validation of the audience: + - when registering an Azure AD web API in the app registration portal (and adding a scope) + the default App ID URI generated by the portal is api://{clientID} + - However, the audience (aud) of the token acquired to access this web API is different depending + on the "accepted access token version" for the web API: + - if accepted token version is 1.0, the audience provided in the token + by the Microsoft identity platform (formerly Azure AD v2.0) endpoint is: api://{ClientID} + - if the accepted token version is 2.0, the audience provided by Azure AD v2.0 in the token + is {CliendID} + When getting an access token for an Azure AD B2C web API the audience in the token is + api://{ClientID}. + + When web API developers don't provide the "Audience" in the configuration, Microsoft.Identity.Web + considers that this is the default App ID URI as explained above. When developer provides the + "Audience" member, it's available in the TokenValidationParameter.ValidAudience. + + Audiences in the security token. + Security token from which to validate the audiences. + Token validation parameters. + True if the token is valid; false, otherwise. + + + + This attribute is used on a controller, pages, or controller actions + to declare (and validate) the scopes required by a web API. These scopes can be declared + in two ways: hardcoding them, or declaring them in the configuration. Depending on your + choice, use either one or the other of the constructors. + For details, see https://aka.ms/ms-id-web/required-scope-attribute. + + + + + Fully qualified name of the configuration key containing the required scopes (separated + by spaces). + + + If the appsettings.json file contains a section named "AzureAd", in which + a property named "Scopes" contains the required scopes, the attribute on the + controller/page/action to protect should be set to the following: + + [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] + + + + + + Verifies that the web API is called with the right scopes. + If the token obtained for this API is on behalf of the authenticated user does not have + any of these in its scope claim, the + method updates the HTTP response providing a status code 403 (Forbidden) + and writes to the response body a message telling which scopes are expected in the token. + + Scopes accepted by this web API. + When the scopes don't match, the response is a 403 (Forbidden), + because the user is authenticated (hence not 401), but not authorized. + + Add the following attribute on the controller/page/action to protect: + + + [RequiredScope("access_as_user")] + + + and + if you want to express the required scopes from the configuration. + + + + Default constructor, to be used along with the + property when you want to get the scopes to validate from the configuration, instead + of hardcoding them in the code. + + + + + If the authenticated user does not have any of these , the + method updates the HTTP response providing a status code 403 (Forbidden) + and writes to the response body a message telling which scopes are expected in the token. + + Scopes accepted by this web API. + When the scopes don't match, the response is a 403 (Forbidden), + because the user is authenticated (hence not 401), but not authorized. + + + + Extension class providing the extension methods for that + can be used in web APIs to validate the roles in controller actions. + + + + + When applied to an , verifies that the application + has the expected roles. + + HttpContext (from the controller). + Roles accepted by this web API. + When the roles don't match, the response is a 403 (Forbidden), + because the app does not have the expected roles. + + + + Extension class providing the extension + methods for that + can be used in web APIs to validate scopes in controller actions. + We recommend using instead the RequiredScope Attribute on the controller, the page or the action. + See https://aka.ms/ms-id-web/required-scope-attribute. + + + + + When applied to an , verifies that the user authenticated in the + web API has any of the accepted scopes. + If there is no authenticated user, the response is a 401 (Unauthenticated). + If the authenticated user does not have any of these , the + method updates the HTTP response providing a status code 403 (Forbidden) + and writes to the response body a message telling which scopes are expected in the token. + We recommend using instead the RequiredScope Attribute on the controller, the page or the action. + See https://aka.ms/ms-id-web/required-scope-attribute. + + HttpContext (from the controller). + Scopes accepted by this web API. + + + + Options passed-in to create the AadIssuerValidator object. + + + + + Sets the name of the HttpClient to get from the IHttpClientFactory for use with the configuration manager. + Needed when customizing the client such as configuring a proxy. + + + + + Extensions for IServiceCollection for startup initialization of web APIs. + + + + + Add the token acquisition service. + + Service collection. + Specifies if an instance of should be a singleton. + The service collection. + + This method is typically called from the ConfigureServices(IServiceCollection services) in Startup.cs. + Note that the implementation of the token cache can be chosen separately. + + + // Token acquisition service and its cache implementation as a session cache + services.AddTokenAcquisition() + .AddDistributedMemoryCache() + .AddSession() + .AddSessionBasedTokenCache(); + + + + + + An implementation of that uses to track error messages. + + + + + Token acquisition service. + + + + + Constructor of the TokenAcquisition service. This requires the Azure AD Options to + configure the confidential client application and a token cache provider. + This constructor is called by ASP.NET Core dependency injection. + + The App token cache provider. + Access to the HttpContext of the request. + Configuration options. + MSAL.NET configuration options. + HTTP client factory. + Logger. + Service provider. + + + + Scopes which are already requested by MSAL.NET. They should not be re-requested;. + + + + + Meta-tenant identifiers which are not allowed in client credentials. + + + + + This handler is executed after the authorization code is received (once the user signs-in and consents) during the + authorization code flow in a web app. + It uses the code to request an access token from the Microsoft identity platform and caches the tokens and an entry about the signed-in user's account in the MSAL's token cache. + The access token (and refresh token) provided in the , once added to the cache, are then used to acquire more tokens using the + on-behalf-of flow for the signed-in user's account, + in order to call to downstream APIs. + + The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. + scopes to request access to. + + From the configuration of the Authentication of the ASP.NET Core web API: + OpenIdConnectOptions options; + + Subscribe to the authorization code received event: + + options.Events = new OpenIdConnectEvents(); + options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; + } + + + And then in the OnAuthorizationCodeRecieved method, call : + + private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + { + var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); + await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); + } + + + + + + Typically used from a web app or web API controller, this method retrieves an access token + for a downstream API using; + 1) the token cache (for web apps and web APIs) if a token exists in the cache + 2) or the on-behalf-of flow + in web APIs, for the user account that is ascertained from claims provided in the + instance of the current HttpContext. + + Scopes to request for the downstream API to call. + Enables overriding of the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. + Azure AD B2C user flow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. + Options passed-in to create the token acquisition options object which calls into MSAL .NET. + An access token to call the downstream API and populated with this downstream API's scopes. + Calling this method from a web API supposes that you have previously called, + in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method + passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that + you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by + OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. + + + + Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Enables overriding of the tenant/account for the same identity. This is useful + for multi tenant apps or daemons. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An authentication result for the app itself, based on its scopes. + + + + Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) + using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + + The scope requested to access a protected API. For this flow (client credentials), the scope + should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + several calls to get tokens for other resources). + Enables overriding of the tenant/account for the same identity. This is useful + for multi tenant apps or daemons. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token for the app itself, based on its scopes. + + + + Typically used from a web app or web API controller, this method retrieves an access token + for a downstream API using; + 1) the token cache (for web apps and web APIs) if a token exists in the cache + 2) or the on-behalf-of flow + in web APIs, for the user account that is ascertained from the claims provided in the + instance of the current HttpContext. + + Scopes to request for the downstream API to call. + Enables overriding of the tenant/account for the same identity. This is useful in the + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Azure AD B2C user flow to target. + Optional claims principal representing the user. If not provided, will use the signed-in + user (in a web app), or the user for which the token was received (in a web API) + cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + An access token to call the downstream API and populated with this downstream API's scopes. + Calling this method from a web API supposes that you have previously called, + in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method + passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that + you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by + OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. + + + + Used in web APIs (no user interaction). + Replies to the client through the HTTP response by sending a 403 (forbidden) and populating the 'WWW-Authenticate' header so that + the client, in turn, can trigger a user interaction so that the user consents to more scopes. + + Scopes to consent to. + The that triggered the challenge. + The to update. + A representing the asynchronous operation. + + + + Removes the account associated with context.HttpContext.User from the MSAL.NET cache. + + RedirectContext passed-in to a + OpenID Connect event. + A that represents a completed account removal operation. + + + + Creates an MSAL confidential client application, if needed. + + + + + Creates an MSAL confidential client application. + + + + + Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal. + + . + Claims principal for the user on behalf of whom to get a token. + Scopes for the downstream API to call. + (optional) Authority based on a specific tenant for which to acquire a token to access the scopes + on behalf of the user described in the claimsPrincipal. + Azure AD B2C user flow to target. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + Gets an access token for a downstream API on behalf of the user whose account is passed as an argument. + + . + User IAccount for which to acquire a token. + See . + Scopes for the downstream API to call. + Authority based on a specific tenant for which to acquire a token to access the scopes + on behalf of the user. + Azure AD B2C user flow. + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + Options passed-in to create the token acquisition object which calls into MSAL .NET. + + + + + Sets the correlation id to be used in the authentication request + to the /token endpoint. + + + + + Sets Extra Query Parameters for the query string in the HTTP authentication request. + + + + + A string with one or multiple claims to request. + Normally used with Conditional Access. + + + + + Specifies if the token request will ignore the access token in the token cache + and will attempt to acquire a new access token. + If true, the request will ignore the token cache. The default is false. + Use this option with care and only when needed, for instance, if you know that conditional access policies have changed, + for it induces performance degradation, as the token cache is not utilized. + + + + + Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), + rather than a Bearer token. + PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, + which MSAL can manage. See https://aka.ms/msal-net-pop. + + + + + Clone the options (to be able to override them). + + A clone of the options. + + + + Extension class used to add distributed token cache serializer to MSAL. + See https://aka.ms/ms-id-web/token-cache-serialization for details. + + + + Adds the .NET Core distributed cache based app token cache to the service collection. + The services collection to add to. + A to chain. + + + + An implementation of the token cache for both Confidential and Public clients backed by a Distributed Cache. + The Distributed Cache (L2), by default creates a Memory Cache (L1), for faster look up, resulting in a two level cache. + + https://aka.ms/msal-net-token-cache-serialization + + + + .NET Core Memory cache. + + + + + MSAL distributed token cache options. + + + + + Initializes a new instance of the class. + + Distributed cache instance to use. + Options for the token cache. + MsalDistributedTokenCacheAdapter logger. + + + + Removes a specific token cache, described by its cache key + from the distributed cache. + + Key of the cache to remove. + A that completes when key removal has completed. + + + + Read a specific token cache, described by its cache key, from the + distributed cache. + + Key of the cache item to retrieve. + Read blob representing a token cache for the cache key + (account or app). + + + + Writes a token cache blob to the serialization cache (by key). + + Cache key. + blob to write. + A that completes when a write operation has completed. + + + + Options for the MSAL token cache serialization adapter, + which delegates the serialization to the IDistributedCache implementations + available with .NET Core. + + + + + Options of the In Memory (L1) cache. + + + + + Callback offered to the app to be notified when the L2 cache fails. + This way the app is given the possibility to act on the L2 cache, + for instance, in the case of Redis, to reconnect. This is left to the application as it's + the only one that knows about the real implementation of the L2 cache. + The handler should return true if the cache should try again the operation, and + false otherwise. When true is passed and the retry fails, an exception + will be thrown. + + + + + Value more than 0, less than 1, to set the In Memory (L1) cache + expiration time values relative to the Distributed (L2) cache. + Default is 1. + + + + + MSAL token cache provider interface. + + + + + Initializes a token cache (which can be a user token cache or an app token cache). + + Token cache for which to initialize the serialization. + A that represents a completed initialization operation. + + + + Clear the user token cache. + + HomeAccountId for a user account in the cache. + A that represents a completed clear operation. + + + + Extension class used to add an in-memory token cache serializer to MSAL. + + + + Adds both the app and per-user in-memory token caches. + The services collection to add to. + the services (for chaining). + + + + MSAL's in-memory token cache options. + + + + Initializes a new instance of the class. + By default, the sliding expiration is set for 14 days. + + + + Gets or sets the value of the duration after which the cache entry will expire unless it's used + This is the duration the tokens are kept in memory cache. + In production, a higher value, up-to 90 days is recommended. + + + The AbsoluteExpirationRelativeToNow value. + + + + + An implementation of token cache for both Confidential and Public clients backed by MemoryCache. + + https://aka.ms/msal-net-token-cache-serialization + + + + .NET Core Memory cache. + + + + + MSAL memory token cache options. + + + + + Constructor. + + serialization cache. + Memory cache options. + + + + Removes a token cache identified by its key, from the serialization + cache. + + token cache key. + A that completes when key removal has completed. + + + + Reads a blob from the serialization cache (identified by its key). + + Token cache key. + Read Bytes. + + + + Writes a token cache blob to the serialization cache (identified by its key). + + Token cache key. + Bytes to write. + A that completes when a write operation has completed. + + + + Token cache provider with default implementation. + + + + + + Initializes the token cache serialization. + + Token cache to serialize/deserialize. + A that represents a completed initialization operation. + + + + Raised AFTER MSAL added the new token in its in-memory copy of the cache. + This notification is called every time MSAL accesses the cache, not just when a write takes place: + If MSAL's current operation resulted in a cache change, the property TokenCacheNotificationArgs.HasStateChanged will be set to true. + If that is the case, we call the TokenCache.SerializeMsalV3() to get a binary blob representing the latest cache content – and persist it. + + Contains parameters used by the MSAL call accessing the cache. + + + + if you want to ensure that no concurrent write takes place, use this notification to place a lock on the entry. + + Token cache notification arguments. + A that represents a completed operation. + + + + Clear the cache. + + HomeAccountId for a user account in the cache. + A that represents a completed clear operation. + + + + Method to be implemented by concrete cache serializers to write the cache bytes. + + Cache key. + Bytes to write. + A that represents a completed write operation. + + + + Method to be implemented by concrete cache serializers to Read the cache bytes. + + Cache key. + Read bytes. + + + + Method to be implemented by concrete cache serializers to remove an entry from the cache. + + Cache key. + A that represents a completed remove key operation. + + + + An implementation of token cache for confidential clients backed by an HTTP session. + + + For this session cache to work effectively, the ASP.NET Core session has to be configured properly. + The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state + + In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + + In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + + app.UseSession(); // Before UseMvc() + + + https://aka.ms/msal-net-token-cache-serialization + + + + MSAL Token cache provider constructor. + + Session for the current user. + Logger. + + + + Read a blob representing the token cache from its key. + + Key representing the token cache + (account or app). + Read blob. + + + + Writes the token cache identified by its key to the serialization mechanism. + + Key for the cache (account ID or app ID). + Blob to write to the cache. + A that completes when a write operation has completed. + + + + Removes a cache described by its key. + + Key of the token cache (user account or app ID). + A that completes when key removal has completed. + + + + Extension class to add a session token cache serializer to MSAL. + + + + + Adds an HTTP session-based application token cache to the service collection. + + + For this session cache to work effectively the ASP.NET Core session has to be configured properly. + The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. + + In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + + In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + + app.UseSession(); // Before UseMvc() + + Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). + + The services collection to add to. + The service collection. + + + + Adds an HTTP session-based per-user token cache to the service collection. + + + For this session cache to work effectively the ASP.NET Core session has to be configured properly. + The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. + + In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + + In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + + app.UseSession(); // Before UseMvc() + + Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). + + The services collection to add to. + The service collection. + + + + Utility methods used by L1/L2 cache. + + + + + Authentication builder for a web API. + + + + + Constructor. + + The services being configured. + Default scheme used for OpenIdConnect. + ACtion called to configure the JwtBearer options. + Action called to configure + the Microsoft identity options. + Configuration section from which to + get parameters. + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + + The action to configure . + The authentication builder to chain. + + + + Extensions for for startup initialization of web APIs. + + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration instance. + The configuration section with the necessary settings to initialize authentication options. + The JWT bearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JWT bearer events. + + The authentication builder to chain. + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration second from which to fill-in the options. + The JWT bearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JWT bearer events. + + The authentication builder to chain. + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + + The to which to add this configuration. + The action to configure . + The action to configure the . + The JWT bearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JWT bearer events. + The authentication builder to chain. + + + + Builder for web API authentication with configuration. + + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The authentication builder to chain. + + + + Extension for IServiceCollection for startup initialization of web APIs. + + + + + Protects the web API with Microsoft identity platform (formerly Azure AD v2.0) + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + Service collection to which to add authentication. + The Configuration object. + The configuration section with the necessary settings to initialize authentication options. + The JwtBearer scheme name to be used. By default it uses "Bearer". + + Set to true if you want to debug, or just understand the JwtBearer events. + The authentication builder to chain extension methods. + + + + Authentication builder returned by the EnableTokenAcquisitionToCallDownstreamApi methods + enabling you to decide token cache implementations. + + + + + Add in memory token caches. + + to configure. + to configure. + the service collection. + + + + Add distributed token caches. + + the service collection. + + + + Add session token caches. + + the service collection. + + + + Authentication builder specific for Microsoft identity platform. + + + + + Constructor. + + The services being configured. + Default scheme used for OpenIdConnect. + Action called to configure + the Microsoft identity options. + Optional configuration section. + + + + The web app calls a web API. + + Initial scopes. + The builder itself for chaining. + + + + The web app calls a web API. This override enables you to specify the + ConfidentialClientApplicationOptions (from MSAL.NET) programmatically. + + Action to configure the + MSAL.NET confidential client application options. + Initial scopes. + The builder itself for chaining. + + + + Extensions for the for startup initialization. + + + + + Add authentication to a web app with Microsoft identity platform. + This method expects the configuration file will have a section, named "AzureAd" as default, + with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration instance. + The configuration section with the necessary settings to initialize authentication options. + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + + Set to true if you want to debug, or just understand the OpenID Connect events. + + The builder for chaining. + + + + Add authentication with Microsoft identity platform. + This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + + The to which to add this configuration. + The configuration section from which to get the options. + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + + Set to true if you want to debug, or just understand the OpenID Connect events. + + The authentication builder for chaining. + + + + Add authentication with Microsoft identity platform. + + The to which to add this configuration. + The action to configure . + The action to configure . + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + + Set to true if you want to debug, or just understand the OpenID Connect events. + + The authentication builder for chaining. + + + + Add authentication with Microsoft identity platform. + + The to which to add this configuration. + The action to configure . + The action to configure . + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + + Set to true if you want to debug, or just understand the OpenID Connect events. + + Configuration section. + The authentication builder for chaining. + + + + Add authentication with Microsoft identity platform. + + The to which to add this configuration. + The action to configure . + The action to configure . + The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + The cookie-based scheme name to be used. By default it uses "Cookies". + + Set to true if you want to debug, or just understand the OpenID Connect events. + + The authentication builder for chaining. + + + + Builder for a Microsoft identity web app authentication where configuration is + available for EnableTokenAcquisitionToCallDownstreamApi. + + + + + Constructor. + + The services being configured. + Default scheme used for OpenIdConnect. + Action called to configure + the Microsoft identity options. + Optional configuration section. + + + + Add support for the web app to acquire tokens to call an API. + + Optional initial scopes to request. + The authentication builder for chaining. + + + + Extension for IServiceCollection for startup initialization. + + + + + Add authentication with Microsoft identity platform. + This method expects the configuration file will have a section, (by default named "AzureAd"), with the necessary settings to + initialize the authentication options. + + Service collection to which to add authentication. + The IConfiguration object. + The name of the configuration section with the necessary + settings to initialize authentication options. + Optional name for the open id connect authentication scheme + (by default OpenIdConnectDefaults.AuthenticationScheme). This can be specified when you want to support + several OpenIdConnect identity providers. + Optional name for the cookie authentication scheme + (by default OpenIdConnectDefaults.AuthenticationScheme). + + Set to true if you want to debug, or just understand the OpenIdConnect events. + + The authentication builder to chain extension methods. + + diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityBaseAuthenticationBuilder.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityBaseAuthenticationBuilder.cs index 68b179527..4dc7f0729 100644 --- a/src/Microsoft.Identity.Web/MicrosoftIdentityBaseAuthenticationBuilder.cs +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityBaseAuthenticationBuilder.cs @@ -6,35 +6,35 @@ namespace Microsoft.Identity.Web { - /// - /// Base class for web app and web API Microsoft Identity authentication - /// builders. - /// - public abstract class MicrosoftIdentityBaseAuthenticationBuilder - { - /// - /// Constructor. - /// - /// The services being configured. - /// Optional configuration section. - protected MicrosoftIdentityBaseAuthenticationBuilder( - IServiceCollection services, - IConfigurationSection? configurationSection = null) - { - Services = services; - ConfigurationSection = configurationSection; - } + /// + /// Base class for web app and web API Microsoft Identity authentication + /// builders. + /// + public abstract class MicrosoftIdentityBaseAuthenticationBuilder + { + /// + /// Constructor. + /// + /// The services being configured. + /// Optional configuration section. + protected MicrosoftIdentityBaseAuthenticationBuilder( + IServiceCollection services, + IConfigurationSection? configurationSection = null) + { + Services = services; + ConfigurationSection = configurationSection; + } - /// - /// The services being configured. - /// - public IServiceCollection Services { get; private set; } + /// + /// The services being configured. + /// + public IServiceCollection Services { get; private set; } - /// - /// Configuration section from which to bind options. - /// - /// It can be null if the configuration happens with delegates - /// rather than configuration. - protected IConfigurationSection? ConfigurationSection { get; set; } - } + /// + /// Configuration section from which to bind options. + /// + /// It can be null if the configuration happens with delegates + /// rather than configuration. + protected IConfigurationSection? ConfigurationSection { get; set; } + } } diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs index 7aa4214ff..6a4df48d3 100644 --- a/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs @@ -17,257 +17,257 @@ namespace Microsoft.Identity.Web { - /// - /// Extensions for IServerSideBlazorBuilder for startup initialization of web APIs. - /// - public static class MicrosoftIdentityBlazorServiceCollectionExtensions - { - /// - /// Add the incremental consent and conditional access handler for Blazor - /// server side pages. - /// - /// Service side blazor builder. - /// The builder. - public static IServerSideBlazorBuilder AddMicrosoftIdentityConsentHandler( - this IServerSideBlazorBuilder builder) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } + /// + /// Extensions for IServerSideBlazorBuilder for startup initialization of web APIs. + /// + public static class MicrosoftIdentityBlazorServiceCollectionExtensions + { + /// + /// Add the incremental consent and conditional access handler for Blazor + /// server side pages. + /// + /// Service side blazor builder. + /// The builder. + public static IServerSideBlazorBuilder AddMicrosoftIdentityConsentHandler( + this IServerSideBlazorBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } - builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped()); - builder.Services.TryAddScoped(); - return builder; - } + builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped()); + builder.Services.TryAddScoped(); + return builder; + } - /// - /// Add the incremental consent and conditional access handler for - /// web app pages, Razor pages, controllers, views, etc... - /// - /// Service collection. - /// The service collection. - public static IServiceCollection AddMicrosoftIdentityConsentHandler( - this IServiceCollection services) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } + /// + /// Add the incremental consent and conditional access handler for + /// web app pages, Razor pages, controllers, views, etc... + /// + /// Service collection. + /// The service collection. + public static IServiceCollection AddMicrosoftIdentityConsentHandler( + this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } - services.TryAddEnumerable(ServiceDescriptor.Scoped()); - services.TryAddScoped(); - return services; - } - } + services.TryAddEnumerable(ServiceDescriptor.Scoped()); + services.TryAddScoped(); + return services; + } + } - /// - /// Handler for Blazor specific APIs to handle incremental consent - /// and conditional access. - /// + /// + /// Handler for Blazor specific APIs to handle incremental consent + /// and conditional access. + /// #pragma warning disable SA1402 // File may only contain a single type - public class MicrosoftIdentityConsentAndConditionalAccessHandler + public class MicrosoftIdentityConsentAndConditionalAccessHandler #pragma warning restore SA1402 // File may only contain a single type - { - private ClaimsPrincipal? _user; - private string? _baseUri; + { + private ClaimsPrincipal? _user; + private string? _baseUri; #pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - private readonly IHttpContextAccessor? _httpContextAccessor; + private readonly IHttpContextAccessor? _httpContextAccessor; #pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - /// - /// Initializes a new instance of the class. - /// - /// Service provider to get the HttpContextAccessor for the current HttpContext, when available. - public MicrosoftIdentityConsentAndConditionalAccessHandler(IServiceProvider serviceProvider) - { - _httpContextAccessor = serviceProvider.GetService(); - } + /// + /// Initializes a new instance of the class. + /// + /// Service provider to get the HttpContextAccessor for the current HttpContext, when available. + public MicrosoftIdentityConsentAndConditionalAccessHandler(IServiceProvider serviceProvider) + { + _httpContextAccessor = serviceProvider.GetService(); + } - /// - /// Boolean to determine if server is Blazor. - /// - public bool IsBlazorServer { get; set; } + /// + /// Boolean to determine if server is Blazor. + /// + public bool IsBlazorServer { get; set; } - /// - /// Current user. - /// - public ClaimsPrincipal User - { - get - { - return _user ?? + /// + /// Current user. + /// + public ClaimsPrincipal User + { + get + { + return _user ?? #pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - (!IsBlazorServer ? _httpContextAccessor.HttpContext.User : + (!IsBlazorServer ? _httpContextAccessor.HttpContext.User : #pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - throw new InvalidOperationException(IDWebErrorMessage.BlazorServerUserNotSet)); - } - set - { - _user = value; - } - } + throw new InvalidOperationException(IDWebErrorMessage.BlazorServerUserNotSet)); + } + set + { + _user = value; + } + } - /// - /// Base URI to use in forming the redirect. - /// - public string? BaseUri - { - get - { - return _baseUri ?? + /// + /// Base URI to use in forming the redirect. + /// + public string? BaseUri + { + get + { + return _baseUri ?? #pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case - (!IsBlazorServer ? CreateBaseUri(_httpContextAccessor.HttpContext.Request) : + (!IsBlazorServer ? CreateBaseUri(_httpContextAccessor.HttpContext.Request) : #pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case - throw new InvalidOperationException(IDWebErrorMessage.BlazorServerBaseUriNotSet)); - } - set - { - _baseUri = value; - } - } + throw new InvalidOperationException(IDWebErrorMessage.BlazorServerBaseUriNotSet)); + } + set + { + _baseUri = value; + } + } - private static string CreateBaseUri(HttpRequest request) - { - string baseUri = string.Format( - CultureInfo.InvariantCulture, - "{0}://{1}/{2}", - request.Scheme, - request.Host.ToString(), - request.PathBase.ToString().TrimStart('/')); - return baseUri.TrimEnd('/'); - } + private static string CreateBaseUri(HttpRequest request) + { + string baseUri = string.Format( + CultureInfo.InvariantCulture, + "{0}://{1}/{2}", + request.Scheme, + request.Host.ToString(), + request.PathBase.ToString().TrimStart('/')); + return baseUri.TrimEnd('/'); + } - /// - /// For Blazor/Razor pages to process the exception from - /// a user challenge. - /// - /// Exception. - public void HandleException(Exception exception) - { - MicrosoftIdentityWebChallengeUserException? microsoftIdentityWebChallengeUserException = - exception as MicrosoftIdentityWebChallengeUserException; + /// + /// For Blazor/Razor pages to process the exception from + /// a user challenge. + /// + /// Exception. + public void HandleException(Exception exception) + { + MicrosoftIdentityWebChallengeUserException? microsoftIdentityWebChallengeUserException = + exception as MicrosoftIdentityWebChallengeUserException; - if (microsoftIdentityWebChallengeUserException == null) - { + if (microsoftIdentityWebChallengeUserException == null) + { #pragma warning disable CA1062 // Validate arguments of public methods - microsoftIdentityWebChallengeUserException = exception.InnerException as MicrosoftIdentityWebChallengeUserException; + microsoftIdentityWebChallengeUserException = exception.InnerException as MicrosoftIdentityWebChallengeUserException; #pragma warning restore CA1062 // Validate arguments of public methods - } + } - if (microsoftIdentityWebChallengeUserException != null && - IncrementalConsentAndConditionalAccessHelper.CanBeSolvedByReSignInOfUser(microsoftIdentityWebChallengeUserException.MsalUiRequiredException)) - { - var properties = IncrementalConsentAndConditionalAccessHelper.BuildAuthenticationProperties( - microsoftIdentityWebChallengeUserException.Scopes, - microsoftIdentityWebChallengeUserException.MsalUiRequiredException, - User, - microsoftIdentityWebChallengeUserException.Userflow); + if (microsoftIdentityWebChallengeUserException != null && + IncrementalConsentAndConditionalAccessHelper.CanBeSolvedByReSignInOfUser(microsoftIdentityWebChallengeUserException.MsalUiRequiredException)) + { + var properties = IncrementalConsentAndConditionalAccessHelper.BuildAuthenticationProperties( + microsoftIdentityWebChallengeUserException.Scopes, + microsoftIdentityWebChallengeUserException.MsalUiRequiredException, + User, + microsoftIdentityWebChallengeUserException.Userflow); - List scopes = properties.Parameters.ContainsKey(Constants.Scope) ? (List)properties.Parameters[Constants.Scope]! : new List(); - string claims = properties.Parameters.ContainsKey(Constants.Claims) ? (string)properties.Parameters[Constants.Claims]! : string.Empty; - string userflow = properties.Items.ContainsKey(OidcConstants.PolicyKey) ? properties.Items[OidcConstants.PolicyKey]! : string.Empty; + List scopes = properties.Parameters.ContainsKey(Constants.Scope) ? (List)properties.Parameters[Constants.Scope]! : new List(); + string claims = properties.Parameters.ContainsKey(Constants.Claims) ? (string)properties.Parameters[Constants.Claims]! : string.Empty; + string userflow = properties.Items.ContainsKey(OidcConstants.PolicyKey) ? properties.Items[OidcConstants.PolicyKey]! : string.Empty; - ChallengeUser( - scopes.ToArray(), - claims, - userflow); - } - else - { - throw exception; - } - } + ChallengeUser( + scopes.ToArray(), + claims, + userflow); + } + else + { + throw exception; + } + } - /// - /// Forces the user to consent to specific scopes and perform - /// Conditional Access to get specific claims. Use on a Razor/Blazor - /// page or controller to proactively ensure the scopes and/or claims - /// before acquiring a token. The other mechanism - /// ensures claims and scopes requested by Azure AD after a failed token acquisition attempt. - /// See https://aka.ms/ms-id-web/ca_incremental-consent for details. - /// - /// Scopes to request. - /// Claims to ensure. - /// Userflow being invoked for AAD B2C. - public void ChallengeUser( - string[]? scopes, - string? claims = null, - string? userflow = null) - { - IEnumerable effectiveScopes = scopes ?? new string[0]; + /// + /// Forces the user to consent to specific scopes and perform + /// Conditional Access to get specific claims. Use on a Razor/Blazor + /// page or controller to proactively ensure the scopes and/or claims + /// before acquiring a token. The other mechanism + /// ensures claims and scopes requested by Azure AD after a failed token acquisition attempt. + /// See https://aka.ms/ms-id-web/ca_incremental-consent for details. + /// + /// Scopes to request. + /// Claims to ensure. + /// Userflow being invoked for AAD B2C. + public void ChallengeUser( + string[]? scopes, + string? claims = null, + string? userflow = null) + { + IEnumerable effectiveScopes = scopes ?? new string[0]; - string[] additionalBuiltInScopes = - { - OidcConstants.ScopeOpenId, - OidcConstants.ScopeOfflineAccess, - OidcConstants.ScopeProfile, - }; + string[] additionalBuiltInScopes = + { + OidcConstants.ScopeOpenId, + OidcConstants.ScopeOfflineAccess, + OidcConstants.ScopeProfile, + }; - effectiveScopes = effectiveScopes.Union(additionalBuiltInScopes).ToArray(); + effectiveScopes = effectiveScopes.Union(additionalBuiltInScopes).ToArray(); - string redirectUri; - if (IsBlazorServer) - { - redirectUri = NavigationManager.Uri; - } - else - { + string redirectUri; + if (IsBlazorServer) + { + redirectUri = NavigationManager.Uri; + } + else + { #pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - var request = _httpContextAccessor.HttpContext.Request; + var request = _httpContextAccessor.HttpContext.Request; #pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - redirectUri = string.Format( - CultureInfo.InvariantCulture, - "{0}/{1}", - CreateBaseUri(request), - request.Path.ToString().TrimStart('/')); - } + redirectUri = string.Format( + CultureInfo.InvariantCulture, + "{0}/{1}", + CreateBaseUri(request), + request.Path.ToString().TrimStart('/')); + } - string url = $"{BaseUri}/{Constants.BlazorChallengeUri}{redirectUri}" - + $"&{Constants.Scope}={string.Join(" ", effectiveScopes!)}&{Constants.LoginHintParameter}={User.GetLoginHint()}" - + $"&{Constants.DomainHintParameter}={User.GetDomainHint()}&{Constants.Claims}={claims}" - + $"&{OidcConstants.PolicyKey}={userflow}"; + string url = $"{BaseUri}/{Constants.BlazorChallengeUri}{redirectUri}" + + $"&{Constants.Scope}={string.Join(" ", effectiveScopes!)}&{Constants.LoginHintParameter}={User.GetLoginHint()}" + + $"&{Constants.DomainHintParameter}={User.GetDomainHint()}&{Constants.Claims}={claims}" + + $"&{OidcConstants.PolicyKey}={userflow}"; - if (IsBlazorServer) - { - NavigationManager.NavigateTo(url, true); - } - else - { + if (IsBlazorServer) + { + NavigationManager.NavigateTo(url, true); + } + else + { #pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - _httpContextAccessor.HttpContext.Response.Redirect(url); + _httpContextAccessor.HttpContext.Response.Redirect(url); #pragma warning restore CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - } - } + } + } - internal NavigationManager NavigationManager { get; set; } = null!; - } + internal NavigationManager NavigationManager { get; set; } = null!; + } #pragma warning disable SA1402 // File may only contain a single type - internal class MicrosoftIdentityServiceHandler : CircuitHandler + internal class MicrosoftIdentityServiceHandler : CircuitHandler #pragma warning restore SA1402 // File may only contain a single type - { - public MicrosoftIdentityServiceHandler(MicrosoftIdentityConsentAndConditionalAccessHandler service, AuthenticationStateProvider provider, NavigationManager manager) - { - Service = service; - Provider = provider; - Manager = manager; - } + { + public MicrosoftIdentityServiceHandler(MicrosoftIdentityConsentAndConditionalAccessHandler service, AuthenticationStateProvider provider, NavigationManager manager) + { + Service = service; + Provider = provider; + Manager = manager; + } - public MicrosoftIdentityConsentAndConditionalAccessHandler Service { get; } + public MicrosoftIdentityConsentAndConditionalAccessHandler Service { get; } - public AuthenticationStateProvider Provider { get; } + public AuthenticationStateProvider Provider { get; } - public NavigationManager Manager { get; } + public NavigationManager Manager { get; } - public override async Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken) - { - var state = await Provider.GetAuthenticationStateAsync().ConfigureAwait(false); - Service.User = state.User; - Service.IsBlazorServer = true; - Service.BaseUri = Manager.BaseUri.TrimEnd('/'); - Service.NavigationManager = Manager; - await base.OnCircuitOpenedAsync(circuit, cancellationToken).ConfigureAwait(false); - } - } + public override async Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken) + { + var state = await Provider.GetAuthenticationStateAsync().ConfigureAwait(false); + Service.User = state.User; + Service.IsBlazorServer = true; + Service.BaseUri = Manager.BaseUri.TrimEnd('/'); + Service.NavigationManager = Manager; + await base.OnCircuitOpenedAsync(circuit, cancellationToken).ConfigureAwait(false); + } + } } diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs index 318cfc0e7..bcad88ee7 100644 --- a/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityOptions.cs @@ -7,131 +7,131 @@ namespace Microsoft.Identity.Web { - /// - /// Options for configuring authentication using Azure Active Directory. It has both AAD and B2C configuration attributes. - /// - public class MicrosoftIdentityOptions : OpenIdConnectOptions - { - /// - /// Gets or sets the Azure Active Directory instance, e.g. "https://login.microsoftonline.com". - /// - public string Instance { get; set; } = null!; - - /// - /// Gets or sets the tenant ID. - /// - public string? TenantId { get; set; } - - /// - /// Gets or sets the domain of the Azure Active Directory tenant, e.g. contoso.onmicrosoft.com. - /// - public string? Domain { get; set; } - - /// - /// Gets or sets the edit profile user flow name for B2C, e.g. b2c_1_edit_profile. - /// - public string? EditProfilePolicyId { get; set; } - - /// - /// Gets or sets the sign up or sign in user flow name for B2C, e.g. b2c_1_susi. - /// - public string? SignUpSignInPolicyId { get; set; } - - /// - /// Gets or sets the reset password user flow name for B2C, e.g. B2C_1_password_reset. - /// - public string? ResetPasswordPolicyId { get; set; } - - /// - /// Gets the default user flow (which is signUpsignIn). - /// - public string? DefaultUserFlow => SignUpSignInPolicyId; - - /// - /// Enables legacy ADAL cache serialization and deserialization. - /// Performance improvements when working with MSAL only apps. - /// Set to true if you have a shared cache with ADAL apps. - /// - /// The default is false. - public bool LegacyCacheCompatibilityEnabled { get; set; } - - /// - /// Is considered B2C if the attribute SignUpSignInPolicyId is defined. - /// - internal bool IsB2C - { - get => !string.IsNullOrWhiteSpace(DefaultUserFlow); - } - - /// - /// Is considered to have client credentials if the attribute ClientCertificates - /// or ClientSecret is defined. - /// - internal bool HasClientCredentials - { - get => !string.IsNullOrWhiteSpace(ClientSecret) || (ClientCertificates != null && ClientCertificates.Any()); - } - - /// - /// Description of the certificates used to prove the identity of the web app or web API. - /// For the moment only the first certificate is considered. - /// - /// An example in the appsetting.json: - /// - /// "ClientCertificates": [ - /// { - /// "SourceType": "StoreWithDistinguishedName", - /// "CertificateStorePath": "CurrentUser/My", - /// "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" - /// } - /// ] - /// - /// See also https://aka.ms/ms-id-web-certificates. - /// - public IEnumerable? ClientCertificates { get; set; } - - /// - /// Description of the certificates used to decrypt an encrypted token in a web API. - /// For the moment only the first certificate is considered. - /// - /// An example in the appsetting.json: - /// - /// "TokenDecryptionCertificates": [ - /// { - /// "SourceType": "StoreWithDistinguishedName", - /// "CertificateStorePath": "CurrentUser/My", - /// "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" - /// } - /// ] - /// - /// See also https://aka.ms/ms-id-web-certificates. - /// - public IEnumerable? TokenDecryptionCertificates { get; set; } - - /// - /// Specifies if the x5c claim (public key of the certificate) should be sent to the STS. - /// Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: - /// this method will send the public certificate to Azure AD along with the token request, - /// so that Azure AD can use it to validate the subject name based on a trusted issuer policy. - /// This saves the application admin from the need to explicitly manage the certificate rollover - /// (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni. - /// - /// The default is false. - public bool SendX5C { get; set; } - - /// - /// Daemon applications can validate a token based on roles, or using the ACL-based authorization - /// pattern to control tokens without a roles claim. If using ACL-based authorization, - /// Microsoft Identity Web will not throw if roles or scopes are not in the Claims. - /// For details see https://aka.ms/ms-identity-web/daemon-ACL. - /// - /// The default is false. - public bool AllowWebApiToBeAuthorizedByACL { get; set; } - - /// - /// Used, when deployed to Azure, to specify explicitly a user assigned managed identity. - /// See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. - /// - public string? UserAssignedManagedIdentityClientId { get; set; } - } + /// + /// Options for configuring authentication using Azure Active Directory. It has both AAD and B2C configuration attributes. + /// + public class MicrosoftIdentityOptions : OpenIdConnectOptions + { + /// + /// Gets or sets the Azure Active Directory instance, e.g. "https://login.microsoftonline.com". + /// + public string Instance { get; set; } = null!; + + /// + /// Gets or sets the tenant ID. + /// + public string? TenantId { get; set; } + + /// + /// Gets or sets the domain of the Azure Active Directory tenant, e.g. contoso.onmicrosoft.com. + /// + public string? Domain { get; set; } + + /// + /// Gets or sets the edit profile user flow name for B2C, e.g. b2c_1_edit_profile. + /// + public string? EditProfilePolicyId { get; set; } + + /// + /// Gets or sets the sign up or sign in user flow name for B2C, e.g. b2c_1_susi. + /// + public string? SignUpSignInPolicyId { get; set; } + + /// + /// Gets or sets the reset password user flow name for B2C, e.g. B2C_1_password_reset. + /// + public string? ResetPasswordPolicyId { get; set; } + + /// + /// Gets the default user flow (which is signUpsignIn). + /// + public string? DefaultUserFlow => SignUpSignInPolicyId; + + /// + /// Enables legacy ADAL cache serialization and deserialization. + /// Performance improvements when working with MSAL only apps. + /// Set to true if you have a shared cache with ADAL apps. + /// + /// The default is false. + public bool LegacyCacheCompatibilityEnabled { get; set; } + + /// + /// Is considered B2C if the attribute SignUpSignInPolicyId is defined. + /// + internal bool IsB2C + { + get => !string.IsNullOrWhiteSpace(DefaultUserFlow); + } + + /// + /// Is considered to have client credentials if the attribute ClientCertificates + /// or ClientSecret is defined. + /// + internal bool HasClientCredentials + { + get => !string.IsNullOrWhiteSpace(ClientSecret) || (ClientCertificates != null && ClientCertificates.Any()); + } + + /// + /// Description of the certificates used to prove the identity of the web app or web API. + /// For the moment only the first certificate is considered. + /// + /// An example in the appsetting.json: + /// + /// "ClientCertificates": [ + /// { + /// "SourceType": "StoreWithDistinguishedName", + /// "CertificateStorePath": "CurrentUser/My", + /// "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" + /// } + /// ] + /// + /// See also https://aka.ms/ms-id-web-certificates. + /// + public IEnumerable? ClientCertificates { get; set; } + + /// + /// Description of the certificates used to decrypt an encrypted token in a web API. + /// For the moment only the first certificate is considered. + /// + /// An example in the appsetting.json: + /// + /// "TokenDecryptionCertificates": [ + /// { + /// "SourceType": "StoreWithDistinguishedName", + /// "CertificateStorePath": "CurrentUser/My", + /// "CertificateDistinguishedName": "CN=WebAppCallingWebApiCert" + /// } + /// ] + /// + /// See also https://aka.ms/ms-id-web-certificates. + /// + public IEnumerable? TokenDecryptionCertificates { get; set; } + + /// + /// Specifies if the x5c claim (public key of the certificate) should be sent to the STS. + /// Sending the x5c enables application developers to achieve easy certificate rollover in Azure AD: + /// this method will send the public certificate to Azure AD along with the token request, + /// so that Azure AD can use it to validate the subject name based on a trusted issuer policy. + /// This saves the application admin from the need to explicitly manage the certificate rollover + /// (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni. + /// + /// The default is false. + public bool SendX5C { get; set; } + + /// + /// Daemon applications can validate a token based on roles, or using the ACL-based authorization + /// pattern to control tokens without a roles claim. If using ACL-based authorization, + /// Microsoft Identity Web will not throw if roles or scopes are not in the Claims. + /// For details see https://aka.ms/ms-identity-web/daemon-ACL. + /// + /// The default is false. + public bool AllowWebApiToBeAuthorizedByACL { get; set; } + + /// + /// Used, when deployed to Azure, to specify explicitly a user assigned managed identity. + /// See https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-manage-ua-identity-portal. + /// + public string? UserAssignedManagedIdentityClientId { get; set; } + } } diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityOptionsValidation.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityOptionsValidation.cs index 7f2a9045d..f5de3babb 100644 --- a/src/Microsoft.Identity.Web/MicrosoftIdentityOptionsValidation.cs +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityOptionsValidation.cs @@ -8,54 +8,54 @@ namespace Microsoft.Identity.Web { - internal class MicrosoftIdentityOptionsValidation : IValidateOptions - { - public ValidateOptionsResult Validate(string name, MicrosoftIdentityOptions options) - { - if (string.IsNullOrEmpty(options.ClientId)) - { - return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.ClientId))); - } + internal class MicrosoftIdentityOptionsValidation : IValidateOptions + { + public ValidateOptionsResult Validate(string name, MicrosoftIdentityOptions options) + { + if (string.IsNullOrEmpty(options.ClientId)) + { + return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.ClientId))); + } - if (string.IsNullOrEmpty(options.Instance)) - { - return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.Instance))); - } + if (string.IsNullOrEmpty(options.Instance)) + { + return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.Instance))); + } - if (options.IsB2C) - { - if (string.IsNullOrEmpty(options.Domain)) - { - return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.Domain))); - } - } - else - { - if (string.IsNullOrEmpty(options.TenantId)) - { - return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.TenantId))); - } - } + if (options.IsB2C) + { + if (string.IsNullOrEmpty(options.Domain)) + { + return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.Domain))); + } + } + else + { + if (string.IsNullOrEmpty(options.TenantId)) + { + return ValidateOptionsResult.Fail(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.ConfigurationOptionRequired, nameof(options.TenantId))); + } + } - return ValidateOptionsResult.Success; - } + return ValidateOptionsResult.Success; + } - public static void ValidateEitherClientCertificateOrClientSecret( - string? clientSecret, - IEnumerable? cert) - { - if (string.IsNullOrEmpty(clientSecret) && (cert == null)) - { - throw new MsalClientException( - ErrorCodes.MissingClientCredentials, - IDWebErrorMessage.ClientSecretAndCertficateNull); - } - else if (!string.IsNullOrEmpty(clientSecret) && (cert != null)) - { - throw new MsalClientException( - ErrorCodes.DuplicateClientCredentials, - IDWebErrorMessage.BothClientSecretAndCertificateProvided); - } - } - } + public static void ValidateEitherClientCertificateOrClientSecret( + string? clientSecret, + IEnumerable? cert) + { + if (string.IsNullOrEmpty(clientSecret) && (cert == null)) + { + throw new MsalClientException( + ErrorCodes.MissingClientCredentials, + IDWebErrorMessage.ClientSecretAndCertficateNull); + } + else if (!string.IsNullOrEmpty(clientSecret) && (cert != null)) + { + throw new MsalClientException( + ErrorCodes.DuplicateClientCredentials, + IDWebErrorMessage.BothClientSecretAndCertificateProvided); + } + } + } } diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityWebChallengeUserException.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityWebChallengeUserException.cs index 9dc7c46f8..57396f2e1 100644 --- a/src/Microsoft.Identity.Web/MicrosoftIdentityWebChallengeUserException.cs +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityWebChallengeUserException.cs @@ -6,45 +6,45 @@ namespace Microsoft.Identity.Web { - /// - /// Microsoft Identity Web specific exception class for - /// use in Blazor or Razor pages to process the user challenge. - /// Handles the . - /// - public class MicrosoftIdentityWebChallengeUserException : Exception - { - /// - /// Exception thrown by MSAL when a user challenge is encountered. - /// - public MsalUiRequiredException MsalUiRequiredException { get; set; } + /// + /// Microsoft Identity Web specific exception class for + /// use in Blazor or Razor pages to process the user challenge. + /// Handles the . + /// + public class MicrosoftIdentityWebChallengeUserException : Exception + { + /// + /// Exception thrown by MSAL when a user challenge is encountered. + /// + public MsalUiRequiredException MsalUiRequiredException { get; set; } - /// - /// Scopes to request. - /// - public string[] Scopes { get; set; } + /// + /// Scopes to request. + /// + public string[] Scopes { get; set; } - /// - /// Specified userflow. - /// - public string? Userflow { get; set; } + /// + /// Specified userflow. + /// + public string? Userflow { get; set; } - /// - /// Handles the user challenge for Blazor or Razor pages. - /// - /// Exception thrown by MSAL when a user challenge is encountered. - /// Scopes to request. - /// Userflow used in B2C. - public MicrosoftIdentityWebChallengeUserException( - MsalUiRequiredException msalUiRequiredException, - string[] scopes, - string? userflow = null) - : base( - IDWebErrorMessage.MicrosoftIdentityWebChallengeUserException, - msalUiRequiredException) - { - MsalUiRequiredException = msalUiRequiredException; - Scopes = scopes; - Userflow = userflow; - } - } + /// + /// Handles the user challenge for Blazor or Razor pages. + /// + /// Exception thrown by MSAL when a user challenge is encountered. + /// Scopes to request. + /// Userflow used in B2C. + public MicrosoftIdentityWebChallengeUserException( + MsalUiRequiredException msalUiRequiredException, + string[] scopes, + string? userflow = null) + : base( + IDWebErrorMessage.MicrosoftIdentityWebChallengeUserException, + msalUiRequiredException) + { + MsalUiRequiredException = msalUiRequiredException; + Scopes = scopes; + Userflow = userflow; + } + } } diff --git a/src/Microsoft.Identity.Web/MsalAspNetCoreHttpClientFactory.cs b/src/Microsoft.Identity.Web/MsalAspNetCoreHttpClientFactory.cs index a97e683c4..9c7fc885f 100644 --- a/src/Microsoft.Identity.Web/MsalAspNetCoreHttpClientFactory.cs +++ b/src/Microsoft.Identity.Web/MsalAspNetCoreHttpClientFactory.cs @@ -6,20 +6,20 @@ namespace Microsoft.Identity.Web { - internal class MsalAspNetCoreHttpClientFactory : IMsalHttpClientFactory - { - private readonly IHttpClientFactory _httpClientFactory; + internal class MsalAspNetCoreHttpClientFactory : IMsalHttpClientFactory + { + private readonly IHttpClientFactory _httpClientFactory; - public MsalAspNetCoreHttpClientFactory(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } + public MsalAspNetCoreHttpClientFactory(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } - public HttpClient GetHttpClient() - { - HttpClient httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.Add(Constants.TelemetryHeaderKey, IdHelper.CreateTelemetryInfo()); - return httpClient; - } - } + public HttpClient GetHttpClient() + { + HttpClient httpClient = _httpClientFactory.CreateClient(); + httpClient.DefaultRequestHeaders.Add(Constants.TelemetryHeaderKey, IdHelper.CreateTelemetryInfo()); + return httpClient; + } + } } diff --git a/src/Microsoft.Identity.Web/NuGet.Config b/src/Microsoft.Identity.Web/NuGet.Config index 7604d0051..5c3f7d279 100644 --- a/src/Microsoft.Identity.Web/NuGet.Config +++ b/src/Microsoft.Identity.Web/NuGet.Config @@ -1,7 +1,7 @@  - - + + diff --git a/src/Microsoft.Identity.Web/Properties/InternalsVisibleTo.cs b/src/Microsoft.Identity.Web/Properties/InternalsVisibleTo.cs index 664a2e4a4..f48ad2b94 100644 --- a/src/Microsoft.Identity.Web/Properties/InternalsVisibleTo.cs +++ b/src/Microsoft.Identity.Web/Properties/InternalsVisibleTo.cs @@ -4,9 +4,9 @@ using System.Runtime.CompilerServices; // Allow this assembly to be serviced when run on desktop CLR -[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraph, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] -[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraphBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] -[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] -[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] -[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraph, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] +[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraphBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] +[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] +[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] +[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs b/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs index 5babdc767..c4d3c014d 100644 --- a/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs +++ b/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs @@ -10,198 +10,198 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Generic class that validates token issuer from the provided Azure AD authority. - /// - public class AadIssuerValidator - { - /// - /// A list of all Issuers across the various Azure AD instances. - /// - private readonly ISet _issuerAliases; - - internal /*internal for tests*/ AadIssuerValidator(IEnumerable aliases) - { - _issuerAliases = new HashSet(aliases, StringComparer.OrdinalIgnoreCase); - } - - /// - /// Validate the issuer for multi-tenant applications of various audiences (Work and School accounts, or Work and School accounts + - /// Personal accounts). - /// - /// Issuer to validate (will be tenanted). - /// Received security token. - /// Token validation parameters. - /// The issuer is considered as valid if it has the same HTTP scheme and authority as the - /// authority from the configuration file, has a tenant ID, and optionally v2.0 (this web API - /// accepts both V1 and V2 tokens). - /// Authority aliasing is also taken into account. - /// The issuer if it's valid, or otherwise SecurityTokenInvalidIssuerException is thrown. - /// if is null. - /// if is null. - /// if the issuer is invalid. - public string Validate(string actualIssuer, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (string.IsNullOrEmpty(actualIssuer)) - { - throw new ArgumentNullException(nameof(actualIssuer)); - } - - if (securityToken == null) - { - throw new ArgumentNullException(nameof(securityToken)); - } - - if (validationParameters == null) - { - throw new ArgumentNullException(nameof(validationParameters)); - } - - string tenantId = GetTenantIdFromToken(securityToken); - if (string.IsNullOrWhiteSpace(tenantId)) - { - throw new SecurityTokenInvalidIssuerException(IDWebErrorMessage.TenantIdClaimNotPresentInToken); - } - - if (validationParameters.ValidIssuers != null) - { - foreach (var validIssuerTemplate in validationParameters.ValidIssuers) - { - if (IsValidIssuer(validIssuerTemplate, tenantId, actualIssuer)) - { - return actualIssuer; - } - } - } - - if (IsValidIssuer(validationParameters.ValidIssuer, tenantId, actualIssuer)) - { - return actualIssuer; - } - - // If a valid issuer is not found, throw - throw new SecurityTokenInvalidIssuerException( - string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.IssuerDoesNotMatchValidIssuers, - actualIssuer)); - } - - private bool IsValidIssuer(string validIssuerTemplate, string tenantId, string actualIssuer) - { - if (string.IsNullOrEmpty(validIssuerTemplate)) - { - return false; - } - - try - { - Uri issuerFromTemplateUri = new Uri(validIssuerTemplate.Replace("{tenantid}", tenantId)); - Uri actualIssuerUri = new Uri(actualIssuer); - - // Template authority is in the aliases - return _issuerAliases.Contains(issuerFromTemplateUri.Authority) && - // "iss" authority is in the aliases - _issuerAliases.Contains(actualIssuerUri.Authority) && - // Template authority ends in the tenant ID - IsValidTidInLocalPath(tenantId, issuerFromTemplateUri) && - // "iss" ends in the tenant ID - IsValidTidInLocalPath(tenantId, actualIssuerUri); - } - catch - { - // if something faults, ignore - } - - return false; - } - - private static bool IsValidTidInLocalPath(string tenantId, Uri uri) - { - string trimmedLocalPath = uri.LocalPath.Trim('/'); - return trimmedLocalPath == tenantId || trimmedLocalPath == $"{tenantId}/v2.0"; - } - - /// Gets the tenant ID from a token. - /// A JWT token. - /// A string containing the tenant ID, if found or . - /// Only and are acceptable types. - private static string GetTenantIdFromToken(SecurityToken securityToken) - { - if (securityToken is JwtSecurityToken jwtSecurityToken) - { - if (jwtSecurityToken.Payload.TryGetValue(ClaimConstants.Tid, out object? tid)) - { - return (string)tid; - } - - jwtSecurityToken.Payload.TryGetValue(ClaimConstants.TenantId, out object? tenantId); - if (tenantId != null) - { - return (string)tenantId; - } - - // Since B2C doesn't have "tid" as default, get it from issuer - return GetTenantIdFromIss(jwtSecurityToken.Issuer); - } - - if (securityToken is JsonWebToken jsonWebToken) - { - jsonWebToken.TryGetPayloadValue(ClaimConstants.Tid, out string? tid); - if (tid != null) - { - return tid; - } - - jsonWebToken.TryGetPayloadValue(ClaimConstants.TenantId, out string? tenantId); - if (tenantId != null) - { - return tenantId; - } - - // Since B2C doesn't have "tid" as default, get it from issuer - return GetTenantIdFromIss(jsonWebToken.Issuer); - } - - return string.Empty; - } - - // The AAD "iss" claims contains the tenant ID in its value. - // The URI can be - // - {domain}/{tid}/v2.0 - // - {domain}/{tid}/v2.0/ - // - {domain}/{tfp}/{tid}/{userFlow}/v2.0/ - private static string GetTenantIdFromIss(string iss) - { - if (string.IsNullOrEmpty(iss)) - { - return string.Empty; - } - - var uri = new Uri(iss); - - if (uri.Segments.Length == 3) - { - return uri.Segments[1].TrimEnd('/'); - } - - if (uri.Segments.Length == 5 && uri.Segments[1].TrimEnd('/') == ClaimConstants.Tfp) - { - throw new SecurityTokenInvalidIssuerException(IDWebErrorMessage.B2CTfpIssuerNotSupported); - } - - return string.Empty; - } - - /// - /// This method is now Obsolete. - /// - /// Aad authority. - /// NotImplementedException. - [Obsolete(IDWebErrorMessage.AadIssuerValidatorGetIssuerValidatorIsObsolete, true)] - public static AadIssuerValidator GetIssuerValidator(string aadAuthority) - { - throw new NotImplementedException(IDWebErrorMessage.AadIssuerValidatorGetIssuerValidatorIsObsolete); - } - } + /// + /// Generic class that validates token issuer from the provided Azure AD authority. + /// + public class AadIssuerValidator + { + /// + /// A list of all Issuers across the various Azure AD instances. + /// + private readonly ISet _issuerAliases; + + internal /*internal for tests*/ AadIssuerValidator(IEnumerable aliases) + { + _issuerAliases = new HashSet(aliases, StringComparer.OrdinalIgnoreCase); + } + + /// + /// Validate the issuer for multi-tenant applications of various audiences (Work and School accounts, or Work and School accounts + + /// Personal accounts). + /// + /// Issuer to validate (will be tenanted). + /// Received security token. + /// Token validation parameters. + /// The issuer is considered as valid if it has the same HTTP scheme and authority as the + /// authority from the configuration file, has a tenant ID, and optionally v2.0 (this web API + /// accepts both V1 and V2 tokens). + /// Authority aliasing is also taken into account. + /// The issuer if it's valid, or otherwise SecurityTokenInvalidIssuerException is thrown. + /// if is null. + /// if is null. + /// if the issuer is invalid. + public string Validate(string actualIssuer, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + if (string.IsNullOrEmpty(actualIssuer)) + { + throw new ArgumentNullException(nameof(actualIssuer)); + } + + if (securityToken == null) + { + throw new ArgumentNullException(nameof(securityToken)); + } + + if (validationParameters == null) + { + throw new ArgumentNullException(nameof(validationParameters)); + } + + string tenantId = GetTenantIdFromToken(securityToken); + if (string.IsNullOrWhiteSpace(tenantId)) + { + throw new SecurityTokenInvalidIssuerException(IDWebErrorMessage.TenantIdClaimNotPresentInToken); + } + + if (validationParameters.ValidIssuers != null) + { + foreach (var validIssuerTemplate in validationParameters.ValidIssuers) + { + if (IsValidIssuer(validIssuerTemplate, tenantId, actualIssuer)) + { + return actualIssuer; + } + } + } + + if (IsValidIssuer(validationParameters.ValidIssuer, tenantId, actualIssuer)) + { + return actualIssuer; + } + + // If a valid issuer is not found, throw + throw new SecurityTokenInvalidIssuerException( + string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.IssuerDoesNotMatchValidIssuers, + actualIssuer)); + } + + private bool IsValidIssuer(string validIssuerTemplate, string tenantId, string actualIssuer) + { + if (string.IsNullOrEmpty(validIssuerTemplate)) + { + return false; + } + + try + { + Uri issuerFromTemplateUri = new Uri(validIssuerTemplate.Replace("{tenantid}", tenantId)); + Uri actualIssuerUri = new Uri(actualIssuer); + + // Template authority is in the aliases + return _issuerAliases.Contains(issuerFromTemplateUri.Authority) && + // "iss" authority is in the aliases + _issuerAliases.Contains(actualIssuerUri.Authority) && + // Template authority ends in the tenant ID + IsValidTidInLocalPath(tenantId, issuerFromTemplateUri) && + // "iss" ends in the tenant ID + IsValidTidInLocalPath(tenantId, actualIssuerUri); + } + catch + { + // if something faults, ignore + } + + return false; + } + + private static bool IsValidTidInLocalPath(string tenantId, Uri uri) + { + string trimmedLocalPath = uri.LocalPath.Trim('/'); + return trimmedLocalPath == tenantId || trimmedLocalPath == $"{tenantId}/v2.0"; + } + + /// Gets the tenant ID from a token. + /// A JWT token. + /// A string containing the tenant ID, if found or . + /// Only and are acceptable types. + private static string GetTenantIdFromToken(SecurityToken securityToken) + { + if (securityToken is JwtSecurityToken jwtSecurityToken) + { + if (jwtSecurityToken.Payload.TryGetValue(ClaimConstants.Tid, out object? tid)) + { + return (string)tid; + } + + jwtSecurityToken.Payload.TryGetValue(ClaimConstants.TenantId, out object? tenantId); + if (tenantId != null) + { + return (string)tenantId; + } + + // Since B2C doesn't have "tid" as default, get it from issuer + return GetTenantIdFromIss(jwtSecurityToken.Issuer); + } + + if (securityToken is JsonWebToken jsonWebToken) + { + jsonWebToken.TryGetPayloadValue(ClaimConstants.Tid, out string? tid); + if (tid != null) + { + return tid; + } + + jsonWebToken.TryGetPayloadValue(ClaimConstants.TenantId, out string? tenantId); + if (tenantId != null) + { + return tenantId; + } + + // Since B2C doesn't have "tid" as default, get it from issuer + return GetTenantIdFromIss(jsonWebToken.Issuer); + } + + return string.Empty; + } + + // The AAD "iss" claims contains the tenant ID in its value. + // The URI can be + // - {domain}/{tid}/v2.0 + // - {domain}/{tid}/v2.0/ + // - {domain}/{tfp}/{tid}/{userFlow}/v2.0/ + private static string GetTenantIdFromIss(string iss) + { + if (string.IsNullOrEmpty(iss)) + { + return string.Empty; + } + + var uri = new Uri(iss); + + if (uri.Segments.Length == 3) + { + return uri.Segments[1].TrimEnd('/'); + } + + if (uri.Segments.Length == 5 && uri.Segments[1].TrimEnd('/') == ClaimConstants.Tfp) + { + throw new SecurityTokenInvalidIssuerException(IDWebErrorMessage.B2CTfpIssuerNotSupported); + } + + return string.Empty; + } + + /// + /// This method is now Obsolete. + /// + /// Aad authority. + /// NotImplementedException. + [Obsolete(IDWebErrorMessage.AadIssuerValidatorGetIssuerValidatorIsObsolete, true)] + public static AadIssuerValidator GetIssuerValidator(string aadAuthority) + { + throw new NotImplementedException(IDWebErrorMessage.AadIssuerValidatorGetIssuerValidatorIsObsolete); + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/AadIssuerValidatorOptions.cs b/src/Microsoft.Identity.Web/Resource/AadIssuerValidatorOptions.cs index d76146bfb..6b05f8cef 100644 --- a/src/Microsoft.Identity.Web/Resource/AadIssuerValidatorOptions.cs +++ b/src/Microsoft.Identity.Web/Resource/AadIssuerValidatorOptions.cs @@ -3,15 +3,15 @@ namespace Microsoft.Identity.Web { - /// - /// Options passed-in to create the AadIssuerValidator object. - /// - public class AadIssuerValidatorOptions - { - /// - /// Sets the name of the HttpClient to get from the IHttpClientFactory for use with the configuration manager. - /// Needed when customizing the client such as configuring a proxy. - /// - public string? HttpClientName { get; set; } - } + /// + /// Options passed-in to create the AadIssuerValidator object. + /// + public class AadIssuerValidatorOptions + { + /// + /// Sets the name of the HttpClient to get from the IHttpClientFactory for use with the configuration manager. + /// Needed when customizing the client such as configuring a proxy. + /// + public string? HttpClientName { get; set; } + } } diff --git a/src/Microsoft.Identity.Web/Resource/IJwtBearerMiddlewareDiagnostics.cs b/src/Microsoft.Identity.Web/Resource/IJwtBearerMiddlewareDiagnostics.cs index 24e3fde2a..14e33e361 100644 --- a/src/Microsoft.Identity.Web/Resource/IJwtBearerMiddlewareDiagnostics.cs +++ b/src/Microsoft.Identity.Web/Resource/IJwtBearerMiddlewareDiagnostics.cs @@ -5,16 +5,16 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Interface implemented by diagnostics for the JWT Bearer middleware. - /// - public interface IJwtBearerMiddlewareDiagnostics - { - /// - /// Called to subscribe to . - /// - /// JWT Bearer events. - /// The events (for chaining). - JwtBearerEvents Subscribe(JwtBearerEvents events); - } + /// + /// Interface implemented by diagnostics for the JWT Bearer middleware. + /// + public interface IJwtBearerMiddlewareDiagnostics + { + /// + /// Called to subscribe to . + /// + /// JWT Bearer events. + /// The events (for chaining). + JwtBearerEvents Subscribe(JwtBearerEvents events); + } } diff --git a/src/Microsoft.Identity.Web/Resource/IOpenIdConnectMiddlewareDiagnostics.cs b/src/Microsoft.Identity.Web/Resource/IOpenIdConnectMiddlewareDiagnostics.cs index 75476ff7d..873f91b6a 100644 --- a/src/Microsoft.Identity.Web/Resource/IOpenIdConnectMiddlewareDiagnostics.cs +++ b/src/Microsoft.Identity.Web/Resource/IOpenIdConnectMiddlewareDiagnostics.cs @@ -5,16 +5,16 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Diagnostics used in the OpenID Connect middleware - /// (used in web apps). - /// - public interface IOpenIdConnectMiddlewareDiagnostics - { - /// - /// Method to subscribe to . - /// - /// OpenID Connect events. - void Subscribe(OpenIdConnectEvents events); - } + /// + /// Diagnostics used in the OpenID Connect middleware + /// (used in web apps). + /// + public interface IOpenIdConnectMiddlewareDiagnostics + { + /// + /// Method to subscribe to . + /// + /// OpenID Connect events. + void Subscribe(OpenIdConnectEvents events); + } } diff --git a/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs b/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs index 952b610d6..44bafdf53 100644 --- a/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs +++ b/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs @@ -9,99 +9,99 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Diagnostics for the JwtBearer middleware (used in web APIs). - /// - public class JwtBearerMiddlewareDiagnostics : IJwtBearerMiddlewareDiagnostics - { - private readonly ILogger _logger; - - /// - /// Constructor for a . This constructor - /// is used by dependency injection. - /// - /// Logger. - public JwtBearerMiddlewareDiagnostics(ILogger logger) - { - _logger = logger; - } - - /// - /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. - /// - private Func s_onAuthenticationFailed = null!; - - /// - /// Invoked when a protocol message is first received. - /// - private Func s_onMessageReceived = null!; - - /// - /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. - /// - private Func s_onTokenValidated = null!; - - /// - /// Invoked before a challenge is sent back to the caller. - /// - private Func s_onChallenge = null!; - - /// - /// Subscribes to all the JwtBearer events, to help debugging, while - /// preserving the previous handlers (which are called). - /// - /// Events to subscribe to. - /// for chaining. - public JwtBearerEvents Subscribe(JwtBearerEvents events) - { - events ??= new JwtBearerEvents(); - - s_onAuthenticationFailed = events.OnAuthenticationFailed; - events.OnAuthenticationFailed = OnAuthenticationFailedAsync; - - s_onMessageReceived = events.OnMessageReceived; - events.OnMessageReceived = OnMessageReceivedAsync; - - s_onTokenValidated = events.OnTokenValidated; - events.OnTokenValidated = OnTokenValidatedAsync; - - s_onChallenge = events.OnChallenge; - events.OnChallenge = OnChallengeAsync; - - return events; - } - - private async Task OnMessageReceivedAsync(MessageReceivedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnMessageReceivedAsync))); - - // Place a breakpoint here and examine the bearer token (context.Request.Headers.HeaderAuthorization / context.Request.Headers["Authorization"]) - // Use https://jwt.ms to decode the token and observe claims - await s_onMessageReceived(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnMessageReceivedAsync))); - } - - private async Task OnAuthenticationFailedAsync(AuthenticationFailedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnAuthenticationFailedAsync))); - - // Place a breakpoint here and examine context.Exception - await s_onAuthenticationFailed(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnAuthenticationFailedAsync))); - } - - private async Task OnTokenValidatedAsync(TokenValidatedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnTokenValidatedAsync))); - await s_onTokenValidated(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnTokenValidatedAsync))); - } - - private async Task OnChallengeAsync(JwtBearerChallengeContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnChallengeAsync))); - await s_onChallenge(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnChallengeAsync))); - } - } + /// + /// Diagnostics for the JwtBearer middleware (used in web APIs). + /// + public class JwtBearerMiddlewareDiagnostics : IJwtBearerMiddlewareDiagnostics + { + private readonly ILogger _logger; + + /// + /// Constructor for a . This constructor + /// is used by dependency injection. + /// + /// Logger. + public JwtBearerMiddlewareDiagnostics(ILogger logger) + { + _logger = logger; + } + + /// + /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. + /// + private Func s_onAuthenticationFailed = null!; + + /// + /// Invoked when a protocol message is first received. + /// + private Func s_onMessageReceived = null!; + + /// + /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. + /// + private Func s_onTokenValidated = null!; + + /// + /// Invoked before a challenge is sent back to the caller. + /// + private Func s_onChallenge = null!; + + /// + /// Subscribes to all the JwtBearer events, to help debugging, while + /// preserving the previous handlers (which are called). + /// + /// Events to subscribe to. + /// for chaining. + public JwtBearerEvents Subscribe(JwtBearerEvents events) + { + events ??= new JwtBearerEvents(); + + s_onAuthenticationFailed = events.OnAuthenticationFailed; + events.OnAuthenticationFailed = OnAuthenticationFailedAsync; + + s_onMessageReceived = events.OnMessageReceived; + events.OnMessageReceived = OnMessageReceivedAsync; + + s_onTokenValidated = events.OnTokenValidated; + events.OnTokenValidated = OnTokenValidatedAsync; + + s_onChallenge = events.OnChallenge; + events.OnChallenge = OnChallengeAsync; + + return events; + } + + private async Task OnMessageReceivedAsync(MessageReceivedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnMessageReceivedAsync))); + + // Place a breakpoint here and examine the bearer token (context.Request.Headers.HeaderAuthorization / context.Request.Headers["Authorization"]) + // Use https://jwt.ms to decode the token and observe claims + await s_onMessageReceived(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnMessageReceivedAsync))); + } + + private async Task OnAuthenticationFailedAsync(AuthenticationFailedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnAuthenticationFailedAsync))); + + // Place a breakpoint here and examine context.Exception + await s_onAuthenticationFailed(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnAuthenticationFailedAsync))); + } + + private async Task OnTokenValidatedAsync(TokenValidatedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnTokenValidatedAsync))); + await s_onTokenValidated(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnTokenValidatedAsync))); + } + + private async Task OnChallengeAsync(JwtBearerChallengeContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnChallengeAsync))); + await s_onChallenge(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnChallengeAsync))); + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/MicrosoftIdentityIssuerValidatorFactory.cs b/src/Microsoft.Identity.Web/Resource/MicrosoftIdentityIssuerValidatorFactory.cs index d1b08b5d9..5640292cd 100644 --- a/src/Microsoft.Identity.Web/Resource/MicrosoftIdentityIssuerValidatorFactory.cs +++ b/src/Microsoft.Identity.Web/Resource/MicrosoftIdentityIssuerValidatorFactory.cs @@ -12,74 +12,74 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Factory class for creating the IssuerValidator per authority. - /// - public class MicrosoftIdentityIssuerValidatorFactory - { - /// - /// Initializes a new instance of the class. - /// - /// Options passed-in to create the AadIssuerValidator object. - /// HttpClientFactory. - public MicrosoftIdentityIssuerValidatorFactory( - IOptions aadIssuerValidatorOptions, - IHttpClientFactory httpClientFactory) - { - if (aadIssuerValidatorOptions?.Value?.HttpClientName != null && httpClientFactory != null) - { - _configManager = - new ConfigurationManager( - Constants.AzureADIssuerMetadataUrl, - new IssuerConfigurationRetriever(), - httpClientFactory.CreateClient(aadIssuerValidatorOptions.Value.HttpClientName)); - } - else - { - _configManager = - new ConfigurationManager( - Constants.AzureADIssuerMetadataUrl, - new IssuerConfigurationRetriever()); - } - } + /// + /// Factory class for creating the IssuerValidator per authority. + /// + public class MicrosoftIdentityIssuerValidatorFactory + { + /// + /// Initializes a new instance of the class. + /// + /// Options passed-in to create the AadIssuerValidator object. + /// HttpClientFactory. + public MicrosoftIdentityIssuerValidatorFactory( + IOptions aadIssuerValidatorOptions, + IHttpClientFactory httpClientFactory) + { + if (aadIssuerValidatorOptions?.Value?.HttpClientName != null && httpClientFactory != null) + { + _configManager = + new ConfigurationManager( + Constants.AzureADIssuerMetadataUrl, + new IssuerConfigurationRetriever(), + httpClientFactory.CreateClient(aadIssuerValidatorOptions.Value.HttpClientName)); + } + else + { + _configManager = + new ConfigurationManager( + Constants.AzureADIssuerMetadataUrl, + new IssuerConfigurationRetriever()); + } + } - private readonly IDictionary _issuerValidators = new ConcurrentDictionary(); + private readonly IDictionary _issuerValidators = new ConcurrentDictionary(); - private readonly ConfigurationManager _configManager; + private readonly ConfigurationManager _configManager; - /// - /// Gets an for an authority. - /// - /// The authority to create the validator for, e.g. https://login.microsoftonline.com/. - /// A for the aadAuthority. - /// if is null or empty. - public AadIssuerValidator GetAadIssuerValidator(string aadAuthority) - { - if (string.IsNullOrEmpty(aadAuthority)) - { - throw new ArgumentNullException(nameof(aadAuthority)); - } + /// + /// Gets an for an authority. + /// + /// The authority to create the validator for, e.g. https://login.microsoftonline.com/. + /// A for the aadAuthority. + /// if is null or empty. + public AadIssuerValidator GetAadIssuerValidator(string aadAuthority) + { + if (string.IsNullOrEmpty(aadAuthority)) + { + throw new ArgumentNullException(nameof(aadAuthority)); + } - Uri.TryCreate(aadAuthority, UriKind.Absolute, out Uri? authorityUri); - string authorityHost = authorityUri?.Authority ?? new Uri(Constants.FallbackAuthority).Authority; + Uri.TryCreate(aadAuthority, UriKind.Absolute, out Uri? authorityUri); + string authorityHost = authorityUri?.Authority ?? new Uri(Constants.FallbackAuthority).Authority; - if (_issuerValidators.TryGetValue(authorityHost, out AadIssuerValidator? aadIssuerValidator)) - { - return aadIssuerValidator; - } + if (_issuerValidators.TryGetValue(authorityHost, out AadIssuerValidator? aadIssuerValidator)) + { + return aadIssuerValidator; + } - // In the constructor, we hit the Azure AD issuer metadata endpoint and cache the aliases. The data is cached for 24 hrs. - IssuerMetadata issuerMetadata = _configManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + // In the constructor, we hit the Azure AD issuer metadata endpoint and cache the aliases. The data is cached for 24 hrs. + IssuerMetadata issuerMetadata = _configManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - // Add issuer aliases of the chosen authority to the cache - IEnumerable aliases = issuerMetadata.Metadata - .Where(m => m.Aliases.Any(a => string.Equals(a, authorityHost, StringComparison.OrdinalIgnoreCase))) - .SelectMany(m => m.Aliases) - .Append(authorityHost) // For B2C scenarios, the alias will be the authority itself - .Distinct(); - _issuerValidators[authorityHost] = new AadIssuerValidator(aliases); + // Add issuer aliases of the chosen authority to the cache + IEnumerable aliases = issuerMetadata.Metadata + .Where(m => m.Aliases.Any(a => string.Equals(a, authorityHost, StringComparison.OrdinalIgnoreCase))) + .SelectMany(m => m.Aliases) + .Append(authorityHost) // For B2C scenarios, the alias will be the authority itself + .Distinct(); + _issuerValidators[authorityHost] = new AadIssuerValidator(aliases); - return _issuerValidators[authorityHost]; - } - } + return _issuerValidators[authorityHost]; + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs b/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs index b69db5827..8dea1371c 100644 --- a/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs +++ b/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs @@ -10,206 +10,206 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Diagnostics used in the OpenID Connect middleware - /// (used in web apps). - /// - public class OpenIdConnectMiddlewareDiagnostics : IOpenIdConnectMiddlewareDiagnostics - { - private readonly ILogger _logger; - - /// - /// Constructor of the , used - /// by dependency injection. - /// - /// Logger used to log the diagnostics. - public OpenIdConnectMiddlewareDiagnostics(ILogger logger) - { - _logger = logger; - } - - /// - /// Invoked before redirecting to the identity provider to authenticate. This can - /// be used to set ProtocolMessage.State that will be persisted through the authentication - /// process. The ProtocolMessage can also be used to add or customize parameters - /// sent to the identity provider. - /// - private Func s_onRedirectToIdentityProvider = null!; - - /// - /// Invoked when a protocol message is first received. - /// - private Func s_onMessageReceived = null!; - - /// - /// Invoked after security token validation if an authorization code is present - /// in the protocol message. - /// - private Func s_onAuthorizationCodeReceived = null!; - - /// - /// Invoked after "authorization code" is redeemed for tokens at the token endpoint. - /// - private Func s_onTokenResponseReceived = null!; - - /// - /// Invoked when an IdToken has been validated and produced an AuthenticationTicket. - /// - private Func s_onTokenValidated = null!; - - /// - /// Invoked when user information is retrieved from the UserInfoEndpoint. - /// - private Func s_onUserInformationReceived = null!; - - /// - /// Invoked if exceptions are thrown during request processing. The exceptions will - /// be re-thrown after this event unless suppressed. - /// - private Func s_onAuthenticationFailed = null!; - - /// - /// Invoked when a request is received on the RemoteSignOutPath. - /// - private Func s_onRemoteSignOut = null!; - - /// - /// Invoked before redirecting to the identity provider to sign out. - /// - private Func s_onRedirectToIdentityProviderForSignOut = null!; - - /// - /// Invoked before redirecting to the Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutRedirectUri - /// at the end of a remote sign-out flow. - /// - private Func s_onSignedOutCallbackRedirect = null!; - - /// - /// Subscribes to all the OpenIdConnect events, to help debugging, while - /// preserving the previous handlers (which are called). - /// - /// Events to subscribe to. - public void Subscribe(OpenIdConnectEvents events) - { - events ??= new OpenIdConnectEvents(); - - s_onRedirectToIdentityProvider = events.OnRedirectToIdentityProvider; - events.OnRedirectToIdentityProvider = OnRedirectToIdentityProviderAsync; - - s_onMessageReceived = events.OnMessageReceived; - events.OnMessageReceived = OnMessageReceivedAsync; - - s_onAuthorizationCodeReceived = events.OnAuthorizationCodeReceived; - events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync; - - s_onTokenResponseReceived = events.OnTokenResponseReceived; - events.OnTokenResponseReceived = OnTokenResponseReceivedAsync; - - s_onTokenValidated = events.OnTokenValidated; - events.OnTokenValidated = OnTokenValidatedAsync; - - s_onUserInformationReceived = events.OnUserInformationReceived; - events.OnUserInformationReceived = OnUserInformationReceivedAsync; - - s_onAuthenticationFailed = events.OnAuthenticationFailed; - events.OnAuthenticationFailed = OnAuthenticationFailedAsync; - - s_onRemoteSignOut = events.OnRemoteSignOut; - events.OnRemoteSignOut = OnRemoteSignOutAsync; - - s_onRedirectToIdentityProviderForSignOut = events.OnRedirectToIdentityProviderForSignOut; - events.OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOutAsync; - - s_onSignedOutCallbackRedirect = events.OnSignedOutCallbackRedirect; - events.OnSignedOutCallbackRedirect = OnSignedOutCallbackRedirectAsync; - } - - private async Task OnRedirectToIdentityProviderAsync(RedirectContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnRedirectToIdentityProviderAsync))); - - await s_onRedirectToIdentityProvider(context).ConfigureAwait(false); - - _logger.LogDebug(" Sending OpenIdConnect message:"); - DisplayProtocolMessage(context.ProtocolMessage); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnRedirectToIdentityProviderAsync))); - } - - private void DisplayProtocolMessage(OpenIdConnectMessage message) - { - foreach (var property in message.GetType().GetProperties()) - { - object? value = property.GetValue(message); - if (value != null) - { - _logger.LogDebug($" - {property.Name}={value}"); - } - } - } - - private async Task OnMessageReceivedAsync(MessageReceivedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnMessageReceivedAsync))); - _logger.LogDebug(" Received from STS the OpenIdConnect message:"); - DisplayProtocolMessage(context.ProtocolMessage); - await s_onMessageReceived(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnMessageReceivedAsync))); - } - - private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnAuthorizationCodeReceivedAsync))); - await s_onAuthorizationCodeReceived(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnAuthorizationCodeReceivedAsync))); - } - - private async Task OnTokenResponseReceivedAsync(TokenResponseReceivedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnTokenResponseReceivedAsync))); - await s_onTokenResponseReceived(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnTokenResponseReceivedAsync))); - } - - private async Task OnTokenValidatedAsync(TokenValidatedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnTokenValidatedAsync))); - await s_onTokenValidated(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnTokenValidatedAsync))); - } - - private async Task OnUserInformationReceivedAsync(UserInformationReceivedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnUserInformationReceivedAsync))); - await s_onUserInformationReceived(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnUserInformationReceivedAsync))); - } - - private async Task OnAuthenticationFailedAsync(AuthenticationFailedContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnAuthenticationFailedAsync))); - await s_onAuthenticationFailed(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnAuthenticationFailedAsync))); - } - - private async Task OnRedirectToIdentityProviderForSignOutAsync(RedirectContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnRedirectToIdentityProviderForSignOutAsync))); - await s_onRedirectToIdentityProviderForSignOut(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnRedirectToIdentityProviderForSignOutAsync))); - } - - private async Task OnRemoteSignOutAsync(RemoteSignOutContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnRemoteSignOutAsync))); - await s_onRemoteSignOut(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnRemoteSignOutAsync))); - } - - private async Task OnSignedOutCallbackRedirectAsync(RemoteSignOutContext context) - { - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnSignedOutCallbackRedirectAsync))); - await s_onSignedOutCallbackRedirect(context).ConfigureAwait(false); - _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnSignedOutCallbackRedirectAsync))); - } - } + /// + /// Diagnostics used in the OpenID Connect middleware + /// (used in web apps). + /// + public class OpenIdConnectMiddlewareDiagnostics : IOpenIdConnectMiddlewareDiagnostics + { + private readonly ILogger _logger; + + /// + /// Constructor of the , used + /// by dependency injection. + /// + /// Logger used to log the diagnostics. + public OpenIdConnectMiddlewareDiagnostics(ILogger logger) + { + _logger = logger; + } + + /// + /// Invoked before redirecting to the identity provider to authenticate. This can + /// be used to set ProtocolMessage.State that will be persisted through the authentication + /// process. The ProtocolMessage can also be used to add or customize parameters + /// sent to the identity provider. + /// + private Func s_onRedirectToIdentityProvider = null!; + + /// + /// Invoked when a protocol message is first received. + /// + private Func s_onMessageReceived = null!; + + /// + /// Invoked after security token validation if an authorization code is present + /// in the protocol message. + /// + private Func s_onAuthorizationCodeReceived = null!; + + /// + /// Invoked after "authorization code" is redeemed for tokens at the token endpoint. + /// + private Func s_onTokenResponseReceived = null!; + + /// + /// Invoked when an IdToken has been validated and produced an AuthenticationTicket. + /// + private Func s_onTokenValidated = null!; + + /// + /// Invoked when user information is retrieved from the UserInfoEndpoint. + /// + private Func s_onUserInformationReceived = null!; + + /// + /// Invoked if exceptions are thrown during request processing. The exceptions will + /// be re-thrown after this event unless suppressed. + /// + private Func s_onAuthenticationFailed = null!; + + /// + /// Invoked when a request is received on the RemoteSignOutPath. + /// + private Func s_onRemoteSignOut = null!; + + /// + /// Invoked before redirecting to the identity provider to sign out. + /// + private Func s_onRedirectToIdentityProviderForSignOut = null!; + + /// + /// Invoked before redirecting to the Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions.SignedOutRedirectUri + /// at the end of a remote sign-out flow. + /// + private Func s_onSignedOutCallbackRedirect = null!; + + /// + /// Subscribes to all the OpenIdConnect events, to help debugging, while + /// preserving the previous handlers (which are called). + /// + /// Events to subscribe to. + public void Subscribe(OpenIdConnectEvents events) + { + events ??= new OpenIdConnectEvents(); + + s_onRedirectToIdentityProvider = events.OnRedirectToIdentityProvider; + events.OnRedirectToIdentityProvider = OnRedirectToIdentityProviderAsync; + + s_onMessageReceived = events.OnMessageReceived; + events.OnMessageReceived = OnMessageReceivedAsync; + + s_onAuthorizationCodeReceived = events.OnAuthorizationCodeReceived; + events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync; + + s_onTokenResponseReceived = events.OnTokenResponseReceived; + events.OnTokenResponseReceived = OnTokenResponseReceivedAsync; + + s_onTokenValidated = events.OnTokenValidated; + events.OnTokenValidated = OnTokenValidatedAsync; + + s_onUserInformationReceived = events.OnUserInformationReceived; + events.OnUserInformationReceived = OnUserInformationReceivedAsync; + + s_onAuthenticationFailed = events.OnAuthenticationFailed; + events.OnAuthenticationFailed = OnAuthenticationFailedAsync; + + s_onRemoteSignOut = events.OnRemoteSignOut; + events.OnRemoteSignOut = OnRemoteSignOutAsync; + + s_onRedirectToIdentityProviderForSignOut = events.OnRedirectToIdentityProviderForSignOut; + events.OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOutAsync; + + s_onSignedOutCallbackRedirect = events.OnSignedOutCallbackRedirect; + events.OnSignedOutCallbackRedirect = OnSignedOutCallbackRedirectAsync; + } + + private async Task OnRedirectToIdentityProviderAsync(RedirectContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnRedirectToIdentityProviderAsync))); + + await s_onRedirectToIdentityProvider(context).ConfigureAwait(false); + + _logger.LogDebug(" Sending OpenIdConnect message:"); + DisplayProtocolMessage(context.ProtocolMessage); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnRedirectToIdentityProviderAsync))); + } + + private void DisplayProtocolMessage(OpenIdConnectMessage message) + { + foreach (var property in message.GetType().GetProperties()) + { + object? value = property.GetValue(message); + if (value != null) + { + _logger.LogDebug($" - {property.Name}={value}"); + } + } + } + + private async Task OnMessageReceivedAsync(MessageReceivedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnMessageReceivedAsync))); + _logger.LogDebug(" Received from STS the OpenIdConnect message:"); + DisplayProtocolMessage(context.ProtocolMessage); + await s_onMessageReceived(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnMessageReceivedAsync))); + } + + private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnAuthorizationCodeReceivedAsync))); + await s_onAuthorizationCodeReceived(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnAuthorizationCodeReceivedAsync))); + } + + private async Task OnTokenResponseReceivedAsync(TokenResponseReceivedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnTokenResponseReceivedAsync))); + await s_onTokenResponseReceived(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnTokenResponseReceivedAsync))); + } + + private async Task OnTokenValidatedAsync(TokenValidatedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnTokenValidatedAsync))); + await s_onTokenValidated(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnTokenValidatedAsync))); + } + + private async Task OnUserInformationReceivedAsync(UserInformationReceivedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnUserInformationReceivedAsync))); + await s_onUserInformationReceived(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnUserInformationReceivedAsync))); + } + + private async Task OnAuthenticationFailedAsync(AuthenticationFailedContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnAuthenticationFailedAsync))); + await s_onAuthenticationFailed(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnAuthenticationFailedAsync))); + } + + private async Task OnRedirectToIdentityProviderForSignOutAsync(RedirectContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnRedirectToIdentityProviderForSignOutAsync))); + await s_onRedirectToIdentityProviderForSignOut(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnRedirectToIdentityProviderForSignOutAsync))); + } + + private async Task OnRemoteSignOutAsync(RemoteSignOutContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnRemoteSignOutAsync))); + await s_onRemoteSignOut(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnRemoteSignOutAsync))); + } + + private async Task OnSignedOutCallbackRedirectAsync(RemoteSignOutContext context) + { + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnSignedOutCallbackRedirectAsync))); + await s_onSignedOutCallbackRedirect(context).ConfigureAwait(false); + _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnSignedOutCallbackRedirectAsync))); + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/RegisterValidAudience.cs b/src/Microsoft.Identity.Web/Resource/RegisterValidAudience.cs index 0c67f654c..f1ad60efe 100644 --- a/src/Microsoft.Identity.Web/Resource/RegisterValidAudience.cs +++ b/src/Microsoft.Identity.Web/Resource/RegisterValidAudience.cs @@ -9,82 +9,82 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Generic class that registers the token audience from the provided Azure AD authority. - /// - internal class RegisterValidAudience - { - private string ClientId { get; set; } = null!; - private bool IsB2C { get; set; } + /// + /// Generic class that registers the token audience from the provided Azure AD authority. + /// + internal class RegisterValidAudience + { + private string ClientId { get; set; } = null!; + private bool IsB2C { get; set; } - public void RegisterAudienceValidation( - TokenValidationParameters validationParameters, - MicrosoftIdentityOptions microsoftIdentityOptions) - { - if (validationParameters == null) - { - throw new ArgumentNullException(nameof(validationParameters)); - } + public void RegisterAudienceValidation( + TokenValidationParameters validationParameters, + MicrosoftIdentityOptions microsoftIdentityOptions) + { + if (validationParameters == null) + { + throw new ArgumentNullException(nameof(validationParameters)); + } - ClientId = microsoftIdentityOptions.ClientId; - IsB2C = microsoftIdentityOptions.IsB2C; + ClientId = microsoftIdentityOptions.ClientId; + IsB2C = microsoftIdentityOptions.IsB2C; - validationParameters.AudienceValidator = ValidateAudience; - } + validationParameters.AudienceValidator = ValidateAudience; + } - /// - /// Default validation of the audience: - /// - when registering an Azure AD web API in the app registration portal (and adding a scope) - /// the default App ID URI generated by the portal is api://{clientID} - /// - However, the audience (aud) of the token acquired to access this web API is different depending - /// on the "accepted access token version" for the web API: - /// - if accepted token version is 1.0, the audience provided in the token - /// by the Microsoft identity platform (formerly Azure AD v2.0) endpoint is: api://{ClientID} - /// - if the accepted token version is 2.0, the audience provided by Azure AD v2.0 in the token - /// is {CliendID} - /// When getting an access token for an Azure AD B2C web API the audience in the token is - /// api://{ClientID}. - /// - /// When web API developers don't provide the "Audience" in the configuration, Microsoft.Identity.Web - /// considers that this is the default App ID URI as explained above. When developer provides the - /// "Audience" member, it's available in the TokenValidationParameter.ValidAudience. - /// - /// Audiences in the security token. - /// Security token from which to validate the audiences. - /// Token validation parameters. - /// True if the token is valid; false, otherwise. - internal /*for test only*/ bool ValidateAudience( - IEnumerable audiences, - SecurityToken securityToken, - TokenValidationParameters validationParameters) - { - JwtSecurityToken? token = securityToken as JwtSecurityToken; - if (token == null) - { - throw new SecurityTokenValidationException(IDWebErrorMessage.TokenIsNotJwtToken); - } + /// + /// Default validation of the audience: + /// - when registering an Azure AD web API in the app registration portal (and adding a scope) + /// the default App ID URI generated by the portal is api://{clientID} + /// - However, the audience (aud) of the token acquired to access this web API is different depending + /// on the "accepted access token version" for the web API: + /// - if accepted token version is 1.0, the audience provided in the token + /// by the Microsoft identity platform (formerly Azure AD v2.0) endpoint is: api://{ClientID} + /// - if the accepted token version is 2.0, the audience provided by Azure AD v2.0 in the token + /// is {CliendID} + /// When getting an access token for an Azure AD B2C web API the audience in the token is + /// api://{ClientID}. + /// + /// When web API developers don't provide the "Audience" in the configuration, Microsoft.Identity.Web + /// considers that this is the default App ID URI as explained above. When developer provides the + /// "Audience" member, it's available in the TokenValidationParameter.ValidAudience. + /// + /// Audiences in the security token. + /// Security token from which to validate the audiences. + /// Token validation parameters. + /// True if the token is valid; false, otherwise. + internal /*for test only*/ bool ValidateAudience( + IEnumerable audiences, + SecurityToken securityToken, + TokenValidationParameters validationParameters) + { + JwtSecurityToken? token = securityToken as JwtSecurityToken; + if (token == null) + { + throw new SecurityTokenValidationException(IDWebErrorMessage.TokenIsNotJwtToken); + } - validationParameters.AudienceValidator = null; + validationParameters.AudienceValidator = null; - // Case of a default App ID URI (the developer did not provide explicit valid audience(s) - if (string.IsNullOrEmpty(validationParameters.ValidAudience) && - validationParameters.ValidAudiences == null) - { - // handle v2.0 access token or Azure AD B2C tokens (even if v1.0) - if (IsB2C || token.Claims.Any(c => c.Type == Constants.Version && c.Value == Constants.V2)) - { - validationParameters.ValidAudience = $"{ClientId}"; - } + // Case of a default App ID URI (the developer did not provide explicit valid audience(s) + if (string.IsNullOrEmpty(validationParameters.ValidAudience) && + validationParameters.ValidAudiences == null) + { + // handle v2.0 access token or Azure AD B2C tokens (even if v1.0) + if (IsB2C || token.Claims.Any(c => c.Type == Constants.Version && c.Value == Constants.V2)) + { + validationParameters.ValidAudience = $"{ClientId}"; + } - // handle v1.0 access token - else if (token.Claims.Any(c => c.Type == Constants.Version && c.Value == Constants.V1)) - { - validationParameters.ValidAudience = $"api://{ClientId}"; - } - } + // handle v1.0 access token + else if (token.Claims.Any(c => c.Type == Constants.Version && c.Value == Constants.V1)) + { + validationParameters.ValidAudience = $"api://{ClientId}"; + } + } - Validators.ValidateAudience(audiences, securityToken, validationParameters); - return true; - } - } + Validators.ValidateAudience(audiences, securityToken, validationParameters); + return true; + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/RequiredScopeAttribute.cs b/src/Microsoft.Identity.Web/Resource/RequiredScopeAttribute.cs index b2225f9e8..97b1a936a 100644 --- a/src/Microsoft.Identity.Web/Resource/RequiredScopeAttribute.cs +++ b/src/Microsoft.Identity.Web/Resource/RequiredScopeAttribute.cs @@ -5,68 +5,68 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// This attribute is used on a controller, pages, or controller actions - /// to declare (and validate) the scopes required by a web API. These scopes can be declared - /// in two ways: hardcoding them, or declaring them in the configuration. Depending on your - /// choice, use either one or the other of the constructors. - /// For details, see https://aka.ms/ms-id-web/required-scope-attribute. - /// - public class RequiredScopeAttribute : TypeFilterAttribute - { - /// - /// Fully qualified name of the configuration key containing the required scopes (separated - /// by spaces). - /// - /// - /// If the appsettings.json file contains a section named "AzureAd", in which - /// a property named "Scopes" contains the required scopes, the attribute on the - /// controller/page/action to protect should be set to the following: - /// - /// [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] - /// - /// - public string RequiredScopesConfigurationKey - { - get { return string.Empty; } - set { Arguments = new object[] { Constants.RequiredScopesSetting, value }; } - } + /// + /// This attribute is used on a controller, pages, or controller actions + /// to declare (and validate) the scopes required by a web API. These scopes can be declared + /// in two ways: hardcoding them, or declaring them in the configuration. Depending on your + /// choice, use either one or the other of the constructors. + /// For details, see https://aka.ms/ms-id-web/required-scope-attribute. + /// + public class RequiredScopeAttribute : TypeFilterAttribute + { + /// + /// Fully qualified name of the configuration key containing the required scopes (separated + /// by spaces). + /// + /// + /// If the appsettings.json file contains a section named "AzureAd", in which + /// a property named "Scopes" contains the required scopes, the attribute on the + /// controller/page/action to protect should be set to the following: + /// + /// [RequiredScope(RequiredScopesConfigurationKey="AzureAd:Scopes")] + /// + /// + public string RequiredScopesConfigurationKey + { + get { return string.Empty; } + set { Arguments = new object[] { Constants.RequiredScopesSetting, value }; } + } - /// - /// Verifies that the web API is called with the right scopes. - /// If the token obtained for this API is on behalf of the authenticated user does not have - /// any of these in its scope claim, the - /// method updates the HTTP response providing a status code 403 (Forbidden) - /// and writes to the response body a message telling which scopes are expected in the token. - /// - /// Scopes accepted by this web API. - /// When the scopes don't match, the response is a 403 (Forbidden), - /// because the user is authenticated (hence not 401), but not authorized. - /// - /// Add the following attribute on the controller/page/action to protect: - /// - /// - /// [RequiredScope("access_as_user")] - /// - /// - /// and - /// if you want to express the required scopes from the configuration. - public RequiredScopeAttribute(params string[] acceptedScopes) - : base(typeof(RequiredScopeFilter)) - { - Arguments = new object[] { acceptedScopes }; - IsReusable = true; - } + /// + /// Verifies that the web API is called with the right scopes. + /// If the token obtained for this API is on behalf of the authenticated user does not have + /// any of these in its scope claim, the + /// method updates the HTTP response providing a status code 403 (Forbidden) + /// and writes to the response body a message telling which scopes are expected in the token. + /// + /// Scopes accepted by this web API. + /// When the scopes don't match, the response is a 403 (Forbidden), + /// because the user is authenticated (hence not 401), but not authorized. + /// + /// Add the following attribute on the controller/page/action to protect: + /// + /// + /// [RequiredScope("access_as_user")] + /// + /// + /// and + /// if you want to express the required scopes from the configuration. + public RequiredScopeAttribute(params string[] acceptedScopes) + : base(typeof(RequiredScopeFilter)) + { + Arguments = new object[] { acceptedScopes }; + IsReusable = true; + } - /// - /// Default constructor, to be used along with the - /// property when you want to get the scopes to validate from the configuration, instead - /// of hardcoding them in the code. - /// - public RequiredScopeAttribute() - : base(typeof(RequiredScopeFilter)) - { - IsReusable = true; - } - } + /// + /// Default constructor, to be used along with the + /// property when you want to get the scopes to validate from the configuration, instead + /// of hardcoding them in the code. + /// + public RequiredScopeAttribute() + : base(typeof(RequiredScopeFilter)) + { + IsReusable = true; + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs b/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs index 45857d6e0..91c039640 100644 --- a/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs +++ b/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs @@ -13,108 +13,108 @@ namespace Microsoft.Identity.Web.Resource { - internal class RequiredScopeFilter : IAuthorizationFilter - { - internal readonly string[] _acceptedScopes; - internal string[]? _effectiveAcceptedScopes; + internal class RequiredScopeFilter : IAuthorizationFilter + { + internal readonly string[] _acceptedScopes; + internal string[]? _effectiveAcceptedScopes; - /// - /// If the authenticated user does not have any of these , the - /// method updates the HTTP response providing a status code 403 (Forbidden) - /// and writes to the response body a message telling which scopes are expected in the token. - /// - /// Scopes accepted by this web API. - /// When the scopes don't match, the response is a 403 (Forbidden), - /// because the user is authenticated (hence not 401), but not authorized. - public RequiredScopeFilter(params string[] acceptedScopes) - { - _acceptedScopes = acceptedScopes; - } + /// + /// If the authenticated user does not have any of these , the + /// method updates the HTTP response providing a status code 403 (Forbidden) + /// and writes to the response body a message telling which scopes are expected in the token. + /// + /// Scopes accepted by this web API. + /// When the scopes don't match, the response is a 403 (Forbidden), + /// because the user is authenticated (hence not 401), but not authorized. + public RequiredScopeFilter(params string[] acceptedScopes) + { + _acceptedScopes = acceptedScopes; + } - public void OnAuthorization(AuthorizationFilterContext context) - { - if (_acceptedScopes == null || _acceptedScopes.Length == 0) - { - throw new ArgumentNullException(nameof(_acceptedScopes)); - } + public void OnAuthorization(AuthorizationFilterContext context) + { + if (_acceptedScopes == null || _acceptedScopes.Length == 0) + { + throw new ArgumentNullException(nameof(_acceptedScopes)); + } - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } - GetEffectiveScopes(context); + GetEffectiveScopes(context); - ValidateEffectiveScopes(context); - } + ValidateEffectiveScopes(context); + } - private void ValidateEffectiveScopes(AuthorizationFilterContext context) - { - if (_effectiveAcceptedScopes == null || !_effectiveAcceptedScopes.Any()) - { - throw new InvalidOperationException(IDWebErrorMessage.MissingRequiredScopesForAuthorizationFilter); - } + private void ValidateEffectiveScopes(AuthorizationFilterContext context) + { + if (_effectiveAcceptedScopes == null || !_effectiveAcceptedScopes.Any()) + { + throw new InvalidOperationException(IDWebErrorMessage.MissingRequiredScopesForAuthorizationFilter); + } - if (context.HttpContext.User == null || context.HttpContext.User.Claims == null || !context.HttpContext.User.Claims.Any()) - { - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser); - } - else - { - // Attempt with Scp claim - Claim? scopeClaim = context.HttpContext.User.FindFirst(ClaimConstants.Scp); + if (context.HttpContext.User == null || context.HttpContext.User.Claims == null || !context.HttpContext.User.Claims.Any()) + { + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser); + } + else + { + // Attempt with Scp claim + Claim? scopeClaim = context.HttpContext.User.FindFirst(ClaimConstants.Scp); - // Fallback to Scope claim name - if (scopeClaim == null) - { - scopeClaim = context.HttpContext.User.FindFirst(ClaimConstants.Scope); - } + // Fallback to Scope claim name + if (scopeClaim == null) + { + scopeClaim = context.HttpContext.User.FindFirst(ClaimConstants.Scope); + } - if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(_effectiveAcceptedScopes).Any()) - { - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - string message = string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.MissingScopes, - string.Join(",", _effectiveAcceptedScopes)); - context.HttpContext.Response.WriteAsync(message); - context.HttpContext.Response.CompleteAsync(); - throw new UnauthorizedAccessException(message); - } - } - } + if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(_effectiveAcceptedScopes).Any()) + { + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + string message = string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.MissingScopes, + string.Join(",", _effectiveAcceptedScopes)); + context.HttpContext.Response.WriteAsync(message); + context.HttpContext.Response.CompleteAsync(); + throw new UnauthorizedAccessException(message); + } + } + } - private void GetEffectiveScopes(AuthorizationFilterContext context) - { - if (_effectiveAcceptedScopes == null) - { - if (_acceptedScopes.Length == 2 && _acceptedScopes[0] == Constants.RequiredScopesSetting) - { - string scopeConfigurationKeyName = _acceptedScopes[1]; + private void GetEffectiveScopes(AuthorizationFilterContext context) + { + if (_effectiveAcceptedScopes == null) + { + if (_acceptedScopes.Length == 2 && _acceptedScopes[0] == Constants.RequiredScopesSetting) + { + string scopeConfigurationKeyName = _acceptedScopes[1]; - if (!string.IsNullOrWhiteSpace(scopeConfigurationKeyName)) - { - // Load the injected IConfiguration - IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService(); + if (!string.IsNullOrWhiteSpace(scopeConfigurationKeyName)) + { + // Load the injected IConfiguration + IConfiguration configuration = context.HttpContext.RequestServices.GetRequiredService(); - if (configuration == null) - { - throw new InvalidOperationException( - string.Format( - CultureInfo.InvariantCulture, - IDWebErrorMessage.ScopeKeySectionIsProvidedButNotPresentInTheServicesCollection, - nameof(scopeConfigurationKeyName))); - } + if (configuration == null) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.ScopeKeySectionIsProvidedButNotPresentInTheServicesCollection, + nameof(scopeConfigurationKeyName))); + } - _effectiveAcceptedScopes = configuration.GetValue(scopeConfigurationKeyName)?.Split(' '); - } - } - else - { - _effectiveAcceptedScopes = _acceptedScopes; - } - } - } - } + _effectiveAcceptedScopes = configuration.GetValue(scopeConfigurationKeyName)?.Split(' '); + } + } + else + { + _effectiveAcceptedScopes = _acceptedScopes; + } + } + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs b/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs index c1ab2bb0c..dbf144864 100644 --- a/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs +++ b/src/Microsoft.Identity.Web/Resource/RolesRequiredHttpContextExtensions.cs @@ -11,52 +11,52 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Extension class providing the extension methods for that - /// can be used in web APIs to validate the roles in controller actions. - /// - public static class RolesRequiredHttpContextExtensions - { - /// - /// When applied to an , verifies that the application - /// has the expected roles. - /// - /// HttpContext (from the controller). - /// Roles accepted by this web API. - /// When the roles don't match, the response is a 403 (Forbidden), - /// because the app does not have the expected roles. - public static void ValidateAppRole(this HttpContext context, params string[] acceptedRoles) - { - if (acceptedRoles == null) - { - throw new ArgumentNullException(nameof(acceptedRoles)); - } + /// + /// Extension class providing the extension methods for that + /// can be used in web APIs to validate the roles in controller actions. + /// + public static class RolesRequiredHttpContextExtensions + { + /// + /// When applied to an , verifies that the application + /// has the expected roles. + /// + /// HttpContext (from the controller). + /// Roles accepted by this web API. + /// When the roles don't match, the response is a 403 (Forbidden), + /// because the app does not have the expected roles. + public static void ValidateAppRole(this HttpContext context, params string[] acceptedRoles) + { + if (acceptedRoles == null) + { + throw new ArgumentNullException(nameof(acceptedRoles)); + } - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - else if (context.User == null || context.User.Claims == null || !context.User.Claims.Any()) - { - context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser); - } - else - { - // Attempt with Roles claim - IEnumerable rolesClaim = context.User.Claims.Where( - c => c.Type == ClaimConstants.Roles || c.Type == ClaimConstants.Role) - .SelectMany(c => c.Value.Split(' ')); + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + else if (context.User == null || context.User.Claims == null || !context.User.Claims.Any()) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser); + } + else + { + // Attempt with Roles claim + IEnumerable rolesClaim = context.User.Claims.Where( + c => c.Type == ClaimConstants.Roles || c.Type == ClaimConstants.Role) + .SelectMany(c => c.Value.Split(' ')); - if (!rolesClaim.Intersect(acceptedRoles).Any()) - { - context.Response.StatusCode = (int)HttpStatusCode.Forbidden; - string message = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.MissingRoles, string.Join(", ", acceptedRoles)); - context.Response.WriteAsync(message); - context.Response.CompleteAsync(); - throw new UnauthorizedAccessException(message); - } - } - } - } + if (!rolesClaim.Intersect(acceptedRoles).Any()) + { + context.Response.StatusCode = (int)HttpStatusCode.Forbidden; + string message = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.MissingRoles, string.Join(", ", acceptedRoles)); + context.Response.WriteAsync(message); + context.Response.CompleteAsync(); + throw new UnauthorizedAccessException(message); + } + } + } + } } diff --git a/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs b/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs index 9045b31b2..d1eb5af90 100644 --- a/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs +++ b/src/Microsoft.Identity.Web/Resource/ScopesRequiredHttpContextExtensions.cs @@ -11,63 +11,63 @@ namespace Microsoft.Identity.Web.Resource { - /// - /// Extension class providing the extension - /// methods for that - /// can be used in web APIs to validate scopes in controller actions. - /// We recommend using instead the RequiredScope Attribute on the controller, the page or the action. - /// See https://aka.ms/ms-id-web/required-scope-attribute. - /// - public static class ScopesRequiredHttpContextExtensions - { - /// - /// When applied to an , verifies that the user authenticated in the - /// web API has any of the accepted scopes. - /// If there is no authenticated user, the response is a 401 (Unauthenticated). - /// If the authenticated user does not have any of these , the - /// method updates the HTTP response providing a status code 403 (Forbidden) - /// and writes to the response body a message telling which scopes are expected in the token. - /// We recommend using instead the RequiredScope Attribute on the controller, the page or the action. - /// See https://aka.ms/ms-id-web/required-scope-attribute. - /// - /// HttpContext (from the controller). - /// Scopes accepted by this web API. - public static void VerifyUserHasAnyAcceptedScope(this HttpContext context, params string[] acceptedScopes) - { - if (acceptedScopes == null) - { - throw new ArgumentNullException(nameof(acceptedScopes)); - } + /// + /// Extension class providing the extension + /// methods for that + /// can be used in web APIs to validate scopes in controller actions. + /// We recommend using instead the RequiredScope Attribute on the controller, the page or the action. + /// See https://aka.ms/ms-id-web/required-scope-attribute. + /// + public static class ScopesRequiredHttpContextExtensions + { + /// + /// When applied to an , verifies that the user authenticated in the + /// web API has any of the accepted scopes. + /// If there is no authenticated user, the response is a 401 (Unauthenticated). + /// If the authenticated user does not have any of these , the + /// method updates the HTTP response providing a status code 403 (Forbidden) + /// and writes to the response body a message telling which scopes are expected in the token. + /// We recommend using instead the RequiredScope Attribute on the controller, the page or the action. + /// See https://aka.ms/ms-id-web/required-scope-attribute. + /// + /// HttpContext (from the controller). + /// Scopes accepted by this web API. + public static void VerifyUserHasAnyAcceptedScope(this HttpContext context, params string[] acceptedScopes) + { + if (acceptedScopes == null) + { + throw new ArgumentNullException(nameof(acceptedScopes)); + } - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - else if (context.User == null || context.User.Claims == null || !context.User.Claims.Any()) - { - context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser); - } - else - { - // Attempt with Scp claim - Claim? scopeClaim = context.User.FindFirst(ClaimConstants.Scp); + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + else if (context.User == null || context.User.Claims == null || !context.User.Claims.Any()) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + throw new UnauthorizedAccessException(IDWebErrorMessage.UnauthenticatedUser); + } + else + { + // Attempt with Scp claim + Claim? scopeClaim = context.User.FindFirst(ClaimConstants.Scp); - // Fallback to Scope claim name - if (scopeClaim == null) - { - scopeClaim = context.User.FindFirst(ClaimConstants.Scope); - } + // Fallback to Scope claim name + if (scopeClaim == null) + { + scopeClaim = context.User.FindFirst(ClaimConstants.Scope); + } - if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(acceptedScopes).Any()) - { - context.Response.StatusCode = (int)HttpStatusCode.Forbidden; - string message = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.MissingScopes, string.Join(",", acceptedScopes)); - context.Response.WriteAsync(message); - context.Response.CompleteAsync(); - throw new UnauthorizedAccessException(message); - } - } - } - } + if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(acceptedScopes).Any()) + { + context.Response.StatusCode = (int)HttpStatusCode.Forbidden; + string message = string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.MissingScopes, string.Join(",", acceptedScopes)); + context.Response.WriteAsync(message); + context.Response.CompleteAsync(); + throw new UnauthorizedAccessException(message); + } + } + } + } } diff --git a/src/Microsoft.Identity.Web/ServiceCollectionExtensions.cs b/src/Microsoft.Identity.Web/ServiceCollectionExtensions.cs index ba933abad..5e87cc973 100644 --- a/src/Microsoft.Identity.Web/ServiceCollectionExtensions.cs +++ b/src/Microsoft.Identity.Web/ServiceCollectionExtensions.cs @@ -7,69 +7,69 @@ namespace Microsoft.Identity.Web { - /// - /// Extensions for IServiceCollection for startup initialization of web APIs. - /// - public static class ServiceCollectionExtensions - { - /// - /// Add the token acquisition service. - /// - /// Service collection. - /// Specifies if an instance of should be a singleton. - /// The service collection. - /// - /// This method is typically called from the ConfigureServices(IServiceCollection services) in Startup.cs. - /// Note that the implementation of the token cache can be chosen separately. - /// - /// - /// // Token acquisition service and its cache implementation as a session cache - /// services.AddTokenAcquisition() - /// .AddDistributedMemoryCache() - /// .AddSession() - /// .AddSessionBasedTokenCache(); - /// - /// - public static IServiceCollection AddTokenAcquisition( - this IServiceCollection services, - bool isTokenAcquisitionSingleton = false) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } + /// + /// Extensions for IServiceCollection for startup initialization of web APIs. + /// + public static class ServiceCollectionExtensions + { + /// + /// Add the token acquisition service. + /// + /// Service collection. + /// Specifies if an instance of should be a singleton. + /// The service collection. + /// + /// This method is typically called from the ConfigureServices(IServiceCollection services) in Startup.cs. + /// Note that the implementation of the token cache can be chosen separately. + /// + /// + /// // Token acquisition service and its cache implementation as a session cache + /// services.AddTokenAcquisition() + /// .AddDistributedMemoryCache() + /// .AddSession() + /// .AddSessionBasedTokenCache(); + /// + /// + public static IServiceCollection AddTokenAcquisition( + this IServiceCollection services, + bool isTokenAcquisitionSingleton = false) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } - ServiceDescriptor? tokenAcquisitionService = services.FirstOrDefault(s => s.ServiceType == typeof(ITokenAcquisition)); - ServiceDescriptor? tokenAcquisitionInternalService = services.FirstOrDefault(s => s.ServiceType == typeof(ITokenAcquisitionInternal)); - if (tokenAcquisitionService != null && tokenAcquisitionInternalService != null) - { - if (isTokenAcquisitionSingleton ^ (tokenAcquisitionService.Lifetime == ServiceLifetime.Singleton)) - { - // The service was already added, but not with the right lifetime - services.Remove(tokenAcquisitionService); - services.Remove(tokenAcquisitionInternalService); - } - else - { - // The service is already added with the right lifetime - return services; - } - } + ServiceDescriptor? tokenAcquisitionService = services.FirstOrDefault(s => s.ServiceType == typeof(ITokenAcquisition)); + ServiceDescriptor? tokenAcquisitionInternalService = services.FirstOrDefault(s => s.ServiceType == typeof(ITokenAcquisitionInternal)); + if (tokenAcquisitionService != null && tokenAcquisitionInternalService != null) + { + if (isTokenAcquisitionSingleton ^ (tokenAcquisitionService.Lifetime == ServiceLifetime.Singleton)) + { + // The service was already added, but not with the right lifetime + services.Remove(tokenAcquisitionService); + services.Remove(tokenAcquisitionInternalService); + } + else + { + // The service is already added with the right lifetime + return services; + } + } - // Token acquisition service - services.AddHttpContextAccessor(); - if (isTokenAcquisitionSingleton) - { - services.AddSingleton(); - services.AddSingleton(s => (ITokenAcquisitionInternal)s.GetRequiredService()); - } - else - { - services.AddScoped(); - services.AddScoped(s => (ITokenAcquisitionInternal)s.GetRequiredService()); - } + // Token acquisition service + services.AddHttpContextAccessor(); + if (isTokenAcquisitionSingleton) + { + services.AddSingleton(); + services.AddSingleton(s => (ITokenAcquisitionInternal)s.GetRequiredService()); + } + else + { + services.AddScoped(); + services.AddScoped(s => (ITokenAcquisitionInternal)s.GetRequiredService()); + } - return services; - } - } + return services; + } + } } diff --git a/src/Microsoft.Identity.Web/TempDataLoginErrorAccessor.cs b/src/Microsoft.Identity.Web/TempDataLoginErrorAccessor.cs index da5e6812f..ac72c15af 100644 --- a/src/Microsoft.Identity.Web/TempDataLoginErrorAccessor.cs +++ b/src/Microsoft.Identity.Web/TempDataLoginErrorAccessor.cs @@ -6,75 +6,75 @@ namespace Microsoft.Identity.Web { - /// - /// An implementation of that uses to track error messages. - /// - internal class TempDataLoginErrorAccessor : ILoginErrorAccessor - { - private const string Name = "MicrosoftIdentityError"; + /// + /// An implementation of that uses to track error messages. + /// + internal class TempDataLoginErrorAccessor : ILoginErrorAccessor + { + private const string Name = "MicrosoftIdentityError"; - private readonly ITempDataDictionaryFactory _factory; + private readonly ITempDataDictionaryFactory _factory; - private ITempDataDictionary? _tempData; + private ITempDataDictionary? _tempData; - public static ILoginErrorAccessor Create( - ITempDataDictionaryFactory? factory, - bool isDevelopment) - { - if (!isDevelopment || factory is null) - { - return new EmptyLoginErrorAccessor(); - } - else - { - return new TempDataLoginErrorAccessor(factory); - } - } + public static ILoginErrorAccessor Create( + ITempDataDictionaryFactory? factory, + bool isDevelopment) + { + if (!isDevelopment || factory is null) + { + return new EmptyLoginErrorAccessor(); + } + else + { + return new TempDataLoginErrorAccessor(factory); + } + } - private TempDataLoginErrorAccessor(ITempDataDictionaryFactory factory) - { - _factory = factory; - } + private TempDataLoginErrorAccessor(ITempDataDictionaryFactory factory) + { + _factory = factory; + } - public bool IsEnabled => true; + public bool IsEnabled => true; - public string? GetMessage(HttpContext context) - { - if (_tempData == null) - { - _tempData = _factory.GetTempData(context); - } + public string? GetMessage(HttpContext context) + { + if (_tempData == null) + { + _tempData = _factory.GetTempData(context); + } - if (_tempData.TryGetValue(Name, out var result) && result is string msg) - { - return msg; - } + if (_tempData.TryGetValue(Name, out var result) && result is string msg) + { + return msg; + } - return null; - } + return null; + } - public void SetMessage(HttpContext context, string? message) - { - if (message != null) - { - _tempData = _factory.GetTempData(context); + public void SetMessage(HttpContext context, string? message) + { + if (message != null) + { + _tempData = _factory.GetTempData(context); - _tempData.Add(Name, message); - _tempData.Save(); - } - } + _tempData.Add(Name, message); + _tempData.Save(); + } + } - private class EmptyLoginErrorAccessor : ILoginErrorAccessor - { - public bool IsEnabled => false; + private class EmptyLoginErrorAccessor : ILoginErrorAccessor + { + public bool IsEnabled => false; - public string? GetMessage(HttpContext context) - => null; + public string? GetMessage(HttpContext context) + => null; - public void SetMessage(HttpContext context, string? message) - { - // This is empty. - } - } - } + public void SetMessage(HttpContext context, string? message) + { + // This is empty. + } + } + } } diff --git a/src/Microsoft.Identity.Web/TokenAcquisition.cs b/src/Microsoft.Identity.Web/TokenAcquisition.cs index 9563a2132..3ace69173 100644 --- a/src/Microsoft.Identity.Web/TokenAcquisition.cs +++ b/src/Microsoft.Identity.Web/TokenAcquisition.cs @@ -24,806 +24,806 @@ namespace Microsoft.Identity.Web { - /// - /// Token acquisition service. - /// - internal class TokenAcquisition : ITokenAcquisitionInternal - { - private readonly MicrosoftIdentityOptions _microsoftIdentityOptions; - private readonly ConfidentialClientApplicationOptions _applicationOptions; - private readonly IMsalTokenCacheProvider _tokenCacheProvider; - - private IConfidentialClientApplication? _application; - private readonly IHttpContextAccessor _httpContextAccessor; - private HttpContext? CurrentHttpContext => _httpContextAccessor.HttpContext; - private readonly IMsalHttpClientFactory _httpClientFactory; - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - - /// - /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to - /// configure the confidential client application and a token cache provider. - /// This constructor is called by ASP.NET Core dependency injection. - /// - /// The App token cache provider. - /// Access to the HttpContext of the request. - /// Configuration options. - /// MSAL.NET configuration options. - /// HTTP client factory. - /// Logger. - /// Service provider. - public TokenAcquisition( - IMsalTokenCacheProvider tokenCacheProvider, - IHttpContextAccessor httpContextAccessor, - IOptions microsoftIdentityOptions, - IOptions applicationOptions, - IHttpClientFactory httpClientFactory, - ILogger logger, - IServiceProvider serviceProvider) - { - _httpContextAccessor = httpContextAccessor; - _microsoftIdentityOptions = microsoftIdentityOptions.Value; - _applicationOptions = applicationOptions.Value; - _tokenCacheProvider = tokenCacheProvider; - _httpClientFactory = new MsalAspNetCoreHttpClientFactory(httpClientFactory); - _logger = logger; - _serviceProvider = serviceProvider; - - _applicationOptions.ClientId ??= _microsoftIdentityOptions.ClientId; - _applicationOptions.Instance ??= _microsoftIdentityOptions.Instance; - _applicationOptions.ClientSecret ??= _microsoftIdentityOptions.ClientSecret; - _applicationOptions.TenantId ??= _microsoftIdentityOptions.TenantId; - _applicationOptions.LegacyCacheCompatibilityEnabled = _microsoftIdentityOptions.LegacyCacheCompatibilityEnabled; - DefaultCertificateLoader.UserAssignedManagedIdentityClientId = _microsoftIdentityOptions.UserAssignedManagedIdentityClientId; - } - - /// - /// Scopes which are already requested by MSAL.NET. They should not be re-requested;. - /// - private readonly string[] _scopesRequestedByMsal = new string[] - { - OidcConstants.ScopeOpenId, - OidcConstants.ScopeProfile, - OidcConstants.ScopeOfflineAccess, - }; - - /// - /// Meta-tenant identifiers which are not allowed in client credentials. - /// - private readonly ISet _metaTenantIdentifiers = new HashSet( - new[] - { - Constants.Common, - Constants.Organizations, - }, - StringComparer.OrdinalIgnoreCase); - - /// - /// This handler is executed after the authorization code is received (once the user signs-in and consents) during the - /// authorization code flow in a web app. - /// It uses the code to request an access token from the Microsoft identity platform and caches the tokens and an entry about the signed-in user's account in the MSAL's token cache. - /// The access token (and refresh token) provided in the , once added to the cache, are then used to acquire more tokens using the - /// on-behalf-of flow for the signed-in user's account, - /// in order to call to downstream APIs. - /// - /// The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. - /// scopes to request access to. - /// - /// From the configuration of the Authentication of the ASP.NET Core web API: - /// OpenIdConnectOptions options; - /// - /// Subscribe to the authorization code received event: - /// - /// options.Events = new OpenIdConnectEvents(); - /// options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; - /// } - /// - /// - /// And then in the OnAuthorizationCodeRecieved method, call : - /// - /// private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) - /// { - /// var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); - /// await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); - /// } - /// - /// - public async Task AddAccountToCacheFromAuthorizationCodeAsync( - AuthorizationCodeReceivedContext context, - IEnumerable scopes) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (scopes == null) - { - throw new ArgumentNullException(nameof(scopes)); - } - - try - { - _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); - - // Do not share the access token with ASP.NET Core otherwise ASP.NET will cache it and will not send the OAuth 2.0 request in - // case a further call to AcquireTokenByAuthorizationCodeAsync in the future is required for incremental consent (getting a code requesting more scopes) - // Share the ID token though - var builder = _application - .AcquireTokenByAuthorizationCode(scopes.Except(_scopesRequestedByMsal), context.ProtocolMessage.Code) - .WithSendX5C(_microsoftIdentityOptions.SendX5C); - - if (_microsoftIdentityOptions.IsB2C) - { - string? userFlow = context.Principal?.GetUserFlowId(); - var authority = $"{_applicationOptions.Instance}{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{userFlow ?? _microsoftIdentityOptions.DefaultUserFlow}"; - builder.WithB2CAuthority(authority); - } - - var result = await builder.ExecuteAsync() - .ConfigureAwait(false); - - context.HandleCodeRedemption(null, result.IdToken); - } - catch (MsalException ex) - { - _logger.LogInformation( - ex, - LogMessages.ExceptionOccurredWhenAddingAnAccountToTheCacheFromAuthCode); - throw; - } - } - - /// - /// Typically used from a web app or web API controller, this method retrieves an access token - /// for a downstream API using; - /// 1) the token cache (for web apps and web APIs) if a token exists in the cache - /// 2) or the on-behalf-of flow - /// in web APIs, for the user account that is ascertained from claims provided in the - /// instance of the current HttpContext. - /// - /// Scopes to request for the downstream API to call. - /// Enables overriding of the tenant/account for the same identity. This is useful in the - /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. - /// Azure AD B2C user flow to target. - /// Optional claims principal representing the user. If not provided, will use the signed-in - /// user (in a web app), or the user for which the token was received (in a web API) - /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. - /// Options passed-in to create the token acquisition options object which calls into MSAL .NET. - /// An access token to call the downstream API and populated with this downstream API's scopes. - /// Calling this method from a web API supposes that you have previously called, - /// in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method - /// passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that - /// you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by - /// OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. - public async Task GetAuthenticationResultForUserAsync( - IEnumerable scopes, - string? tenantId = null, - string? userFlow = null, - ClaimsPrincipal? user = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - if (scopes == null) - { - throw new ArgumentNullException(nameof(scopes)); - } - - user = await GetAuthenticatedUserAsync(user).ConfigureAwait(false); - - _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); - - string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenantId); - - AuthenticationResult? authenticationResult; - - try - { - // Access token will return if call is from a web API - authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync( - _application, - authority, - scopes, - tokenAcquisitionOptions).ConfigureAwait(false); - - if (authenticationResult != null) - { - return authenticationResult; - } - - // If access token is null, this is a web app - return await GetAuthenticationResultForWebAppWithAccountFromCacheAsync( - _application, - user, - scopes, - authority, - userFlow) - .ConfigureAwait(false); - } - catch (MsalUiRequiredException ex) - { - // GetAccessTokenForUserAsync is an abstraction that can be called from a web app or a web API - _logger.LogInformation(ex.Message); - - // Case of the web app: we let the MsalUiRequiredException be caught by the - // AuthorizeForScopesAttribute exception filter so that the user can consent, do 2FA, etc ... - throw new MicrosoftIdentityWebChallengeUserException(ex, scopes.ToArray(), userFlow); - } - } - - /// - /// Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) - /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - /// - /// The scope requested to access a protected API. For this flow (client credentials), the scope - /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use - /// several calls to get tokens for other resources). - /// Enables overriding of the tenant/account for the same identity. This is useful - /// for multi tenant apps or daemons. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// An authentication result for the app itself, based on its scopes. - public async Task GetAuthenticationResultForAppAsync( - string scope, - string? tenant = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - if (string.IsNullOrEmpty(scope)) - { - throw new ArgumentNullException(nameof(scope)); - } - - if (!scope.EndsWith("/.default", true, CultureInfo.InvariantCulture)) - { - throw new ArgumentException(IDWebErrorMessage.ClientCredentialScopeParameterShouldEndInDotDefault, nameof(scope)); - } - - if (string.IsNullOrEmpty(tenant)) - { - tenant = _applicationOptions.TenantId; - } - - if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant)) - { - throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant)); - } - - // Use MSAL to get the right token to call the API - _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); - string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenant); - - AuthenticationResult result; - var builder = _application - .AcquireTokenForClient(new string[] { scope }.Except(_scopesRequestedByMsal)) - .WithSendX5C(_microsoftIdentityOptions.SendX5C) - .WithAuthority(authority); - - if (tokenAcquisitionOptions != null) - { - builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); - builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); - builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); - builder.WithClaims(tokenAcquisitionOptions.Claims); - if (tokenAcquisitionOptions.PoPConfiguration != null) - { - builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); - } - } - - result = await builder.ExecuteAsync() - .ConfigureAwait(false); - return result; - } - - /// - /// Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) - /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. - /// - /// The scope requested to access a protected API. For this flow (client credentials), the scope - /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft - /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration - /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use - /// several calls to get tokens for other resources). - /// Enables overriding of the tenant/account for the same identity. This is useful - /// for multi tenant apps or daemons. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// An access token for the app itself, based on its scopes. - public async Task GetAccessTokenForAppAsync( - string scope, - string? tenant = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - AuthenticationResult authResult = await GetAuthenticationResultForAppAsync(scope, tenant, tokenAcquisitionOptions).ConfigureAwait(false); - return authResult.AccessToken; - } - - /// - /// Typically used from a web app or web API controller, this method retrieves an access token - /// for a downstream API using; - /// 1) the token cache (for web apps and web APIs) if a token exists in the cache - /// 2) or the on-behalf-of flow - /// in web APIs, for the user account that is ascertained from the claims provided in the - /// instance of the current HttpContext. - /// - /// Scopes to request for the downstream API to call. - /// Enables overriding of the tenant/account for the same identity. This is useful in the - /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - /// Azure AD B2C user flow to target. - /// Optional claims principal representing the user. If not provided, will use the signed-in - /// user (in a web app), or the user for which the token was received (in a web API) - /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// An access token to call the downstream API and populated with this downstream API's scopes. - /// Calling this method from a web API supposes that you have previously called, - /// in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method - /// passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that - /// you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by - /// OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. - public async Task GetAccessTokenForUserAsync( - IEnumerable scopes, - string? tenantId = null, - string? userFlow = null, - ClaimsPrincipal? user = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - AuthenticationResult result = - await GetAuthenticationResultForUserAsync( - scopes, - tenantId, - userFlow, - user, - tokenAcquisitionOptions).ConfigureAwait(false); - return result.AccessToken; - } - - /// - /// Used in web APIs (no user interaction). - /// Replies to the client through the HTTP response by sending a 403 (forbidden) and populating the 'WWW-Authenticate' header so that - /// the client, in turn, can trigger a user interaction so that the user consents to more scopes. - /// - /// Scopes to consent to. - /// The that triggered the challenge. - /// The to update. - /// A representing the asynchronous operation. - public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null) - { - // A user interaction is required, but we are in a web API, and therefore, we need to report back to the client through a 'WWW-Authenticate' header https://tools.ietf.org/html/rfc6750#section-3.1 - string proposedAction = Constants.Consent; - if (msalServiceException.ErrorCode == MsalError.InvalidGrantError && AcceptedTokenVersionMismatch(msalServiceException)) - { - throw msalServiceException; - } - - _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); - - string consentUrl = $"{_application.Authority}/oauth2/v2.0/authorize?client_id={_applicationOptions.ClientId}" - + $"&response_type=code&redirect_uri={_application.AppConfig.RedirectUri}" - + $"&response_mode=query&scope=offline_access%20{string.Join("%20", scopes)}"; - - IDictionary parameters = new Dictionary() - { - { Constants.ConsentUrl, consentUrl }, - { Constants.Claims, msalServiceException.Claims }, - { Constants.Scopes, string.Join(",", scopes) }, - { Constants.ProposedAction, proposedAction }, - }; - - string parameterString = string.Join(", ", parameters.Select(p => $"{p.Key}=\"{p.Value}\"")); - - httpResponse ??= CurrentHttpContext?.Response; - - if (httpResponse == null) - { - throw new InvalidOperationException(IDWebErrorMessage.HttpContextAndHttpResponseAreNull); - } - - var headers = httpResponse.Headers; - httpResponse.StatusCode = (int)HttpStatusCode.Forbidden; - - headers[HeaderNames.WWWAuthenticate] = new StringValues($"{Constants.Bearer} {parameterString}"); - } - - /// - /// Removes the account associated with context.HttpContext.User from the MSAL.NET cache. - /// - /// RedirectContext passed-in to a - /// OpenID Connect event. - /// A that represents a completed account removal operation. - public async Task RemoveAccountAsync(RedirectContext context) - { - ClaimsPrincipal user = context.HttpContext.User; - string? userId = user.GetMsalAccountId(); - if (!string.IsNullOrEmpty(userId)) - { - IConfidentialClientApplication app = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); - - if (_microsoftIdentityOptions.IsB2C) - { - await _tokenCacheProvider.ClearAsync(userId).ConfigureAwait(false); - } - else - { - string? identifier = context.HttpContext.User.GetMsalAccountId(); - IAccount account = await app.GetAccountAsync(identifier).ConfigureAwait(false); - - if (account != null) - { - await app.RemoveAsync(account).ConfigureAwait(false); - await _tokenCacheProvider.ClearAsync(userId).ConfigureAwait(false); - } - } - } - } - - /// - /// Creates an MSAL confidential client application, if needed. - /// - internal /* for testing */ async Task GetOrBuildConfidentialClientApplicationAsync() - { - if (_application == null) - { - return await BuildConfidentialClientApplicationAsync().ConfigureAwait(false); - } - - return _application; - } - - /// - /// Creates an MSAL confidential client application. - /// - private async Task BuildConfidentialClientApplicationAsync() - { - var request = CurrentHttpContext?.Request; - string? currentUri = null; - - if (!string.IsNullOrEmpty(_applicationOptions.RedirectUri)) - { - currentUri = _applicationOptions.RedirectUri; - } - - if (request != null && string.IsNullOrEmpty(currentUri)) - { - currentUri = UriHelper.BuildAbsolute( - request.Scheme, - request.Host, - request.PathBase, - _microsoftIdentityOptions.CallbackPath.Value ?? string.Empty); - } - - PrepareAuthorityInstanceForMsal(); - - MicrosoftIdentityOptionsValidation.ValidateEitherClientCertificateOrClientSecret( - _applicationOptions.ClientSecret, - _microsoftIdentityOptions.ClientCertificates); - - try - { - var builder = ConfidentialClientApplicationBuilder - .CreateWithApplicationOptions(_applicationOptions) - .WithHttpClientFactory(_httpClientFactory) - .WithLogging( - Log, - ConvertMicrosoftExtensionsLogLevelToMsal(_logger), - enablePiiLogging: _applicationOptions.EnablePiiLogging) - .WithExperimentalFeatures(); - - // The redirect URI is not needed for OBO - if (!string.IsNullOrEmpty(currentUri)) - { - builder.WithRedirectUri(currentUri); - } - - string authority; - - if (_microsoftIdentityOptions.IsB2C) - { - authority = $"{_applicationOptions.Instance}{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{_microsoftIdentityOptions.DefaultUserFlow}"; - builder.WithB2CAuthority(authority); - } - else - { - authority = $"{_applicationOptions.Instance}{_applicationOptions.TenantId}/"; - builder.WithAuthority(authority); - } - - if (_microsoftIdentityOptions.ClientCertificates != null) - { - X509Certificate2? certificate = DefaultCertificateLoader.LoadFirstCertificate(_microsoftIdentityOptions.ClientCertificates); - builder.WithCertificate(certificate); - } - - IConfidentialClientApplication app = builder.Build(); - _application = app; - // Initialize token cache providers - await _tokenCacheProvider.InitializeAsync(app.AppTokenCache).ConfigureAwait(false); - await _tokenCacheProvider.InitializeAsync(app.UserTokenCache).ConfigureAwait(false); - return app; - } - catch (Exception ex) - { - _logger.LogInformation( - ex, - IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient); - throw; - } - } - - private void PrepareAuthorityInstanceForMsal() - { - if (_microsoftIdentityOptions.IsB2C && _applicationOptions.Instance.EndsWith("/tfp/")) - { - _applicationOptions.Instance = _applicationOptions.Instance.Replace("/tfp/", string.Empty).TrimEnd('/') + "/"; - } - else - { - _applicationOptions.Instance = _applicationOptions.Instance.TrimEnd('/') + "/"; - } - } - - private async Task GetAuthenticationResultForWebApiToCallDownstreamApiAsync( - IConfidentialClientApplication application, - string authority, - IEnumerable scopes, - TokenAcquisitionOptions? tokenAcquisitionOptions) - { - try - { - // In web API, validatedToken will not be null - JwtSecurityToken? validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI(); - - // Case of web APIs: we need to do an on-behalf-of flow, with the token used to call the API - if (validatedToken != null) - { - // In the case the token is a JWE (encrypted token), we use the decrypted token. - string tokenUsedToCallTheWebApi = validatedToken.InnerToken == null ? validatedToken.RawData - : validatedToken.InnerToken.RawData; - var builder = application - .AcquireTokenOnBehalfOf( - scopes.Except(_scopesRequestedByMsal), - new UserAssertion(tokenUsedToCallTheWebApi)) - .WithSendX5C(_microsoftIdentityOptions.SendX5C) - .WithAuthority(authority); - - if (tokenAcquisitionOptions != null) - { - builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); - builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); - builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); - builder.WithClaims(tokenAcquisitionOptions.Claims); - if (tokenAcquisitionOptions.PoPConfiguration != null) - { - builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); - } - } - - return await builder.ExecuteAsync() - .ConfigureAwait(false); - } - - return null; - } - catch (MsalUiRequiredException ex) - { - _logger.LogInformation( - string.Format( - CultureInfo.InvariantCulture, - LogMessages.ErrorAcquiringTokenForDownstreamWebApi, - ex.Message)); - throw; - } - } - - /// - /// Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal. - /// - /// . - /// Claims principal for the user on behalf of whom to get a token. - /// Scopes for the downstream API to call. - /// (optional) Authority based on a specific tenant for which to acquire a token to access the scopes - /// on behalf of the user described in the claimsPrincipal. - /// Azure AD B2C user flow to target. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - private async Task GetAuthenticationResultForWebAppWithAccountFromCacheAsync( - IConfidentialClientApplication application, - ClaimsPrincipal? claimsPrincipal, - IEnumerable scopes, - string? authority, - string? userFlow = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - IAccount? account = null; - if (_microsoftIdentityOptions.IsB2C && !string.IsNullOrEmpty(userFlow)) - { - string? nameIdentifierId = claimsPrincipal?.GetNameIdentifierId(); - string? utid = claimsPrincipal?.GetHomeTenantId(); - string? b2cAccountIdentifier = string.Format(CultureInfo.InvariantCulture, "{0}-{1}.{2}", nameIdentifierId, userFlow, utid); - account = await application.GetAccountAsync(b2cAccountIdentifier).ConfigureAwait(false); - } - else - { - string? accountIdentifier = claimsPrincipal?.GetMsalAccountId(); - - if (accountIdentifier != null) - { - account = await application.GetAccountAsync(accountIdentifier).ConfigureAwait(false); - } - } - - return await GetAuthenticationResultForWebAppWithAccountFromCacheAsync( - application, - account, - scopes, - authority, - userFlow, - tokenAcquisitionOptions).ConfigureAwait(false); - } - - /// - /// Gets an access token for a downstream API on behalf of the user whose account is passed as an argument. - /// - /// . - /// User IAccount for which to acquire a token. - /// See . - /// Scopes for the downstream API to call. - /// Authority based on a specific tenant for which to acquire a token to access the scopes - /// on behalf of the user. - /// Azure AD B2C user flow. - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - private async Task GetAuthenticationResultForWebAppWithAccountFromCacheAsync( - IConfidentialClientApplication application, - IAccount? account, - IEnumerable scopes, - string? authority, - string? userFlow = null, - TokenAcquisitionOptions? tokenAcquisitionOptions = null) - { - if (scopes == null) - { - throw new ArgumentNullException(nameof(scopes)); - } - - var builder = application - .AcquireTokenSilent(scopes.Except(_scopesRequestedByMsal), account) - .WithSendX5C(_microsoftIdentityOptions.SendX5C); - - if (tokenAcquisitionOptions != null) - { - builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); - builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); - builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); - builder.WithClaims(tokenAcquisitionOptions.Claims); - if (tokenAcquisitionOptions.PoPConfiguration != null) - { - builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); - } - } - - // Acquire an access token as a B2C authority - if (_microsoftIdentityOptions.IsB2C) - { - string b2cAuthority = application.Authority.Replace( - new Uri(application.Authority).PathAndQuery, - $"/{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{userFlow ?? _microsoftIdentityOptions.DefaultUserFlow}"); - - builder.WithB2CAuthority(b2cAuthority) - .WithSendX5C(_microsoftIdentityOptions.SendX5C); - } - else - { - builder.WithAuthority(authority); - } - - return await builder.ExecuteAsync() - .ConfigureAwait(false); - } - - private static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalServiceException) - { - // Normally app developers should not make decisions based on the internal AAD code - // however until the STS sends sub-error codes for this error, this is the only - // way to distinguish the case. - // This is subject to change in the future - return msalServiceException.Message.Contains( - ErrorCodes.B2CPasswordResetErrorCode, - StringComparison.InvariantCulture); - } - - private async Task GetAuthenticatedUserAsync(ClaimsPrincipal? user) - { - if (user == null && _httpContextAccessor.HttpContext?.User != null) - { - user = _httpContextAccessor.HttpContext.User; - } - - if (user == null) - { - try - { - AuthenticationStateProvider? authenticationStateProvider = - _serviceProvider.GetService(typeof(AuthenticationStateProvider)) - as AuthenticationStateProvider; - - if (authenticationStateProvider != null) - { - // AuthenticationState provider is only available in Blazor - AuthenticationState state = await authenticationStateProvider.GetAuthenticationStateAsync().ConfigureAwait(false); - user = state.User; - } - } - catch - { - // do nothing. - } - } - - return user; - } - - internal /*for tests*/ string CreateAuthorityBasedOnTenantIfProvided( - IConfidentialClientApplication application, - string? tenant) - { - string authority; - if (!string.IsNullOrEmpty(tenant)) - { - authority = application.Authority.Replace(new Uri(application.Authority).PathAndQuery, $"/{tenant}/"); - } - else - { - authority = application.Authority; - } - - return authority; - } - - private void Log( - Client.LogLevel level, - string message, - bool containsPii) - { - switch (level) - { - case Client.LogLevel.Error: - _logger.LogError(message); - break; - case Client.LogLevel.Warning: - _logger.LogWarning(message); - break; - case Client.LogLevel.Info: - _logger.LogInformation(message); - break; - case Client.LogLevel.Verbose: - _logger.LogDebug(message); - break; - default: - break; - } - } - - private Client.LogLevel? ConvertMicrosoftExtensionsLogLevelToMsal(ILogger logger) - { - if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information)) - { - return Client.LogLevel.Info; - } - else if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) - || logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Trace)) - { - return Client.LogLevel.Verbose; - } - else if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning)) - { - return Client.LogLevel.Warning; - } - else if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error) - || logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Critical)) - { - return Client.LogLevel.Error; - } - else - { - return null; - } - } - } + /// + /// Token acquisition service. + /// + internal class TokenAcquisition : ITokenAcquisitionInternal + { + private readonly MicrosoftIdentityOptions _microsoftIdentityOptions; + private readonly ConfidentialClientApplicationOptions _applicationOptions; + private readonly IMsalTokenCacheProvider _tokenCacheProvider; + + private IConfidentialClientApplication? _application; + private readonly IHttpContextAccessor _httpContextAccessor; + private HttpContext? CurrentHttpContext => _httpContextAccessor.HttpContext; + private readonly IMsalHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + /// + /// Constructor of the TokenAcquisition service. This requires the Azure AD Options to + /// configure the confidential client application and a token cache provider. + /// This constructor is called by ASP.NET Core dependency injection. + /// + /// The App token cache provider. + /// Access to the HttpContext of the request. + /// Configuration options. + /// MSAL.NET configuration options. + /// HTTP client factory. + /// Logger. + /// Service provider. + public TokenAcquisition( + IMsalTokenCacheProvider tokenCacheProvider, + IHttpContextAccessor httpContextAccessor, + IOptions microsoftIdentityOptions, + IOptions applicationOptions, + IHttpClientFactory httpClientFactory, + ILogger logger, + IServiceProvider serviceProvider) + { + _httpContextAccessor = httpContextAccessor; + _microsoftIdentityOptions = microsoftIdentityOptions.Value; + _applicationOptions = applicationOptions.Value; + _tokenCacheProvider = tokenCacheProvider; + _httpClientFactory = new MsalAspNetCoreHttpClientFactory(httpClientFactory); + _logger = logger; + _serviceProvider = serviceProvider; + + _applicationOptions.ClientId ??= _microsoftIdentityOptions.ClientId; + _applicationOptions.Instance ??= _microsoftIdentityOptions.Instance; + _applicationOptions.ClientSecret ??= _microsoftIdentityOptions.ClientSecret; + _applicationOptions.TenantId ??= _microsoftIdentityOptions.TenantId; + _applicationOptions.LegacyCacheCompatibilityEnabled = _microsoftIdentityOptions.LegacyCacheCompatibilityEnabled; + DefaultCertificateLoader.UserAssignedManagedIdentityClientId = _microsoftIdentityOptions.UserAssignedManagedIdentityClientId; + } + + /// + /// Scopes which are already requested by MSAL.NET. They should not be re-requested;. + /// + private readonly string[] _scopesRequestedByMsal = new string[] + { + OidcConstants.ScopeOpenId, + OidcConstants.ScopeProfile, + OidcConstants.ScopeOfflineAccess, + }; + + /// + /// Meta-tenant identifiers which are not allowed in client credentials. + /// + private readonly ISet _metaTenantIdentifiers = new HashSet( + new[] + { + Constants.Common, + Constants.Organizations, + }, + StringComparer.OrdinalIgnoreCase); + + /// + /// This handler is executed after the authorization code is received (once the user signs-in and consents) during the + /// authorization code flow in a web app. + /// It uses the code to request an access token from the Microsoft identity platform and caches the tokens and an entry about the signed-in user's account in the MSAL's token cache. + /// The access token (and refresh token) provided in the , once added to the cache, are then used to acquire more tokens using the + /// on-behalf-of flow for the signed-in user's account, + /// in order to call to downstream APIs. + /// + /// The context used when an 'AuthorizationCode' is received over the OpenIdConnect protocol. + /// scopes to request access to. + /// + /// From the configuration of the Authentication of the ASP.NET Core web API: + /// OpenIdConnectOptions options; + /// + /// Subscribe to the authorization code received event: + /// + /// options.Events = new OpenIdConnectEvents(); + /// options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; + /// } + /// + /// + /// And then in the OnAuthorizationCodeRecieved method, call : + /// + /// private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) + /// { + /// var tokenAcquisition = context.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>(); + /// await _tokenAcquisition.AddAccountToCacheFromAuthorizationCode(context, new string[] { "user.read" }); + /// } + /// + /// + public async Task AddAccountToCacheFromAuthorizationCodeAsync( + AuthorizationCodeReceivedContext context, + IEnumerable scopes) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (scopes == null) + { + throw new ArgumentNullException(nameof(scopes)); + } + + try + { + _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); + + // Do not share the access token with ASP.NET Core otherwise ASP.NET will cache it and will not send the OAuth 2.0 request in + // case a further call to AcquireTokenByAuthorizationCodeAsync in the future is required for incremental consent (getting a code requesting more scopes) + // Share the ID token though + var builder = _application + .AcquireTokenByAuthorizationCode(scopes.Except(_scopesRequestedByMsal), context.ProtocolMessage.Code) + .WithSendX5C(_microsoftIdentityOptions.SendX5C); + + if (_microsoftIdentityOptions.IsB2C) + { + string? userFlow = context.Principal?.GetUserFlowId(); + var authority = $"{_applicationOptions.Instance}{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{userFlow ?? _microsoftIdentityOptions.DefaultUserFlow}"; + builder.WithB2CAuthority(authority); + } + + var result = await builder.ExecuteAsync() + .ConfigureAwait(false); + + context.HandleCodeRedemption(null, result.IdToken); + } + catch (MsalException ex) + { + _logger.LogInformation( + ex, + LogMessages.ExceptionOccurredWhenAddingAnAccountToTheCacheFromAuthCode); + throw; + } + } + + /// + /// Typically used from a web app or web API controller, this method retrieves an access token + /// for a downstream API using; + /// 1) the token cache (for web apps and web APIs) if a token exists in the cache + /// 2) or the on-behalf-of flow + /// in web APIs, for the user account that is ascertained from claims provided in the + /// instance of the current HttpContext. + /// + /// Scopes to request for the downstream API to call. + /// Enables overriding of the tenant/account for the same identity. This is useful in the + /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. + /// Azure AD B2C user flow to target. + /// Optional claims principal representing the user. If not provided, will use the signed-in + /// user (in a web app), or the user for which the token was received (in a web API) + /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant, like where the user is a guest. + /// Options passed-in to create the token acquisition options object which calls into MSAL .NET. + /// An access token to call the downstream API and populated with this downstream API's scopes. + /// Calling this method from a web API supposes that you have previously called, + /// in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method + /// passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that + /// you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by + /// OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. + public async Task GetAuthenticationResultForUserAsync( + IEnumerable scopes, + string? tenantId = null, + string? userFlow = null, + ClaimsPrincipal? user = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + if (scopes == null) + { + throw new ArgumentNullException(nameof(scopes)); + } + + user = await GetAuthenticatedUserAsync(user).ConfigureAwait(false); + + _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); + + string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenantId); + + AuthenticationResult? authenticationResult; + + try + { + // Access token will return if call is from a web API + authenticationResult = await GetAuthenticationResultForWebApiToCallDownstreamApiAsync( + _application, + authority, + scopes, + tokenAcquisitionOptions).ConfigureAwait(false); + + if (authenticationResult != null) + { + return authenticationResult; + } + + // If access token is null, this is a web app + return await GetAuthenticationResultForWebAppWithAccountFromCacheAsync( + _application, + user, + scopes, + authority, + userFlow) + .ConfigureAwait(false); + } + catch (MsalUiRequiredException ex) + { + // GetAccessTokenForUserAsync is an abstraction that can be called from a web app or a web API + _logger.LogInformation(ex.Message); + + // Case of the web app: we let the MsalUiRequiredException be caught by the + // AuthorizeForScopesAttribute exception filter so that the user can consent, do 2FA, etc ... + throw new MicrosoftIdentityWebChallengeUserException(ex, scopes.ToArray(), userFlow); + } + } + + /// + /// Acquires an authentication result from the authority configured in the app, for the confidential client itself (not on behalf of a user) + /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + /// + /// The scope requested to access a protected API. For this flow (client credentials), the scope + /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + /// several calls to get tokens for other resources). + /// Enables overriding of the tenant/account for the same identity. This is useful + /// for multi tenant apps or daemons. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// An authentication result for the app itself, based on its scopes. + public async Task GetAuthenticationResultForAppAsync( + string scope, + string? tenant = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + if (string.IsNullOrEmpty(scope)) + { + throw new ArgumentNullException(nameof(scope)); + } + + if (!scope.EndsWith("/.default", true, CultureInfo.InvariantCulture)) + { + throw new ArgumentException(IDWebErrorMessage.ClientCredentialScopeParameterShouldEndInDotDefault, nameof(scope)); + } + + if (string.IsNullOrEmpty(tenant)) + { + tenant = _applicationOptions.TenantId; + } + + if (!string.IsNullOrEmpty(tenant) && _metaTenantIdentifiers.Contains(tenant)) + { + throw new ArgumentException(IDWebErrorMessage.ClientCredentialTenantShouldBeTenanted, nameof(tenant)); + } + + // Use MSAL to get the right token to call the API + _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); + string authority = CreateAuthorityBasedOnTenantIfProvided(_application, tenant); + + AuthenticationResult result; + var builder = _application + .AcquireTokenForClient(new string[] { scope }.Except(_scopesRequestedByMsal)) + .WithSendX5C(_microsoftIdentityOptions.SendX5C) + .WithAuthority(authority); + + if (tokenAcquisitionOptions != null) + { + builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); + builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); + builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); + builder.WithClaims(tokenAcquisitionOptions.Claims); + if (tokenAcquisitionOptions.PoPConfiguration != null) + { + builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); + } + } + + result = await builder.ExecuteAsync() + .ConfigureAwait(false); + return result; + } + + /// + /// Acquires a token from the authority configured in the app, for the confidential client itself (not on behalf of a user) + /// using the client credentials flow. See https://aka.ms/msal-net-client-credentials. + /// + /// The scope requested to access a protected API. For this flow (client credentials), the scope + /// should be of the form "{ResourceIdUri/.default}" for instance https://management.azure.net/.default or, for Microsoft + /// Graph, https://graph.microsoft.com/.default as the requested scopes are defined statically with the application registration + /// in the portal, and cannot be overridden in the application, as you can request a token for only one resource at a time (use + /// several calls to get tokens for other resources). + /// Enables overriding of the tenant/account for the same identity. This is useful + /// for multi tenant apps or daemons. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// An access token for the app itself, based on its scopes. + public async Task GetAccessTokenForAppAsync( + string scope, + string? tenant = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + AuthenticationResult authResult = await GetAuthenticationResultForAppAsync(scope, tenant, tokenAcquisitionOptions).ConfigureAwait(false); + return authResult.AccessToken; + } + + /// + /// Typically used from a web app or web API controller, this method retrieves an access token + /// for a downstream API using; + /// 1) the token cache (for web apps and web APIs) if a token exists in the cache + /// 2) or the on-behalf-of flow + /// in web APIs, for the user account that is ascertained from the claims provided in the + /// instance of the current HttpContext. + /// + /// Scopes to request for the downstream API to call. + /// Enables overriding of the tenant/account for the same identity. This is useful in the + /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + /// Azure AD B2C user flow to target. + /// Optional claims principal representing the user. If not provided, will use the signed-in + /// user (in a web app), or the user for which the token was received (in a web API) + /// cases where a given account is a guest in other tenants, and you want to acquire tokens for a specific tenant. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// An access token to call the downstream API and populated with this downstream API's scopes. + /// Calling this method from a web API supposes that you have previously called, + /// in a method called by JwtBearerOptions.Events.OnTokenValidated, the HttpContextExtensions.StoreTokenUsedToCallWebAPI method + /// passing the validated token (as a JwtSecurityToken). Calling it from a web app supposes that + /// you have previously called AddAccountToCacheFromAuthorizationCodeAsync from a method called by + /// OpenIdConnectOptions.Events.OnAuthorizationCodeReceived. + public async Task GetAccessTokenForUserAsync( + IEnumerable scopes, + string? tenantId = null, + string? userFlow = null, + ClaimsPrincipal? user = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + AuthenticationResult result = + await GetAuthenticationResultForUserAsync( + scopes, + tenantId, + userFlow, + user, + tokenAcquisitionOptions).ConfigureAwait(false); + return result.AccessToken; + } + + /// + /// Used in web APIs (no user interaction). + /// Replies to the client through the HTTP response by sending a 403 (forbidden) and populating the 'WWW-Authenticate' header so that + /// the client, in turn, can trigger a user interaction so that the user consents to more scopes. + /// + /// Scopes to consent to. + /// The that triggered the challenge. + /// The to update. + /// A representing the asynchronous operation. + public async Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null) + { + // A user interaction is required, but we are in a web API, and therefore, we need to report back to the client through a 'WWW-Authenticate' header https://tools.ietf.org/html/rfc6750#section-3.1 + string proposedAction = Constants.Consent; + if (msalServiceException.ErrorCode == MsalError.InvalidGrantError && AcceptedTokenVersionMismatch(msalServiceException)) + { + throw msalServiceException; + } + + _application = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); + + string consentUrl = $"{_application.Authority}/oauth2/v2.0/authorize?client_id={_applicationOptions.ClientId}" + + $"&response_type=code&redirect_uri={_application.AppConfig.RedirectUri}" + + $"&response_mode=query&scope=offline_access%20{string.Join("%20", scopes)}"; + + IDictionary parameters = new Dictionary() + { + { Constants.ConsentUrl, consentUrl }, + { Constants.Claims, msalServiceException.Claims }, + { Constants.Scopes, string.Join(",", scopes) }, + { Constants.ProposedAction, proposedAction }, + }; + + string parameterString = string.Join(", ", parameters.Select(p => $"{p.Key}=\"{p.Value}\"")); + + httpResponse ??= CurrentHttpContext?.Response; + + if (httpResponse == null) + { + throw new InvalidOperationException(IDWebErrorMessage.HttpContextAndHttpResponseAreNull); + } + + var headers = httpResponse.Headers; + httpResponse.StatusCode = (int)HttpStatusCode.Forbidden; + + headers[HeaderNames.WWWAuthenticate] = new StringValues($"{Constants.Bearer} {parameterString}"); + } + + /// + /// Removes the account associated with context.HttpContext.User from the MSAL.NET cache. + /// + /// RedirectContext passed-in to a + /// OpenID Connect event. + /// A that represents a completed account removal operation. + public async Task RemoveAccountAsync(RedirectContext context) + { + ClaimsPrincipal user = context.HttpContext.User; + string? userId = user.GetMsalAccountId(); + if (!string.IsNullOrEmpty(userId)) + { + IConfidentialClientApplication app = await GetOrBuildConfidentialClientApplicationAsync().ConfigureAwait(false); + + if (_microsoftIdentityOptions.IsB2C) + { + await _tokenCacheProvider.ClearAsync(userId).ConfigureAwait(false); + } + else + { + string? identifier = context.HttpContext.User.GetMsalAccountId(); + IAccount account = await app.GetAccountAsync(identifier).ConfigureAwait(false); + + if (account != null) + { + await app.RemoveAsync(account).ConfigureAwait(false); + await _tokenCacheProvider.ClearAsync(userId).ConfigureAwait(false); + } + } + } + } + + /// + /// Creates an MSAL confidential client application, if needed. + /// + internal /* for testing */ async Task GetOrBuildConfidentialClientApplicationAsync() + { + if (_application == null) + { + return await BuildConfidentialClientApplicationAsync().ConfigureAwait(false); + } + + return _application; + } + + /// + /// Creates an MSAL confidential client application. + /// + private async Task BuildConfidentialClientApplicationAsync() + { + var request = CurrentHttpContext?.Request; + string? currentUri = null; + + if (!string.IsNullOrEmpty(_applicationOptions.RedirectUri)) + { + currentUri = _applicationOptions.RedirectUri; + } + + if (request != null && string.IsNullOrEmpty(currentUri)) + { + currentUri = UriHelper.BuildAbsolute( + request.Scheme, + request.Host, + request.PathBase, + _microsoftIdentityOptions.CallbackPath.Value ?? string.Empty); + } + + PrepareAuthorityInstanceForMsal(); + + MicrosoftIdentityOptionsValidation.ValidateEitherClientCertificateOrClientSecret( + _applicationOptions.ClientSecret, + _microsoftIdentityOptions.ClientCertificates); + + try + { + var builder = ConfidentialClientApplicationBuilder + .CreateWithApplicationOptions(_applicationOptions) + .WithHttpClientFactory(_httpClientFactory) + .WithLogging( + Log, + ConvertMicrosoftExtensionsLogLevelToMsal(_logger), + enablePiiLogging: _applicationOptions.EnablePiiLogging) + .WithExperimentalFeatures(); + + // The redirect URI is not needed for OBO + if (!string.IsNullOrEmpty(currentUri)) + { + builder.WithRedirectUri(currentUri); + } + + string authority; + + if (_microsoftIdentityOptions.IsB2C) + { + authority = $"{_applicationOptions.Instance}{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{_microsoftIdentityOptions.DefaultUserFlow}"; + builder.WithB2CAuthority(authority); + } + else + { + authority = $"{_applicationOptions.Instance}{_applicationOptions.TenantId}/"; + builder.WithAuthority(authority); + } + + if (_microsoftIdentityOptions.ClientCertificates != null) + { + X509Certificate2? certificate = DefaultCertificateLoader.LoadFirstCertificate(_microsoftIdentityOptions.ClientCertificates); + builder.WithCertificate(certificate); + } + + IConfidentialClientApplication app = builder.Build(); + _application = app; + // Initialize token cache providers + await _tokenCacheProvider.InitializeAsync(app.AppTokenCache).ConfigureAwait(false); + await _tokenCacheProvider.InitializeAsync(app.UserTokenCache).ConfigureAwait(false); + return app; + } + catch (Exception ex) + { + _logger.LogInformation( + ex, + IDWebErrorMessage.ExceptionAcquiringTokenForConfidentialClient); + throw; + } + } + + private void PrepareAuthorityInstanceForMsal() + { + if (_microsoftIdentityOptions.IsB2C && _applicationOptions.Instance.EndsWith("/tfp/")) + { + _applicationOptions.Instance = _applicationOptions.Instance.Replace("/tfp/", string.Empty).TrimEnd('/') + "/"; + } + else + { + _applicationOptions.Instance = _applicationOptions.Instance.TrimEnd('/') + "/"; + } + } + + private async Task GetAuthenticationResultForWebApiToCallDownstreamApiAsync( + IConfidentialClientApplication application, + string authority, + IEnumerable scopes, + TokenAcquisitionOptions? tokenAcquisitionOptions) + { + try + { + // In web API, validatedToken will not be null + JwtSecurityToken? validatedToken = CurrentHttpContext?.GetTokenUsedToCallWebAPI(); + + // Case of web APIs: we need to do an on-behalf-of flow, with the token used to call the API + if (validatedToken != null) + { + // In the case the token is a JWE (encrypted token), we use the decrypted token. + string tokenUsedToCallTheWebApi = validatedToken.InnerToken == null ? validatedToken.RawData + : validatedToken.InnerToken.RawData; + var builder = application + .AcquireTokenOnBehalfOf( + scopes.Except(_scopesRequestedByMsal), + new UserAssertion(tokenUsedToCallTheWebApi)) + .WithSendX5C(_microsoftIdentityOptions.SendX5C) + .WithAuthority(authority); + + if (tokenAcquisitionOptions != null) + { + builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); + builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); + builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); + builder.WithClaims(tokenAcquisitionOptions.Claims); + if (tokenAcquisitionOptions.PoPConfiguration != null) + { + builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); + } + } + + return await builder.ExecuteAsync() + .ConfigureAwait(false); + } + + return null; + } + catch (MsalUiRequiredException ex) + { + _logger.LogInformation( + string.Format( + CultureInfo.InvariantCulture, + LogMessages.ErrorAcquiringTokenForDownstreamWebApi, + ex.Message)); + throw; + } + } + + /// + /// Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal. + /// + /// . + /// Claims principal for the user on behalf of whom to get a token. + /// Scopes for the downstream API to call. + /// (optional) Authority based on a specific tenant for which to acquire a token to access the scopes + /// on behalf of the user described in the claimsPrincipal. + /// Azure AD B2C user flow to target. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + private async Task GetAuthenticationResultForWebAppWithAccountFromCacheAsync( + IConfidentialClientApplication application, + ClaimsPrincipal? claimsPrincipal, + IEnumerable scopes, + string? authority, + string? userFlow = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + IAccount? account = null; + if (_microsoftIdentityOptions.IsB2C && !string.IsNullOrEmpty(userFlow)) + { + string? nameIdentifierId = claimsPrincipal?.GetNameIdentifierId(); + string? utid = claimsPrincipal?.GetHomeTenantId(); + string? b2cAccountIdentifier = string.Format(CultureInfo.InvariantCulture, "{0}-{1}.{2}", nameIdentifierId, userFlow, utid); + account = await application.GetAccountAsync(b2cAccountIdentifier).ConfigureAwait(false); + } + else + { + string? accountIdentifier = claimsPrincipal?.GetMsalAccountId(); + + if (accountIdentifier != null) + { + account = await application.GetAccountAsync(accountIdentifier).ConfigureAwait(false); + } + } + + return await GetAuthenticationResultForWebAppWithAccountFromCacheAsync( + application, + account, + scopes, + authority, + userFlow, + tokenAcquisitionOptions).ConfigureAwait(false); + } + + /// + /// Gets an access token for a downstream API on behalf of the user whose account is passed as an argument. + /// + /// . + /// User IAccount for which to acquire a token. + /// See . + /// Scopes for the downstream API to call. + /// Authority based on a specific tenant for which to acquire a token to access the scopes + /// on behalf of the user. + /// Azure AD B2C user flow. + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + private async Task GetAuthenticationResultForWebAppWithAccountFromCacheAsync( + IConfidentialClientApplication application, + IAccount? account, + IEnumerable scopes, + string? authority, + string? userFlow = null, + TokenAcquisitionOptions? tokenAcquisitionOptions = null) + { + if (scopes == null) + { + throw new ArgumentNullException(nameof(scopes)); + } + + var builder = application + .AcquireTokenSilent(scopes.Except(_scopesRequestedByMsal), account) + .WithSendX5C(_microsoftIdentityOptions.SendX5C); + + if (tokenAcquisitionOptions != null) + { + builder.WithExtraQueryParameters(tokenAcquisitionOptions.ExtraQueryParameters); + builder.WithCorrelationId(tokenAcquisitionOptions.CorrelationId); + builder.WithForceRefresh(tokenAcquisitionOptions.ForceRefresh); + builder.WithClaims(tokenAcquisitionOptions.Claims); + if (tokenAcquisitionOptions.PoPConfiguration != null) + { + builder.WithProofOfPossession(tokenAcquisitionOptions.PoPConfiguration); + } + } + + // Acquire an access token as a B2C authority + if (_microsoftIdentityOptions.IsB2C) + { + string b2cAuthority = application.Authority.Replace( + new Uri(application.Authority).PathAndQuery, + $"/{ClaimConstants.Tfp}/{_microsoftIdentityOptions.Domain}/{userFlow ?? _microsoftIdentityOptions.DefaultUserFlow}"); + + builder.WithB2CAuthority(b2cAuthority) + .WithSendX5C(_microsoftIdentityOptions.SendX5C); + } + else + { + builder.WithAuthority(authority); + } + + return await builder.ExecuteAsync() + .ConfigureAwait(false); + } + + private static bool AcceptedTokenVersionMismatch(MsalUiRequiredException msalServiceException) + { + // Normally app developers should not make decisions based on the internal AAD code + // however until the STS sends sub-error codes for this error, this is the only + // way to distinguish the case. + // This is subject to change in the future + return msalServiceException.Message.Contains( + ErrorCodes.B2CPasswordResetErrorCode, + StringComparison.InvariantCulture); + } + + private async Task GetAuthenticatedUserAsync(ClaimsPrincipal? user) + { + if (user == null && _httpContextAccessor.HttpContext?.User != null) + { + user = _httpContextAccessor.HttpContext.User; + } + + if (user == null) + { + try + { + AuthenticationStateProvider? authenticationStateProvider = + _serviceProvider.GetService(typeof(AuthenticationStateProvider)) + as AuthenticationStateProvider; + + if (authenticationStateProvider != null) + { + // AuthenticationState provider is only available in Blazor + AuthenticationState state = await authenticationStateProvider.GetAuthenticationStateAsync().ConfigureAwait(false); + user = state.User; + } + } + catch + { + // do nothing. + } + } + + return user; + } + + internal /*for tests*/ string CreateAuthorityBasedOnTenantIfProvided( + IConfidentialClientApplication application, + string? tenant) + { + string authority; + if (!string.IsNullOrEmpty(tenant)) + { + authority = application.Authority.Replace(new Uri(application.Authority).PathAndQuery, $"/{tenant}/"); + } + else + { + authority = application.Authority; + } + + return authority; + } + + private void Log( + Client.LogLevel level, + string message, + bool containsPii) + { + switch (level) + { + case Client.LogLevel.Error: + _logger.LogError(message); + break; + case Client.LogLevel.Warning: + _logger.LogWarning(message); + break; + case Client.LogLevel.Info: + _logger.LogInformation(message); + break; + case Client.LogLevel.Verbose: + _logger.LogDebug(message); + break; + default: + break; + } + } + + private Client.LogLevel? ConvertMicrosoftExtensionsLogLevelToMsal(ILogger logger) + { + if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information)) + { + return Client.LogLevel.Info; + } + else if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) + || logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Trace)) + { + return Client.LogLevel.Verbose; + } + else if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning)) + { + return Client.LogLevel.Warning; + } + else if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error) + || logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Critical)) + { + return Client.LogLevel.Error; + } + else + { + return null; + } + } + } } diff --git a/src/Microsoft.Identity.Web/TokenAcquisitionOptions.cs b/src/Microsoft.Identity.Web/TokenAcquisitionOptions.cs index e5f9af7a5..b33d05fd8 100644 --- a/src/Microsoft.Identity.Web/TokenAcquisitionOptions.cs +++ b/src/Microsoft.Identity.Web/TokenAcquisitionOptions.cs @@ -7,58 +7,58 @@ namespace Microsoft.Identity.Web { - /// - /// Options passed-in to create the token acquisition object which calls into MSAL .NET. - /// - public class TokenAcquisitionOptions - { - /// - /// Sets the correlation id to be used in the authentication request - /// to the /token endpoint. - /// - public Guid CorrelationId { get; set; } + /// + /// Options passed-in to create the token acquisition object which calls into MSAL .NET. + /// + public class TokenAcquisitionOptions + { + /// + /// Sets the correlation id to be used in the authentication request + /// to the /token endpoint. + /// + public Guid CorrelationId { get; set; } - /// - /// Sets Extra Query Parameters for the query string in the HTTP authentication request. - /// - public Dictionary? ExtraQueryParameters { get; set; } + /// + /// Sets Extra Query Parameters for the query string in the HTTP authentication request. + /// + public Dictionary? ExtraQueryParameters { get; set; } - /// - /// A string with one or multiple claims to request. - /// Normally used with Conditional Access. - /// - public string? Claims { get; set; } + /// + /// A string with one or multiple claims to request. + /// Normally used with Conditional Access. + /// + public string? Claims { get; set; } - /// - /// Specifies if the token request will ignore the access token in the token cache - /// and will attempt to acquire a new access token. - /// If true, the request will ignore the token cache. The default is false. - /// Use this option with care and only when needed, for instance, if you know that conditional access policies have changed, - /// for it induces performance degradation, as the token cache is not utilized. - /// - public bool ForceRefresh { get; set; } + /// + /// Specifies if the token request will ignore the access token in the token cache + /// and will attempt to acquire a new access token. + /// If true, the request will ignore the token cache. The default is false. + /// Use this option with care and only when needed, for instance, if you know that conditional access policies have changed, + /// for it induces performance degradation, as the token cache is not utilized. + /// + public bool ForceRefresh { get; set; } - /// - /// Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), - /// rather than a Bearer token. - /// PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, - /// which MSAL can manage. See https://aka.ms/msal-net-pop. - /// - public PoPAuthenticationConfiguration? PoPConfiguration { get; set; } + /// + /// Modifies the token acquisition request so that the acquired token is a Proof of Possession token (PoP), + /// rather than a Bearer token. + /// PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, + /// which MSAL can manage. See https://aka.ms/msal-net-pop. + /// + public PoPAuthenticationConfiguration? PoPConfiguration { get; set; } - /// - /// Clone the options (to be able to override them). - /// - /// A clone of the options. - public TokenAcquisitionOptions Clone() - { - return new TokenAcquisitionOptions - { - CorrelationId = CorrelationId, - ExtraQueryParameters = ExtraQueryParameters, - ForceRefresh = ForceRefresh, - Claims = Claims, - }; - } - } + /// + /// Clone the options (to be able to override them). + /// + /// A clone of the options. + public TokenAcquisitionOptions Clone() + { + return new TokenAcquisitionOptions + { + CorrelationId = CorrelationId, + ExtraQueryParameters = ExtraQueryParameters, + ForceRefresh = ForceRefresh, + Claims = Claims, + }; + } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/DistributedTokenCacheAdapterExtension.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/DistributedTokenCacheAdapterExtension.cs index 3da02c121..51e556dde 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/DistributedTokenCacheAdapterExtension.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/DistributedTokenCacheAdapterExtension.cs @@ -6,26 +6,26 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Distributed { - /// - /// Extension class used to add distributed token cache serializer to MSAL. - /// See https://aka.ms/ms-id-web/token-cache-serialization for details. - /// - public static class DistributedTokenCacheAdapterExtension - { - /// Adds the .NET Core distributed cache based app token cache to the service collection. - /// The services collection to add to. - /// A to chain. - public static IServiceCollection AddDistributedTokenCaches( - this IServiceCollection services) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } + /// + /// Extension class used to add distributed token cache serializer to MSAL. + /// See https://aka.ms/ms-id-web/token-cache-serialization for details. + /// + public static class DistributedTokenCacheAdapterExtension + { + /// Adds the .NET Core distributed cache based app token cache to the service collection. + /// The services collection to add to. + /// A to chain. + public static IServiceCollection AddDistributedTokenCaches( + this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } - services.AddDistributedMemoryCache(); - services.AddSingleton(); - return services; - } - } + services.AddDistributedMemoryCache(); + services.AddSingleton(); + return services; + } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs index db0820ef4..f6f2507b0 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapter.cs @@ -10,210 +10,210 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Distributed { - /// - /// An implementation of the token cache for both Confidential and Public clients backed by a Distributed Cache. - /// The Distributed Cache (L2), by default creates a Memory Cache (L1), for faster look up, resulting in a two level cache. - /// - /// https://aka.ms/msal-net-token-cache-serialization - public class MsalDistributedTokenCacheAdapter : MsalAbstractTokenCacheProvider - { - /// - /// .NET Core Memory cache. - /// - internal /*for tests*/ readonly IDistributedCache _distributedCache; - internal /*for tests*/ readonly MemoryCache _memoryCache; - private readonly ILogger _logger; - private readonly TimeSpan? _expirationTime; - - /// - /// MSAL distributed token cache options. - /// - private readonly MsalDistributedTokenCacheAdapterOptions _distributedCacheOptions; - - /// - /// Initializes a new instance of the class. - /// - /// Distributed cache instance to use. - /// Options for the token cache. - /// MsalDistributedTokenCacheAdapter logger. - public MsalDistributedTokenCacheAdapter( - IDistributedCache distributedCache, - IOptions distributedCacheOptions, - ILogger logger) - { - if (distributedCacheOptions == null) - { - throw new ArgumentNullException(nameof(distributedCacheOptions)); - } - - _distributedCache = distributedCache; - _distributedCacheOptions = distributedCacheOptions.Value; - _memoryCache = new MemoryCache(_distributedCacheOptions.L1CacheOptions ?? new MemoryCacheOptions { SizeLimit = 500 * 1024 * 1024 }); - _logger = logger; - - if (_distributedCacheOptions.AbsoluteExpirationRelativeToNow != null) - { - if (_distributedCacheOptions.L1ExpirationTimeRatio <= 0 || _distributedCacheOptions.L1ExpirationTimeRatio > 1) - { - throw new ArgumentOutOfRangeException(nameof(_distributedCacheOptions.L1ExpirationTimeRatio), "L1ExpirationTimeRatio must be greater than 0, less than 1. "); - } - - _expirationTime = TimeSpan.FromMilliseconds(_distributedCacheOptions.AbsoluteExpirationRelativeToNow.Value.TotalMilliseconds * _distributedCacheOptions.L1ExpirationTimeRatio); - } - } - - /// - /// Removes a specific token cache, described by its cache key - /// from the distributed cache. - /// - /// Key of the cache to remove. - /// A that completes when key removal has completed. - protected override async Task RemoveKeyAsync(string cacheKey) - { - _memoryCache.Remove(cacheKey); - _logger.LogDebug($"[MsIdWeb] MemoryCache: Removed cacheKey {cacheKey}. "); - - await L2OperationWithRetryOnFailureAsync( - "Removed", - (cacheKey) => _distributedCache.RemoveAsync(cacheKey), - cacheKey).ConfigureAwait(false); - } - - /// - /// Read a specific token cache, described by its cache key, from the - /// distributed cache. - /// - /// Key of the cache item to retrieve. - /// Read blob representing a token cache for the cache key - /// (account or app). - protected override async Task ReadCacheBytesAsync(string cacheKey) - { - // check memory cache first - byte[]? result = (byte[])_memoryCache.Get(cacheKey); - _logger.LogDebug($"[MsIdWeb] MemoryCache: Read {cacheKey} cache size: {result?.Length ?? 0}. "); - - if (result == null) - { - var measure = await Task.Run(async () => - { - // not found in memory, check distributed cache - result = await L2OperationWithRetryOnFailureAsync( - "Read", - (cacheKey) => _distributedCache.GetAsync(cacheKey), - cacheKey).ConfigureAwait(false); - }).Measure().ConfigureAwait(false); - - _logger.LogDebug($"[MsIdWeb] DistributedCache: Read time in MilliSeconds: {measure.MilliSeconds}. "); - - // back propagate to memory cache - if (result != null) - { - MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions() - { - AbsoluteExpirationRelativeToNow = _expirationTime, - Size = result?.Length, - }; - - _logger.LogDebug($"[MsIdWeb] Back propagate from Distributed to Memory, cache size: {result?.Length ?? 0}. "); - _memoryCache.Set(cacheKey, result, memoryCacheEntryOptions); - _logger.LogDebug($"[MsIdWeb] MemoryCache: Count: {_memoryCache.Count}. "); - } - } - else - { - await L2OperationWithRetryOnFailureAsync( - "Refresh", - (cacheKey) => _distributedCache.RefreshAsync(cacheKey), - cacheKey).ConfigureAwait(false); - } + /// + /// An implementation of the token cache for both Confidential and Public clients backed by a Distributed Cache. + /// The Distributed Cache (L2), by default creates a Memory Cache (L1), for faster look up, resulting in a two level cache. + /// + /// https://aka.ms/msal-net-token-cache-serialization + public class MsalDistributedTokenCacheAdapter : MsalAbstractTokenCacheProvider + { + /// + /// .NET Core Memory cache. + /// + internal /*for tests*/ readonly IDistributedCache _distributedCache; + internal /*for tests*/ readonly MemoryCache _memoryCache; + private readonly ILogger _logger; + private readonly TimeSpan? _expirationTime; + + /// + /// MSAL distributed token cache options. + /// + private readonly MsalDistributedTokenCacheAdapterOptions _distributedCacheOptions; + + /// + /// Initializes a new instance of the class. + /// + /// Distributed cache instance to use. + /// Options for the token cache. + /// MsalDistributedTokenCacheAdapter logger. + public MsalDistributedTokenCacheAdapter( + IDistributedCache distributedCache, + IOptions distributedCacheOptions, + ILogger logger) + { + if (distributedCacheOptions == null) + { + throw new ArgumentNullException(nameof(distributedCacheOptions)); + } + + _distributedCache = distributedCache; + _distributedCacheOptions = distributedCacheOptions.Value; + _memoryCache = new MemoryCache(_distributedCacheOptions.L1CacheOptions ?? new MemoryCacheOptions { SizeLimit = 500 * 1024 * 1024 }); + _logger = logger; + + if (_distributedCacheOptions.AbsoluteExpirationRelativeToNow != null) + { + if (_distributedCacheOptions.L1ExpirationTimeRatio <= 0 || _distributedCacheOptions.L1ExpirationTimeRatio > 1) + { + throw new ArgumentOutOfRangeException(nameof(_distributedCacheOptions.L1ExpirationTimeRatio), "L1ExpirationTimeRatio must be greater than 0, less than 1. "); + } + + _expirationTime = TimeSpan.FromMilliseconds(_distributedCacheOptions.AbsoluteExpirationRelativeToNow.Value.TotalMilliseconds * _distributedCacheOptions.L1ExpirationTimeRatio); + } + } + + /// + /// Removes a specific token cache, described by its cache key + /// from the distributed cache. + /// + /// Key of the cache to remove. + /// A that completes when key removal has completed. + protected override async Task RemoveKeyAsync(string cacheKey) + { + _memoryCache.Remove(cacheKey); + _logger.LogDebug($"[MsIdWeb] MemoryCache: Removed cacheKey {cacheKey}. "); + + await L2OperationWithRetryOnFailureAsync( + "Removed", + (cacheKey) => _distributedCache.RemoveAsync(cacheKey), + cacheKey).ConfigureAwait(false); + } + + /// + /// Read a specific token cache, described by its cache key, from the + /// distributed cache. + /// + /// Key of the cache item to retrieve. + /// Read blob representing a token cache for the cache key + /// (account or app). + protected override async Task ReadCacheBytesAsync(string cacheKey) + { + // check memory cache first + byte[]? result = (byte[])_memoryCache.Get(cacheKey); + _logger.LogDebug($"[MsIdWeb] MemoryCache: Read {cacheKey} cache size: {result?.Length ?? 0}. "); + + if (result == null) + { + var measure = await Task.Run(async () => + { + // not found in memory, check distributed cache + result = await L2OperationWithRetryOnFailureAsync( + "Read", + (cacheKey) => _distributedCache.GetAsync(cacheKey), + cacheKey).ConfigureAwait(false); + }).Measure().ConfigureAwait(false); + + _logger.LogDebug($"[MsIdWeb] DistributedCache: Read time in MilliSeconds: {measure.MilliSeconds}. "); + + // back propagate to memory cache + if (result != null) + { + MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions() + { + AbsoluteExpirationRelativeToNow = _expirationTime, + Size = result?.Length, + }; + + _logger.LogDebug($"[MsIdWeb] Back propagate from Distributed to Memory, cache size: {result?.Length ?? 0}. "); + _memoryCache.Set(cacheKey, result, memoryCacheEntryOptions); + _logger.LogDebug($"[MsIdWeb] MemoryCache: Count: {_memoryCache.Count}. "); + } + } + else + { + await L2OperationWithRetryOnFailureAsync( + "Refresh", + (cacheKey) => _distributedCache.RefreshAsync(cacheKey), + cacheKey).ConfigureAwait(false); + } #pragma warning disable CS8603 // Possible null reference return. - return result; + return result; #pragma warning restore CS8603 // Possible null reference return. - } - - /// - /// Writes a token cache blob to the serialization cache (by key). - /// - /// Cache key. - /// blob to write. - /// A that completes when a write operation has completed. - protected override async Task WriteCacheBytesAsync(string cacheKey, byte[] bytes) - { - MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions() - { - AbsoluteExpirationRelativeToNow = _expirationTime, - Size = bytes?.Length, - }; - - // write in both - _memoryCache.Set(cacheKey, bytes, memoryCacheEntryOptions); - _logger.LogDebug($"[MsIdWeb] MemoryCache: Write cacheKey {cacheKey} cache size: {bytes?.Length ?? 0}. "); - _logger.LogDebug($"[MsIdWeb] MemoryCache: Count: {_memoryCache.Count}. "); - - await L2OperationWithRetryOnFailureAsync( - "Write", - (cacheKey) => _distributedCache.SetAsync(cacheKey, bytes, _distributedCacheOptions), - cacheKey).Measure().ConfigureAwait(false); - } - - private async Task L2OperationWithRetryOnFailureAsync( - string operation, - Func cacheOperation, - string cacheKey, - byte[]? bytes = null, - bool inRetry = false) - { - try - { - var measure = await cacheOperation(cacheKey).Measure().ConfigureAwait(false); - _logger.LogDebug($"[MsIdWeb] DistributedCache: {operation} cacheKey {cacheKey} cache size {bytes?.Length ?? 0} InRetry? {inRetry} Time in MilliSeconds: {measure.MilliSeconds}. "); - } - catch (Exception ex) - { - _logger.LogError($"[MsIdWeb] DistributedCache: Connection issue. InRetry? {inRetry} Error message: {ex.Message}. "); - - if (_distributedCacheOptions.OnL2CacheFailure != null && _distributedCacheOptions.OnL2CacheFailure(ex) && !inRetry) - { - _logger.LogDebug($"[MsIdWeb] DistributedCache: Retrying {operation} cacheKey {cacheKey}. "); - await L2OperationWithRetryOnFailureAsync( - operation, - cacheOperation, - cacheKey, - bytes, - true).ConfigureAwait(false); - } - } - } - - private async Task L2OperationWithRetryOnFailureAsync( - string operation, - Func> cacheOperation, - string cacheKey, - bool inRetry = false) - { - byte[]? result = null; - try - { - result = await cacheOperation(cacheKey).ConfigureAwait(false); - _logger.LogDebug($"[MsIdWeb] DistributedCache: {operation} cacheKey {cacheKey} cache size {result?.Length ?? 0} InRetry? {inRetry}. "); - } - catch (Exception ex) - { - _logger.LogError($"[MsIdWeb] DistributedCache: Connection issue. InRetry? {inRetry} Error message: {ex.Message}. "); - - if (_distributedCacheOptions.OnL2CacheFailure != null && _distributedCacheOptions.OnL2CacheFailure(ex) && !inRetry) - { - _logger.LogDebug($"[MsIdWeb] DistributedCache: Retrying {operation} cacheKey {cacheKey}. "); - result = await L2OperationWithRetryOnFailureAsync( - operation, - cacheOperation, - cacheKey, - true).ConfigureAwait(false); - } - } - - return result; - } - } + } + + /// + /// Writes a token cache blob to the serialization cache (by key). + /// + /// Cache key. + /// blob to write. + /// A that completes when a write operation has completed. + protected override async Task WriteCacheBytesAsync(string cacheKey, byte[] bytes) + { + MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions() + { + AbsoluteExpirationRelativeToNow = _expirationTime, + Size = bytes?.Length, + }; + + // write in both + _memoryCache.Set(cacheKey, bytes, memoryCacheEntryOptions); + _logger.LogDebug($"[MsIdWeb] MemoryCache: Write cacheKey {cacheKey} cache size: {bytes?.Length ?? 0}. "); + _logger.LogDebug($"[MsIdWeb] MemoryCache: Count: {_memoryCache.Count}. "); + + await L2OperationWithRetryOnFailureAsync( + "Write", + (cacheKey) => _distributedCache.SetAsync(cacheKey, bytes, _distributedCacheOptions), + cacheKey).Measure().ConfigureAwait(false); + } + + private async Task L2OperationWithRetryOnFailureAsync( + string operation, + Func cacheOperation, + string cacheKey, + byte[]? bytes = null, + bool inRetry = false) + { + try + { + var measure = await cacheOperation(cacheKey).Measure().ConfigureAwait(false); + _logger.LogDebug($"[MsIdWeb] DistributedCache: {operation} cacheKey {cacheKey} cache size {bytes?.Length ?? 0} InRetry? {inRetry} Time in MilliSeconds: {measure.MilliSeconds}. "); + } + catch (Exception ex) + { + _logger.LogError($"[MsIdWeb] DistributedCache: Connection issue. InRetry? {inRetry} Error message: {ex.Message}. "); + + if (_distributedCacheOptions.OnL2CacheFailure != null && _distributedCacheOptions.OnL2CacheFailure(ex) && !inRetry) + { + _logger.LogDebug($"[MsIdWeb] DistributedCache: Retrying {operation} cacheKey {cacheKey}. "); + await L2OperationWithRetryOnFailureAsync( + operation, + cacheOperation, + cacheKey, + bytes, + true).ConfigureAwait(false); + } + } + } + + private async Task L2OperationWithRetryOnFailureAsync( + string operation, + Func> cacheOperation, + string cacheKey, + bool inRetry = false) + { + byte[]? result = null; + try + { + result = await cacheOperation(cacheKey).ConfigureAwait(false); + _logger.LogDebug($"[MsIdWeb] DistributedCache: {operation} cacheKey {cacheKey} cache size {result?.Length ?? 0} InRetry? {inRetry}. "); + } + catch (Exception ex) + { + _logger.LogError($"[MsIdWeb] DistributedCache: Connection issue. InRetry? {inRetry} Error message: {ex.Message}. "); + + if (_distributedCacheOptions.OnL2CacheFailure != null && _distributedCacheOptions.OnL2CacheFailure(ex) && !inRetry) + { + _logger.LogDebug($"[MsIdWeb] DistributedCache: Retrying {operation} cacheKey {cacheKey}. "); + result = await L2OperationWithRetryOnFailureAsync( + operation, + cacheOperation, + cacheKey, + true).ConfigureAwait(false); + } + } + + return result; + } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapterOptions.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapterOptions.cs index 5b1276798..9d12e261b 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapterOptions.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Distributed/MsalDistributedTokenCacheAdapterOptions.cs @@ -7,37 +7,37 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Distributed { - /// - /// Options for the MSAL token cache serialization adapter, - /// which delegates the serialization to the IDistributedCache implementations - /// available with .NET Core. - /// - public class MsalDistributedTokenCacheAdapterOptions : DistributedCacheEntryOptions - { - /// - /// Options of the In Memory (L1) cache. - /// - public MemoryCacheOptions L1CacheOptions { get; set; } = new MemoryCacheOptions() - { - SizeLimit = 500 * 1024 * 1024, // 500 Mb - }; + /// + /// Options for the MSAL token cache serialization adapter, + /// which delegates the serialization to the IDistributedCache implementations + /// available with .NET Core. + /// + public class MsalDistributedTokenCacheAdapterOptions : DistributedCacheEntryOptions + { + /// + /// Options of the In Memory (L1) cache. + /// + public MemoryCacheOptions L1CacheOptions { get; set; } = new MemoryCacheOptions() + { + SizeLimit = 500 * 1024 * 1024, // 500 Mb + }; - /// - /// Callback offered to the app to be notified when the L2 cache fails. - /// This way the app is given the possibility to act on the L2 cache, - /// for instance, in the case of Redis, to reconnect. This is left to the application as it's - /// the only one that knows about the real implementation of the L2 cache. - /// The handler should return true if the cache should try again the operation, and - /// false otherwise. When true is passed and the retry fails, an exception - /// will be thrown. - /// - public Func? OnL2CacheFailure { get; set; } + /// + /// Callback offered to the app to be notified when the L2 cache fails. + /// This way the app is given the possibility to act on the L2 cache, + /// for instance, in the case of Redis, to reconnect. This is left to the application as it's + /// the only one that knows about the real implementation of the L2 cache. + /// The handler should return true if the cache should try again the operation, and + /// false otherwise. When true is passed and the retry fails, an exception + /// will be thrown. + /// + public Func? OnL2CacheFailure { get; set; } - /// - /// Value more than 0, less than 1, to set the In Memory (L1) cache - /// expiration time values relative to the Distributed (L2) cache. - /// Default is 1. - /// - internal double L1ExpirationTimeRatio { get; set; } = 1; - } + /// + /// Value more than 0, less than 1, to set the In Memory (L1) cache + /// expiration time values relative to the Distributed (L2) cache. + /// Default is 1. + /// + internal double L1ExpirationTimeRatio { get; set; } = 1; + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs index bf85a894a..9cda887b9 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/IMsalTokenCacheProvider.cs @@ -6,23 +6,23 @@ namespace Microsoft.Identity.Web.TokenCacheProviders { - /// - /// MSAL token cache provider interface. - /// - public interface IMsalTokenCacheProvider - { - /// - /// Initializes a token cache (which can be a user token cache or an app token cache). - /// - /// Token cache for which to initialize the serialization. - /// A that represents a completed initialization operation. - Task InitializeAsync(ITokenCache tokenCache); + /// + /// MSAL token cache provider interface. + /// + public interface IMsalTokenCacheProvider + { + /// + /// Initializes a token cache (which can be a user token cache or an app token cache). + /// + /// Token cache for which to initialize the serialization. + /// A that represents a completed initialization operation. + Task InitializeAsync(ITokenCache tokenCache); - /// - /// Clear the user token cache. - /// - /// HomeAccountId for a user account in the cache. - /// A that represents a completed clear operation. - Task ClearAsync(string homeAccountId); - } + /// + /// Clear the user token cache. + /// + /// HomeAccountId for a user account in the cache. + /// A that represents a completed clear operation. + Task ClearAsync(string homeAccountId); + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/InMemoryTokenCacheProviderExtension.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/InMemoryTokenCacheProviderExtension.cs index 2a8cf8a8a..0ebe42332 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/InMemoryTokenCacheProviderExtension.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/InMemoryTokenCacheProviderExtension.cs @@ -6,25 +6,25 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.InMemory { - /// - /// Extension class used to add an in-memory token cache serializer to MSAL. - /// - public static class InMemoryTokenCacheProviderExtension - { - /// Adds both the app and per-user in-memory token caches. - /// The services collection to add to. - /// the services (for chaining). - public static IServiceCollection AddInMemoryTokenCaches( - this IServiceCollection services) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } + /// + /// Extension class used to add an in-memory token cache serializer to MSAL. + /// + public static class InMemoryTokenCacheProviderExtension + { + /// Adds both the app and per-user in-memory token caches. + /// The services collection to add to. + /// the services (for chaining). + public static IServiceCollection AddInMemoryTokenCaches( + this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } - services.AddMemoryCache(); - services.AddSingleton(); - return services; - } - } + services.AddMemoryCache(); + services.AddSingleton(); + return services; + } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheOptions.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheOptions.cs index 1c1a6015a..0b276be37 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheOptions.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheOptions.cs @@ -5,30 +5,30 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.InMemory { - /// - /// MSAL's in-memory token cache options. - /// - public class MsalMemoryTokenCacheOptions - { - /// Initializes a new instance of the class. - /// By default, the sliding expiration is set for 14 days. - public MsalMemoryTokenCacheOptions() - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(14); - } + /// + /// MSAL's in-memory token cache options. + /// + public class MsalMemoryTokenCacheOptions + { + /// Initializes a new instance of the class. + /// By default, the sliding expiration is set for 14 days. + public MsalMemoryTokenCacheOptions() + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(14); + } - /// - /// Gets or sets the value of the duration after which the cache entry will expire unless it's used - /// This is the duration the tokens are kept in memory cache. - /// In production, a higher value, up-to 90 days is recommended. - /// - /// - /// The AbsoluteExpirationRelativeToNow value. - /// - public TimeSpan AbsoluteExpirationRelativeToNow - { - get; - set; - } - } + /// + /// Gets or sets the value of the duration after which the cache entry will expire unless it's used + /// This is the duration the tokens are kept in memory cache. + /// In production, a higher value, up-to 90 days is recommended. + /// + /// + /// The AbsoluteExpirationRelativeToNow value. + /// + public TimeSpan AbsoluteExpirationRelativeToNow + { + get; + set; + } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs index 34386c0a0..fabab3f51 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/InMemory/MsalMemoryTokenCacheProvider.cs @@ -8,79 +8,79 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.InMemory { - /// - /// An implementation of token cache for both Confidential and Public clients backed by MemoryCache. - /// - /// https://aka.ms/msal-net-token-cache-serialization - public class MsalMemoryTokenCacheProvider : MsalAbstractTokenCacheProvider - { - /// - /// .NET Core Memory cache. - /// - private readonly IMemoryCache _memoryCache; + /// + /// An implementation of token cache for both Confidential and Public clients backed by MemoryCache. + /// + /// https://aka.ms/msal-net-token-cache-serialization + public class MsalMemoryTokenCacheProvider : MsalAbstractTokenCacheProvider + { + /// + /// .NET Core Memory cache. + /// + private readonly IMemoryCache _memoryCache; - /// - /// MSAL memory token cache options. - /// - private readonly MsalMemoryTokenCacheOptions _cacheOptions; + /// + /// MSAL memory token cache options. + /// + private readonly MsalMemoryTokenCacheOptions _cacheOptions; - /// - /// Constructor. - /// - /// serialization cache. - /// Memory cache options. - public MsalMemoryTokenCacheProvider( - IMemoryCache memoryCache, - IOptions cacheOptions) - { - if (cacheOptions == null) - { - throw new ArgumentNullException(nameof(cacheOptions)); - } + /// + /// Constructor. + /// + /// serialization cache. + /// Memory cache options. + public MsalMemoryTokenCacheProvider( + IMemoryCache memoryCache, + IOptions cacheOptions) + { + if (cacheOptions == null) + { + throw new ArgumentNullException(nameof(cacheOptions)); + } - _memoryCache = memoryCache; - _cacheOptions = cacheOptions.Value; - } + _memoryCache = memoryCache; + _cacheOptions = cacheOptions.Value; + } - /// - /// Removes a token cache identified by its key, from the serialization - /// cache. - /// - /// token cache key. - /// A that completes when key removal has completed. - protected override Task RemoveKeyAsync(string cacheKey) - { - _memoryCache.Remove(cacheKey); - return Task.CompletedTask; - } + /// + /// Removes a token cache identified by its key, from the serialization + /// cache. + /// + /// token cache key. + /// A that completes when key removal has completed. + protected override Task RemoveKeyAsync(string cacheKey) + { + _memoryCache.Remove(cacheKey); + return Task.CompletedTask; + } - /// - /// Reads a blob from the serialization cache (identified by its key). - /// - /// Token cache key. - /// Read Bytes. - protected override Task ReadCacheBytesAsync(string cacheKey) - { - byte[] tokenCacheBytes = (byte[])_memoryCache.Get(cacheKey); - return Task.FromResult(tokenCacheBytes); - } + /// + /// Reads a blob from the serialization cache (identified by its key). + /// + /// Token cache key. + /// Read Bytes. + protected override Task ReadCacheBytesAsync(string cacheKey) + { + byte[] tokenCacheBytes = (byte[])_memoryCache.Get(cacheKey); + return Task.FromResult(tokenCacheBytes); + } - /// - /// Writes a token cache blob to the serialization cache (identified by its key). - /// - /// Token cache key. - /// Bytes to write. - /// A that completes when a write operation has completed. - protected override Task WriteCacheBytesAsync(string cacheKey, byte[] bytes) - { - MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions() - { - AbsoluteExpirationRelativeToNow = _cacheOptions.AbsoluteExpirationRelativeToNow, - Size = bytes?.Length, - }; + /// + /// Writes a token cache blob to the serialization cache (identified by its key). + /// + /// Token cache key. + /// Bytes to write. + /// A that completes when a write operation has completed. + protected override Task WriteCacheBytesAsync(string cacheKey, byte[] bytes) + { + MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions() + { + AbsoluteExpirationRelativeToNow = _cacheOptions.AbsoluteExpirationRelativeToNow, + Size = bytes?.Length, + }; - _memoryCache.Set(cacheKey, bytes, memoryCacheEntryOptions); - return Task.CompletedTask; - } - } + _memoryCache.Set(cacheKey, bytes, memoryCacheEntryOptions); + return Task.CompletedTask; + } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/MeasureDurationResult.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/MeasureDurationResult.cs index 7eefd0b98..709d9b285 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/MeasureDurationResult.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/MeasureDurationResult.cs @@ -5,34 +5,34 @@ namespace Microsoft.Identity.Web.TokenCacheProviders { - internal struct MeasureDurationResult - { - public MeasureDurationResult(long ticks) - { - Ticks = ticks; - } + internal struct MeasureDurationResult + { + public MeasureDurationResult(long ticks) + { + Ticks = ticks; + } - public long Ticks { get; } + public long Ticks { get; } - public double MilliSeconds - { - get - { - return (double)Ticks / (double)Stopwatch.Frequency * 1000.0; - } - } - } + public double MilliSeconds + { + get + { + return (double)Ticks / (double)Stopwatch.Frequency * 1000.0; + } + } + } - internal struct MeasureDurationResult - { - public MeasureDurationResult(TResult result, long ticks) - { - Result = result; - Ticks = ticks; - } + internal struct MeasureDurationResult + { + public MeasureDurationResult(TResult result, long ticks) + { + Result = result; + Ticks = ticks; + } - public TResult Result { get; } + public TResult Result { get; } - public long Ticks { get; } - } + public long Ticks { get; } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs index ba91a567a..ff92aa342 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/MsalAbstractTokenCacheProvider.cs @@ -7,108 +7,108 @@ namespace Microsoft.Identity.Web.TokenCacheProviders { - /// - /// Token cache provider with default implementation. - /// - /// - public abstract class MsalAbstractTokenCacheProvider : IMsalTokenCacheProvider - { - /// - /// Initializes the token cache serialization. - /// - /// Token cache to serialize/deserialize. - /// A that represents a completed initialization operation. - public Task InitializeAsync(ITokenCache tokenCache) - { - if (tokenCache == null) - { - throw new ArgumentNullException(nameof(tokenCache)); - } + /// + /// Token cache provider with default implementation. + /// + /// + public abstract class MsalAbstractTokenCacheProvider : IMsalTokenCacheProvider + { + /// + /// Initializes the token cache serialization. + /// + /// Token cache to serialize/deserialize. + /// A that represents a completed initialization operation. + public Task InitializeAsync(ITokenCache tokenCache) + { + if (tokenCache == null) + { + throw new ArgumentNullException(nameof(tokenCache)); + } - tokenCache.SetBeforeAccessAsync(OnBeforeAccessAsync); - tokenCache.SetAfterAccessAsync(OnAfterAccessAsync); - tokenCache.SetBeforeWriteAsync(OnBeforeWriteAsync); + tokenCache.SetBeforeAccessAsync(OnBeforeAccessAsync); + tokenCache.SetAfterAccessAsync(OnAfterAccessAsync); + tokenCache.SetBeforeWriteAsync(OnBeforeWriteAsync); - return Task.CompletedTask; - } + return Task.CompletedTask; + } - /// - /// Raised AFTER MSAL added the new token in its in-memory copy of the cache. - /// This notification is called every time MSAL accesses the cache, not just when a write takes place: - /// If MSAL's current operation resulted in a cache change, the property TokenCacheNotificationArgs.HasStateChanged will be set to true. - /// If that is the case, we call the TokenCache.SerializeMsalV3() to get a binary blob representing the latest cache content – and persist it. - /// - /// Contains parameters used by the MSAL call accessing the cache. - private async Task OnAfterAccessAsync(TokenCacheNotificationArgs args) - { - // The access operation resulted in a cache update. - if (args.HasStateChanged) - { - if (args.HasTokens) - { - await WriteCacheBytesAsync(args.SuggestedCacheKey, args.TokenCache.SerializeMsalV3()).ConfigureAwait(false); - } - else - { - // No token in the cache. we can remove the cache entry - await RemoveKeyAsync(args.SuggestedCacheKey).ConfigureAwait(false); - } - } - } + /// + /// Raised AFTER MSAL added the new token in its in-memory copy of the cache. + /// This notification is called every time MSAL accesses the cache, not just when a write takes place: + /// If MSAL's current operation resulted in a cache change, the property TokenCacheNotificationArgs.HasStateChanged will be set to true. + /// If that is the case, we call the TokenCache.SerializeMsalV3() to get a binary blob representing the latest cache content – and persist it. + /// + /// Contains parameters used by the MSAL call accessing the cache. + private async Task OnAfterAccessAsync(TokenCacheNotificationArgs args) + { + // The access operation resulted in a cache update. + if (args.HasStateChanged) + { + if (args.HasTokens) + { + await WriteCacheBytesAsync(args.SuggestedCacheKey, args.TokenCache.SerializeMsalV3()).ConfigureAwait(false); + } + else + { + // No token in the cache. we can remove the cache entry + await RemoveKeyAsync(args.SuggestedCacheKey).ConfigureAwait(false); + } + } + } - private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args) - { - if (!string.IsNullOrEmpty(args.SuggestedCacheKey)) - { - byte[] tokenCacheBytes = await ReadCacheBytesAsync(args.SuggestedCacheKey).ConfigureAwait(false); - args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true); - } - } + private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args) + { + if (!string.IsNullOrEmpty(args.SuggestedCacheKey)) + { + byte[] tokenCacheBytes = await ReadCacheBytesAsync(args.SuggestedCacheKey).ConfigureAwait(false); + args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true); + } + } - /// - /// if you want to ensure that no concurrent write takes place, use this notification to place a lock on the entry. - /// - /// Token cache notification arguments. - /// A that represents a completed operation. - protected virtual Task OnBeforeWriteAsync(TokenCacheNotificationArgs args) - { - return Task.CompletedTask; - } + /// + /// if you want to ensure that no concurrent write takes place, use this notification to place a lock on the entry. + /// + /// Token cache notification arguments. + /// A that represents a completed operation. + protected virtual Task OnBeforeWriteAsync(TokenCacheNotificationArgs args) + { + return Task.CompletedTask; + } - /// - /// Clear the cache. - /// - /// HomeAccountId for a user account in the cache. - /// A that represents a completed clear operation. - public async Task ClearAsync(string homeAccountId) - { - // This is a user token cache - await RemoveKeyAsync(homeAccountId).ConfigureAwait(false); + /// + /// Clear the cache. + /// + /// HomeAccountId for a user account in the cache. + /// A that represents a completed clear operation. + public async Task ClearAsync(string homeAccountId) + { + // This is a user token cache + await RemoveKeyAsync(homeAccountId).ConfigureAwait(false); - // TODO: Clear the cookie session if any. Get inspiration from - // https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/240 - } + // TODO: Clear the cookie session if any. Get inspiration from + // https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/240 + } - /// - /// Method to be implemented by concrete cache serializers to write the cache bytes. - /// - /// Cache key. - /// Bytes to write. - /// A that represents a completed write operation. - protected abstract Task WriteCacheBytesAsync(string cacheKey, byte[] bytes); + /// + /// Method to be implemented by concrete cache serializers to write the cache bytes. + /// + /// Cache key. + /// Bytes to write. + /// A that represents a completed write operation. + protected abstract Task WriteCacheBytesAsync(string cacheKey, byte[] bytes); - /// - /// Method to be implemented by concrete cache serializers to Read the cache bytes. - /// - /// Cache key. - /// Read bytes. - protected abstract Task ReadCacheBytesAsync(string cacheKey); + /// + /// Method to be implemented by concrete cache serializers to Read the cache bytes. + /// + /// Cache key. + /// Read bytes. + protected abstract Task ReadCacheBytesAsync(string cacheKey); - /// - /// Method to be implemented by concrete cache serializers to remove an entry from the cache. - /// - /// Cache key. - /// A that represents a completed remove key operation. - protected abstract Task RemoveKeyAsync(string cacheKey); - } + /// + /// Method to be implemented by concrete cache serializers to remove an entry from the cache. + /// + /// Cache key. + /// A that represents a completed remove key operation. + protected abstract Task RemoveKeyAsync(string cacheKey); + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs index 2cd03d111..b8a2aebef 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/MsalSessionTokenCacheProvider.cs @@ -9,119 +9,119 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Session { - /// - /// An implementation of token cache for confidential clients backed by an HTTP session. - /// - /// - /// For this session cache to work effectively, the ASP.NET Core session has to be configured properly. - /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state - /// - /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: - /// - /// services.AddSession(option => - /// { - /// option.Cookie.IsEssential = true; - /// }); - /// - /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: - /// - /// app.UseSession(); // Before UseMvc() - /// - /// - /// https://aka.ms/msal-net-token-cache-serialization - public class MsalSessionTokenCacheProvider : MsalAbstractTokenCacheProvider - { - private readonly ILogger _logger; - private readonly ISession _session; + /// + /// An implementation of token cache for confidential clients backed by an HTTP session. + /// + /// + /// For this session cache to work effectively, the ASP.NET Core session has to be configured properly. + /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state + /// + /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + /// + /// services.AddSession(option => + /// { + /// option.Cookie.IsEssential = true; + /// }); + /// + /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + /// + /// app.UseSession(); // Before UseMvc() + /// + /// + /// https://aka.ms/msal-net-token-cache-serialization + public class MsalSessionTokenCacheProvider : MsalAbstractTokenCacheProvider + { + private readonly ILogger _logger; + private readonly ISession _session; - /// - /// MSAL Token cache provider constructor. - /// - /// Session for the current user. - /// Logger. - public MsalSessionTokenCacheProvider( - ISession session, - ILogger logger) - { - _session = session; - _logger = logger; - } + /// + /// MSAL Token cache provider constructor. + /// + /// Session for the current user. + /// Logger. + public MsalSessionTokenCacheProvider( + ISession session, + ILogger logger) + { + _session = session; + _logger = logger; + } - /// - /// Read a blob representing the token cache from its key. - /// - /// Key representing the token cache - /// (account or app). - /// Read blob. - protected override async Task ReadCacheBytesAsync(string cacheKey) - { - await _session.LoadAsync().ConfigureAwait(false); + /// + /// Read a blob representing the token cache from its key. + /// + /// Key representing the token cache + /// (account or app). + /// Read blob. + protected override async Task ReadCacheBytesAsync(string cacheKey) + { + await _session.LoadAsync().ConfigureAwait(false); - _sessionLock.EnterReadLock(); - try - { - if (_session.TryGetValue(cacheKey, out byte[] blob)) - { - _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.DeserializingSessionCache, _session.Id, cacheKey)); - } - else - { - _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.SessionCacheKeyNotFound, cacheKey, _session.Id)); - } + _sessionLock.EnterReadLock(); + try + { + if (_session.TryGetValue(cacheKey, out byte[] blob)) + { + _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.DeserializingSessionCache, _session.Id, cacheKey)); + } + else + { + _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.SessionCacheKeyNotFound, cacheKey, _session.Id)); + } - return blob; - } - finally - { - _sessionLock.ExitReadLock(); - } - } + return blob; + } + finally + { + _sessionLock.ExitReadLock(); + } + } - /// - /// Writes the token cache identified by its key to the serialization mechanism. - /// - /// Key for the cache (account ID or app ID). - /// Blob to write to the cache. - /// A that completes when a write operation has completed. - protected override async Task WriteCacheBytesAsync(string cacheKey, byte[] bytes) - { - _sessionLock.EnterWriteLock(); - try - { - _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.SerializingSessionCache, _session.Id, cacheKey)); + /// + /// Writes the token cache identified by its key to the serialization mechanism. + /// + /// Key for the cache (account ID or app ID). + /// Blob to write to the cache. + /// A that completes when a write operation has completed. + protected override async Task WriteCacheBytesAsync(string cacheKey, byte[] bytes) + { + _sessionLock.EnterWriteLock(); + try + { + _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.SerializingSessionCache, _session.Id, cacheKey)); - // Reflect changes in the persistent store - _session.Set(cacheKey, bytes); - await Task.CompletedTask.ConfigureAwait(false); - } - finally - { - _sessionLock.ExitWriteLock(); - } - } + // Reflect changes in the persistent store + _session.Set(cacheKey, bytes); + await Task.CompletedTask.ConfigureAwait(false); + } + finally + { + _sessionLock.ExitWriteLock(); + } + } - /// - /// Removes a cache described by its key. - /// - /// Key of the token cache (user account or app ID). - /// A that completes when key removal has completed. - protected override async Task RemoveKeyAsync(string cacheKey) - { - _sessionLock.EnterWriteLock(); - try - { - _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.ClearingSessionCache, _session.Id, cacheKey)); + /// + /// Removes a cache described by its key. + /// + /// Key of the token cache (user account or app ID). + /// A that completes when key removal has completed. + protected override async Task RemoveKeyAsync(string cacheKey) + { + _sessionLock.EnterWriteLock(); + try + { + _logger.LogInformation(string.Format(CultureInfo.InvariantCulture, LogMessages.ClearingSessionCache, _session.Id, cacheKey)); - // Reflect changes in the persistent store - _session.Remove(cacheKey); - await Task.CompletedTask.ConfigureAwait(false); - } - finally - { - _sessionLock.ExitWriteLock(); - } - } + // Reflect changes in the persistent store + _session.Remove(cacheKey); + await Task.CompletedTask.ConfigureAwait(false); + } + finally + { + _sessionLock.ExitWriteLock(); + } + } - private readonly ReaderWriterLockSlim _sessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - } + private readonly ReaderWriterLockSlim _sessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs index a1e077f9c..26ee5ae01 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Session/SessionTokenCacheProviderExtension.cs @@ -8,107 +8,107 @@ namespace Microsoft.Identity.Web.TokenCacheProviders.Session { - /// - /// Extension class to add a session token cache serializer to MSAL. - /// - public static class SessionTokenCacheProviderExtension - { - /// - /// Adds an HTTP session-based application token cache to the service collection. - /// - /// - /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. - /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. - /// - /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: - /// - /// services.AddSession(option => - /// { - /// option.Cookie.IsEssential = true; - /// }); - /// - /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: - /// - /// app.UseSession(); // Before UseMvc() - /// - /// Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). - /// - /// The services collection to add to. - /// The service collection. - public static IServiceCollection AddSessionAppTokenCache(this IServiceCollection services) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } + /// + /// Extension class to add a session token cache serializer to MSAL. + /// + public static class SessionTokenCacheProviderExtension + { + /// + /// Adds an HTTP session-based application token cache to the service collection. + /// + /// + /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. + /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. + /// + /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + /// + /// services.AddSession(option => + /// { + /// option.Cookie.IsEssential = true; + /// }); + /// + /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + /// + /// app.UseSession(); // Before UseMvc() + /// + /// Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). + /// + /// The services collection to add to. + /// The service collection. + public static IServiceCollection AddSessionAppTokenCache(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } - services.AddHttpContextAccessor(); - services.AddSession(option => - { - option.Cookie.IsEssential = true; - }); - services.AddScoped(); - services.TryAddScoped(provider => - { - var httpContext = provider.GetRequiredService().HttpContext; - if (httpContext == null) - { - throw new InvalidOperationException(IDWebErrorMessage.HttpContextIsNull); - } + services.AddHttpContextAccessor(); + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + services.AddScoped(); + services.TryAddScoped(provider => + { + var httpContext = provider.GetRequiredService().HttpContext; + if (httpContext == null) + { + throw new InvalidOperationException(IDWebErrorMessage.HttpContextIsNull); + } - return httpContext.Session; - }); + return httpContext.Session; + }); - return services; - } + return services; + } - /// - /// Adds an HTTP session-based per-user token cache to the service collection. - /// - /// - /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. - /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. - /// - /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: - /// - /// services.AddSession(option => - /// { - /// option.Cookie.IsEssential = true; - /// }); - /// - /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: - /// - /// app.UseSession(); // Before UseMvc() - /// - /// Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). - /// - /// The services collection to add to. - /// The service collection. - public static IServiceCollection AddSessionPerUserTokenCache(this IServiceCollection services) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } + /// + /// Adds an HTTP session-based per-user token cache to the service collection. + /// + /// + /// For this session cache to work effectively the ASP.NET Core session has to be configured properly. + /// The latest guidance is provided at https://docs.microsoft.com/aspnet/core/fundamentals/app-state. + /// + /// In the method public void ConfigureServices(IServiceCollection services) in Startup.cs, add the following: + /// + /// services.AddSession(option => + /// { + /// option.Cookie.IsEssential = true; + /// }); + /// + /// In the method public void Configure(IApplicationBuilder app, IHostingEnvironment env) in Startup.cs, add the following: + /// + /// app.UseSession(); // Before UseMvc() + /// + /// Because session token caches are added with scoped lifetime, they should not be used when TokenAcquisition is also used as a singleton (for example, when using Microsoft Graph SDK). + /// + /// The services collection to add to. + /// The service collection. + public static IServiceCollection AddSessionPerUserTokenCache(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } - services.AddHttpContextAccessor(); - services.AddSession(option => - { - option.Cookie.IsEssential = true; - }); - services.AddScoped(); - services.TryAddScoped(provider => - { - var httpContext = provider.GetRequiredService().HttpContext; - if (httpContext == null) - { - throw new InvalidOperationException(IDWebErrorMessage.HttpContextIsNull); - } + services.AddHttpContextAccessor(); + services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + services.AddScoped(); + services.TryAddScoped(provider => + { + var httpContext = provider.GetRequiredService().HttpContext; + if (httpContext == null) + { + throw new InvalidOperationException(IDWebErrorMessage.HttpContextIsNull); + } - return httpContext.Session; - }); + return httpContext.Session; + }); - return services; - } - } + return services; + } + } } diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/TokenCacheSerializers.cd b/src/Microsoft.Identity.Web/TokenCacheProviders/TokenCacheSerializers.cd index 5df2e83fb..6f4b18708 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/TokenCacheSerializers.cd +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/TokenCacheSerializers.cd @@ -1,124 +1,124 @@  - - - EgAAAAAAAFAQAAACAAAAAAIAAAIAAiAAAAAAACAAACA= - TokenCacheProviders\MsalAbstractTokenCacheProvider.cs - - + + + EgAAAAAAAFAQAAACAAAAAAIAAAIAAiAAAAAAACAAACA= + TokenCacheProviders\MsalAbstractTokenCacheProvider.cs + + - - - AgAAAAIAgAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAAA= - TokenCacheProviders\InMemory\MsalMemoryTokenCacheProvider.cs - + + + AgAAAAIAgAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAAA= + TokenCacheProviders\InMemory\MsalMemoryTokenCacheProvider.cs + - - - AgAAIAAAAAAABAAAAAAAAAAAAAAAAiAAAAAACAAAAAA= - TokenCacheProviders\Session\MsalSessionTokenCacheProvider.cs - - + + + AgAAIAAAAAAABAAAAAAAAAAAAAAAAiAAAAAACAAAAAA= + TokenCacheProviders\Session\MsalSessionTokenCacheProvider.cs + + - - - AgAQAABAAAAAAAAAAAAAAAAAAAEAAiAAAAAAAAQAAAA= - TokenCacheProviders\Sql\MsalSqlTokenCacheProvider.cs - + + + AgAQAABAAAAAAAAAAAAAAAAAAAEAAiAAAAAAAAQAAAA= + TokenCacheProviders\Sql\MsalSqlTokenCacheProvider.cs + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\InMemory\MsalAppMemoryTokenCacheProvider.cs - - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\InMemory\MsalAppMemoryTokenCacheProvider.cs + + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\InMemory\MsalPerUserMemoryTokenCacheProvider.cs - - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\InMemory\MsalPerUserMemoryTokenCacheProvider.cs + + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\Session\MsalAppSessionTokenCacheProvider.cs - - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\Session\MsalAppSessionTokenCacheProvider.cs + + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\Session\MsalPerUserSessionTokenCacheProvider.cs - - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\Session\MsalPerUserSessionTokenCacheProvider.cs + + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\Sql\MsalAppSqlTokenCacheProvider.cs - - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\Sql\MsalAppSqlTokenCacheProvider.cs + + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\Sql\MsalPerUserSqlTokenCacheProvider.cs - - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\Sql\MsalPerUserSqlTokenCacheProvider.cs + + - - - AAAAAAAAAAAAAAAAAAAAgAAAAAAAQAAAAAAAAAAAAAA= - TokenCacheProviders\InMemory\InMemoryTokenCacheProviderExtension.cs - + + + AAAAAAAAAAAAAAAAAAAAgAAAAAAAQAAAAAAAAAAAAAA= + TokenCacheProviders\InMemory\InMemoryTokenCacheProviderExtension.cs + - - - AAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAIAAAAAAIA= - TokenCacheProviders\Session\SessionTokenCacheProviderExtension.cs - + + + AAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAIAAAAAAIA= + TokenCacheProviders\Session\SessionTokenCacheProviderExtension.cs + - - - AAAAAAAAAAAAAAAAAAAAAgIAAAAAAAAQAAAAAAAACAA= - TokenCacheProviders\Sql\SqlTokenCacheProviderExtension.cs - + + + AAAAAAAAAAAAAAAAAAAAAgIAAAAAAAAQAAAAAAAACAA= + TokenCacheProviders\Sql\SqlTokenCacheProviderExtension.cs + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\IMsalAppTokenCacheProvider.cs - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\IMsalAppTokenCacheProvider.cs + - - - EAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\IMsalTokenCacheProvider .cs - + + + EAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\IMsalTokenCacheProvider .cs + - - - EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - TokenCacheProviders\IMSALUserTokenCacheProvider.cs - + + + EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + TokenCacheProviders\IMSALUserTokenCacheProvider.cs + \ No newline at end of file diff --git a/src/Microsoft.Identity.Web/TokenCacheProviders/Utility.cs b/src/Microsoft.Identity.Web/TokenCacheProviders/Utility.cs index 9833fadfc..a2eb7644b 100644 --- a/src/Microsoft.Identity.Web/TokenCacheProviders/Utility.cs +++ b/src/Microsoft.Identity.Web/TokenCacheProviders/Utility.cs @@ -7,37 +7,37 @@ namespace Microsoft.Identity.Web.TokenCacheProviders { - /// - /// Utility methods used by L1/L2 cache. - /// - internal static class Utility - { - internal static readonly Stopwatch Watch = Stopwatch.StartNew(); - - internal static async Task Measure(this Task task) - { - if (task == null) - { - throw new ArgumentNullException(nameof(task)); - } - - var startTicks = Watch.Elapsed.Ticks; - await task.ConfigureAwait(false); - - return new MeasureDurationResult(Watch.Elapsed.Ticks - startTicks); - } - - internal static async Task> Measure(this Task task) - { - if (task == null) - { - throw new ArgumentNullException(nameof(task)); - } - - var startTicks = Watch.Elapsed.Ticks; - var taskResult = await task.ConfigureAwait(false); - - return new MeasureDurationResult(taskResult, Watch.Elapsed.Ticks - startTicks); - } - } + /// + /// Utility methods used by L1/L2 cache. + /// + internal static class Utility + { + internal static readonly Stopwatch Watch = Stopwatch.StartNew(); + + internal static async Task Measure(this Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + var startTicks = Watch.Elapsed.Ticks; + await task.ConfigureAwait(false); + + return new MeasureDurationResult(Watch.Elapsed.Ticks - startTicks); + } + + internal static async Task> Measure(this Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + var startTicks = Watch.Elapsed.Ticks; + var taskResult = await task.ConfigureAwait(false); + + return new MeasureDurationResult(taskResult, Watch.Elapsed.Ticks - startTicks); + } + } } diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/ClassDiagram1.cd b/src/Microsoft.Identity.Web/WebApiExtensions/ClassDiagram1.cd index fa21bfa1a..ec4720702 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/ClassDiagram1.cd +++ b/src/Microsoft.Identity.Web/WebApiExtensions/ClassDiagram1.cd @@ -1,18 +1,18 @@  - - - gAAAAQAAAAAAIAAAAAAAAABAAAAAgAAAAAAgAAACAAA= - WebApiExtensions\MicrosoftWebApiAuthenticationBuilder.cs - + + + gAAAAQAAAAAAIAAAAAAAAABAAAAAgAAAAAAgAAACAAA= + WebApiExtensions\MicrosoftWebApiAuthenticationBuilder.cs + - - - AAAAAAAAAQAAIAAAAAAAAAAAAAAAgAAAAAAgAAAAAAA= - WebAppExtensions\MicrosoftWebAppAuthenticationBuilder.cs - + + + AAAAAAAAAQAAIAAAAAAAAAAAAAAAgAAAAAAgAAAAAAA= + WebAppExtensions\MicrosoftWebAppAuthenticationBuilder.cs + \ No newline at end of file diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilder.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilder.cs index a6ea74611..c444198cb 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilder.cs +++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilder.cs @@ -10,99 +10,99 @@ namespace Microsoft.Identity.Web { - /// - /// Authentication builder for a web API. - /// - public class MicrosoftIdentityWebApiAuthenticationBuilder : MicrosoftIdentityBaseAuthenticationBuilder - { - /// - /// Constructor. - /// - /// The services being configured. - /// Default scheme used for OpenIdConnect. - /// ACtion called to configure the JwtBearer options. - /// Action called to configure - /// the Microsoft identity options. - /// Configuration section from which to - /// get parameters. - internal MicrosoftIdentityWebApiAuthenticationBuilder( - IServiceCollection services, - string jwtBearerAuthenticationScheme, - Action configureJwtBearerOptions, - Action configureMicrosoftIdentityOptions, - IConfigurationSection? configurationSection) - : base(services, configurationSection) - { - JwtBearerAuthenticationScheme = jwtBearerAuthenticationScheme; - ConfigureJwtBearerOptions = configureJwtBearerOptions; - ConfigureMicrosoftIdentityOptions = configureMicrosoftIdentityOptions; - - if (ConfigureMicrosoftIdentityOptions == null) - { - throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); - } - - if (ConfigureJwtBearerOptions == null) - { - throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); - } - - Services.Configure(configureMicrosoftIdentityOptions); - } - - private Action ConfigureMicrosoftIdentityOptions { get; set; } - - private string JwtBearerAuthenticationScheme { get; set; } - - private Action ConfigureJwtBearerOptions { get; set; } - - /// - /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - /// - /// The action to configure . - /// The authentication builder to chain. - public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( - Action configureConfidentialClientApplicationOptions) - { - if (configureConfidentialClientApplicationOptions == null) - { - throw new ArgumentNullException(nameof(configureConfidentialClientApplicationOptions)); - } - - CallsWebApiImplementation( - Services, - JwtBearerAuthenticationScheme, - configureConfidentialClientApplicationOptions); - - return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder( - Services, - ConfigurationSection); - } - - internal static void CallsWebApiImplementation( - IServiceCollection services, - string jwtBearerAuthenticationScheme, - Action configureConfidentialClientApplicationOptions) - { - services.Configure(configureConfidentialClientApplicationOptions); - - services.AddTokenAcquisition(); - services.AddHttpContextAccessor(); - - services.AddOptions(jwtBearerAuthenticationScheme) - .Configure((options, serviceProvider) => - { - options.Events ??= new JwtBearerEvents(); - - var onTokenValidatedHandler = options.Events.OnTokenValidated; - - options.Events.OnTokenValidated = async context => - { - await onTokenValidatedHandler(context).ConfigureAwait(false); - context.HttpContext.StoreTokenUsedToCallWebAPI(context.SecurityToken as JwtSecurityToken); - context.Success(); - }; - }); - } - } + /// + /// Authentication builder for a web API. + /// + public class MicrosoftIdentityWebApiAuthenticationBuilder : MicrosoftIdentityBaseAuthenticationBuilder + { + /// + /// Constructor. + /// + /// The services being configured. + /// Default scheme used for OpenIdConnect. + /// ACtion called to configure the JwtBearer options. + /// Action called to configure + /// the Microsoft identity options. + /// Configuration section from which to + /// get parameters. + internal MicrosoftIdentityWebApiAuthenticationBuilder( + IServiceCollection services, + string jwtBearerAuthenticationScheme, + Action configureJwtBearerOptions, + Action configureMicrosoftIdentityOptions, + IConfigurationSection? configurationSection) + : base(services, configurationSection) + { + JwtBearerAuthenticationScheme = jwtBearerAuthenticationScheme; + ConfigureJwtBearerOptions = configureJwtBearerOptions; + ConfigureMicrosoftIdentityOptions = configureMicrosoftIdentityOptions; + + if (ConfigureMicrosoftIdentityOptions == null) + { + throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); + } + + if (ConfigureJwtBearerOptions == null) + { + throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); + } + + Services.Configure(configureMicrosoftIdentityOptions); + } + + private Action ConfigureMicrosoftIdentityOptions { get; set; } + + private string JwtBearerAuthenticationScheme { get; set; } + + private Action ConfigureJwtBearerOptions { get; set; } + + /// + /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + /// + /// The action to configure . + /// The authentication builder to chain. + public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( + Action configureConfidentialClientApplicationOptions) + { + if (configureConfidentialClientApplicationOptions == null) + { + throw new ArgumentNullException(nameof(configureConfidentialClientApplicationOptions)); + } + + CallsWebApiImplementation( + Services, + JwtBearerAuthenticationScheme, + configureConfidentialClientApplicationOptions); + + return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder( + Services, + ConfigurationSection); + } + + internal static void CallsWebApiImplementation( + IServiceCollection services, + string jwtBearerAuthenticationScheme, + Action configureConfidentialClientApplicationOptions) + { + services.Configure(configureConfidentialClientApplicationOptions); + + services.AddTokenAcquisition(); + services.AddHttpContextAccessor(); + + services.AddOptions(jwtBearerAuthenticationScheme) + .Configure((options, serviceProvider) => + { + options.Events ??= new JwtBearerEvents(); + + var onTokenValidatedHandler = options.Events.OnTokenValidated; + + options.Events.OnTokenValidated = async context => + { + await onTokenValidatedHandler(context).ConfigureAwait(false); + context.HttpContext.StoreTokenUsedToCallWebAPI(context.SecurityToken as JwtSecurityToken); + context.Success(); + }; + }); + } + } } diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs index 7b5a75460..e4689f1b4 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs @@ -16,230 +16,230 @@ namespace Microsoft.Identity.Web { - /// - /// Extensions for for startup initialization of web APIs. - /// - public static partial class MicrosoftIdentityWebApiAuthenticationBuilderExtensions - { - /// - /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - /// - /// The to which to add this configuration. - /// The configuration instance. - /// The configuration section with the necessary settings to initialize authentication options. - /// The JWT bearer scheme name to be used. By default it uses "Bearer". - /// - /// Set to true if you want to debug, or just understand the JWT bearer events. - /// - /// The authentication builder to chain. - public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApi( - this AuthenticationBuilder builder, - IConfiguration configuration, - string configSectionName = Constants.AzureAd, - string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, - bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) - { - if (configuration == null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - if (configSectionName == null) - { - throw new ArgumentNullException(nameof(configSectionName)); - } - - IConfigurationSection configurationSection = configuration.GetSection(configSectionName); - - return builder.AddMicrosoftIdentityWebApi( - configurationSection, - jwtBearerScheme, - subscribeToJwtBearerMiddlewareDiagnosticsEvents); - } - - /// - /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - /// - /// The to which to add this configuration. - /// The configuration second from which to fill-in the options. - /// The JWT bearer scheme name to be used. By default it uses "Bearer". - /// - /// Set to true if you want to debug, or just understand the JWT bearer events. - /// - /// The authentication builder to chain. - public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApi( - this AuthenticationBuilder builder, - IConfigurationSection configurationSection, - string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, - bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) - { - if (configurationSection == null) - { - throw new ArgumentNullException(nameof(configurationSection)); - } - - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - AddMicrosoftIdentityWebApiImplementation( - builder, - options => configurationSection.Bind(options), - options => configurationSection.Bind(options), - jwtBearerScheme, - subscribeToJwtBearerMiddlewareDiagnosticsEvents); - - return new MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration( - builder.Services, - jwtBearerScheme, - options => configurationSection.Bind(options), - options => configurationSection.Bind(options), - configurationSection); - } - - /// - /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - /// - /// The to which to add this configuration. - /// The action to configure . - /// The action to configure the . - /// The JWT bearer scheme name to be used. By default it uses "Bearer". - /// - /// Set to true if you want to debug, or just understand the JWT bearer events. - /// The authentication builder to chain. - public static MicrosoftIdentityWebApiAuthenticationBuilder AddMicrosoftIdentityWebApi( - this AuthenticationBuilder builder, - Action configureJwtBearerOptions, - Action configureMicrosoftIdentityOptions, - string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, - bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (configureJwtBearerOptions == null) - { - throw new ArgumentNullException(nameof(configureJwtBearerOptions)); - } - - if (configureMicrosoftIdentityOptions == null) - { - throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); - } - - AddMicrosoftIdentityWebApiImplementation( - builder, - configureJwtBearerOptions, - configureMicrosoftIdentityOptions, - jwtBearerScheme, - subscribeToJwtBearerMiddlewareDiagnosticsEvents); - - return new MicrosoftIdentityWebApiAuthenticationBuilder( - builder.Services, - jwtBearerScheme, - configureJwtBearerOptions, - configureMicrosoftIdentityOptions, - null); - } - - private static void AddMicrosoftIdentityWebApiImplementation( - AuthenticationBuilder builder, - Action configureJwtBearerOptions, - Action configureMicrosoftIdentityOptions, - string jwtBearerScheme, - bool subscribeToJwtBearerMiddlewareDiagnosticsEvents) - { - builder.AddJwtBearer(jwtBearerScheme, configureJwtBearerOptions); - builder.Services.Configure(jwtBearerScheme, configureMicrosoftIdentityOptions); - - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, MicrosoftIdentityOptionsValidation>()); - builder.Services.AddHttpContextAccessor(); - builder.Services.AddHttpClient(); - builder.Services.TryAddSingleton(); - builder.Services.AddOptions(); - - if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) - { - builder.Services.AddSingleton(); - } - - // Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0). - builder.Services.AddOptions(jwtBearerScheme) - .Configure>((options, serviceProvider, microsoftIdentityOptionsMonitor) => - { - var microsoftIdentityOptions = microsoftIdentityOptionsMonitor.Get(jwtBearerScheme); - - if (string.IsNullOrWhiteSpace(options.Authority)) - { - options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions); - } - - // This is a Microsoft identity platform web API - options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority); - - if (options.TokenValidationParameters.AudienceValidator == null - && options.TokenValidationParameters.ValidAudience == null - && options.TokenValidationParameters.ValidAudiences == null) - { - RegisterValidAudience registerAudience = new RegisterValidAudience(); - registerAudience.RegisterAudienceValidation( - options.TokenValidationParameters, - microsoftIdentityOptions); - } - - // If the developer registered an IssuerValidator, do not overwrite it - if (options.TokenValidationParameters.ValidateIssuer && options.TokenValidationParameters.IssuerValidator == null) - { - // Instead of using the default validation (validating against a single tenant, as we do in line of business apps), - // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens) - MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory = - serviceProvider.GetRequiredService(); - - options.TokenValidationParameters.IssuerValidator = - microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate; - } - - // If you provide a token decryption certificate, it will be used to decrypt the token - if (microsoftIdentityOptions.TokenDecryptionCertificates != null) - { - IEnumerable certificates = DefaultCertificateLoader.LoadAllCertificates(microsoftIdentityOptions.TokenDecryptionCertificates); - IEnumerable keys = certificates.Select(c => new X509SecurityKey(c)); - options.TokenValidationParameters.TokenDecryptionKeys = keys; - } - - if (options.Events == null) - { - options.Events = new JwtBearerEvents(); - } - - // When an access token for our own web API is validated, we add it to MSAL.NET's cache so that it can - // be used from the controllers. - var tokenValidatedHandler = options.Events.OnTokenValidated; - options.Events.OnTokenValidated = async context => - { - if (!microsoftIdentityOptions.AllowWebApiToBeAuthorizedByACL && !context!.Principal!.Claims.Any(x => x.Type == ClaimConstants.Scope) - && !context!.Principal!.Claims.Any(y => y.Type == ClaimConstants.Scp) - && !context!.Principal!.Claims.Any(y => y.Type == ClaimConstants.Roles) - && !context!.Principal!.Claims.Any(y => y.Type == ClaimConstants.Role)) - { - throw new UnauthorizedAccessException(IDWebErrorMessage.NeitherScopeOrRolesClaimFoundInToken); - } - - await tokenValidatedHandler(context).ConfigureAwait(false); - }; - - if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) - { - var diagnostics = serviceProvider.GetRequiredService(); - - diagnostics.Subscribe(options.Events); - } - }); - } - } + /// + /// Extensions for for startup initialization of web APIs. + /// + public static partial class MicrosoftIdentityWebApiAuthenticationBuilderExtensions + { + /// + /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + /// + /// The to which to add this configuration. + /// The configuration instance. + /// The configuration section with the necessary settings to initialize authentication options. + /// The JWT bearer scheme name to be used. By default it uses "Bearer". + /// + /// Set to true if you want to debug, or just understand the JWT bearer events. + /// + /// The authentication builder to chain. + public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApi( + this AuthenticationBuilder builder, + IConfiguration configuration, + string configSectionName = Constants.AzureAd, + string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, + bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (configSectionName == null) + { + throw new ArgumentNullException(nameof(configSectionName)); + } + + IConfigurationSection configurationSection = configuration.GetSection(configSectionName); + + return builder.AddMicrosoftIdentityWebApi( + configurationSection, + jwtBearerScheme, + subscribeToJwtBearerMiddlewareDiagnosticsEvents); + } + + /// + /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + /// + /// The to which to add this configuration. + /// The configuration second from which to fill-in the options. + /// The JWT bearer scheme name to be used. By default it uses "Bearer". + /// + /// Set to true if you want to debug, or just understand the JWT bearer events. + /// + /// The authentication builder to chain. + public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApi( + this AuthenticationBuilder builder, + IConfigurationSection configurationSection, + string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, + bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) + { + if (configurationSection == null) + { + throw new ArgumentNullException(nameof(configurationSection)); + } + + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddMicrosoftIdentityWebApiImplementation( + builder, + options => configurationSection.Bind(options), + options => configurationSection.Bind(options), + jwtBearerScheme, + subscribeToJwtBearerMiddlewareDiagnosticsEvents); + + return new MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration( + builder.Services, + jwtBearerScheme, + options => configurationSection.Bind(options), + options => configurationSection.Bind(options), + configurationSection); + } + + /// + /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + /// + /// The to which to add this configuration. + /// The action to configure . + /// The action to configure the . + /// The JWT bearer scheme name to be used. By default it uses "Bearer". + /// + /// Set to true if you want to debug, or just understand the JWT bearer events. + /// The authentication builder to chain. + public static MicrosoftIdentityWebApiAuthenticationBuilder AddMicrosoftIdentityWebApi( + this AuthenticationBuilder builder, + Action configureJwtBearerOptions, + Action configureMicrosoftIdentityOptions, + string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, + bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configureJwtBearerOptions == null) + { + throw new ArgumentNullException(nameof(configureJwtBearerOptions)); + } + + if (configureMicrosoftIdentityOptions == null) + { + throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); + } + + AddMicrosoftIdentityWebApiImplementation( + builder, + configureJwtBearerOptions, + configureMicrosoftIdentityOptions, + jwtBearerScheme, + subscribeToJwtBearerMiddlewareDiagnosticsEvents); + + return new MicrosoftIdentityWebApiAuthenticationBuilder( + builder.Services, + jwtBearerScheme, + configureJwtBearerOptions, + configureMicrosoftIdentityOptions, + null); + } + + private static void AddMicrosoftIdentityWebApiImplementation( + AuthenticationBuilder builder, + Action configureJwtBearerOptions, + Action configureMicrosoftIdentityOptions, + string jwtBearerScheme, + bool subscribeToJwtBearerMiddlewareDiagnosticsEvents) + { + builder.AddJwtBearer(jwtBearerScheme, configureJwtBearerOptions); + builder.Services.Configure(jwtBearerScheme, configureMicrosoftIdentityOptions); + + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, MicrosoftIdentityOptionsValidation>()); + builder.Services.AddHttpContextAccessor(); + builder.Services.AddHttpClient(); + builder.Services.TryAddSingleton(); + builder.Services.AddOptions(); + + if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) + { + builder.Services.AddSingleton(); + } + + // Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0). + builder.Services.AddOptions(jwtBearerScheme) + .Configure>((options, serviceProvider, microsoftIdentityOptionsMonitor) => + { + var microsoftIdentityOptions = microsoftIdentityOptionsMonitor.Get(jwtBearerScheme); + + if (string.IsNullOrWhiteSpace(options.Authority)) + { + options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions); + } + + // This is a Microsoft identity platform web API + options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority); + + if (options.TokenValidationParameters.AudienceValidator == null + && options.TokenValidationParameters.ValidAudience == null + && options.TokenValidationParameters.ValidAudiences == null) + { + RegisterValidAudience registerAudience = new RegisterValidAudience(); + registerAudience.RegisterAudienceValidation( + options.TokenValidationParameters, + microsoftIdentityOptions); + } + + // If the developer registered an IssuerValidator, do not overwrite it + if (options.TokenValidationParameters.ValidateIssuer && options.TokenValidationParameters.IssuerValidator == null) + { + // Instead of using the default validation (validating against a single tenant, as we do in line of business apps), + // we inject our own multi-tenant validation logic (which even accepts both v1.0 and v2.0 tokens) + MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory = + serviceProvider.GetRequiredService(); + + options.TokenValidationParameters.IssuerValidator = + microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate; + } + + // If you provide a token decryption certificate, it will be used to decrypt the token + if (microsoftIdentityOptions.TokenDecryptionCertificates != null) + { + IEnumerable certificates = DefaultCertificateLoader.LoadAllCertificates(microsoftIdentityOptions.TokenDecryptionCertificates); + IEnumerable keys = certificates.Select(c => new X509SecurityKey(c)); + options.TokenValidationParameters.TokenDecryptionKeys = keys; + } + + if (options.Events == null) + { + options.Events = new JwtBearerEvents(); + } + + // When an access token for our own web API is validated, we add it to MSAL.NET's cache so that it can + // be used from the controllers. + var tokenValidatedHandler = options.Events.OnTokenValidated; + options.Events.OnTokenValidated = async context => + { + if (!microsoftIdentityOptions.AllowWebApiToBeAuthorizedByACL && !context!.Principal!.Claims.Any(x => x.Type == ClaimConstants.Scope) + && !context!.Principal!.Claims.Any(y => y.Type == ClaimConstants.Scp) + && !context!.Principal!.Claims.Any(y => y.Type == ClaimConstants.Roles) + && !context!.Principal!.Claims.Any(y => y.Type == ClaimConstants.Role)) + { + throw new UnauthorizedAccessException(IDWebErrorMessage.NeitherScopeOrRolesClaimFoundInToken); + } + + await tokenValidatedHandler(context).ConfigureAwait(false); + }; + + if (subscribeToJwtBearerMiddlewareDiagnosticsEvents) + { + var diagnostics = serviceProvider.GetRequiredService(); + + diagnostics.Subscribe(options.Events); + } + }); + } + } } diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration.cs index cc38429c7..8db505d10 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration.cs +++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration.cs @@ -8,33 +8,33 @@ namespace Microsoft.Identity.Web { - /// - /// Builder for web API authentication with configuration. - /// - public class MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration : MicrosoftIdentityWebApiAuthenticationBuilder - { - internal MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration( - IServiceCollection services, - string jwtBearerAuthenticationScheme, - Action configureJwtBearerOptions, - Action configureMicrosoftIdentityOptions, - IConfigurationSection? configurationSection) - : base(services, jwtBearerAuthenticationScheme, configureJwtBearerOptions, configureMicrosoftIdentityOptions, configurationSection) - { - if (configurationSection == null) - { - throw new ArgumentNullException(nameof(configurationSection)); - } - } + /// + /// Builder for web API authentication with configuration. + /// + public class MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration : MicrosoftIdentityWebApiAuthenticationBuilder + { + internal MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration( + IServiceCollection services, + string jwtBearerAuthenticationScheme, + Action configureJwtBearerOptions, + Action configureMicrosoftIdentityOptions, + IConfigurationSection? configurationSection) + : base(services, jwtBearerAuthenticationScheme, configureJwtBearerOptions, configureMicrosoftIdentityOptions, configurationSection) + { + if (configurationSection == null) + { + throw new ArgumentNullException(nameof(configurationSection)); + } + } - /// - /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). - /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - /// - /// The authentication builder to chain. - public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi() - { - return EnableTokenAcquisitionToCallDownstreamApi(options => ConfigurationSection.Bind(options)); - } - } + /// + /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0). + /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + /// + /// The authentication builder to chain. + public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi() + { + return EnableTokenAcquisitionToCallDownstreamApi(options => ConfigurationSection.Bind(options)); + } + } } diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiServiceCollectionExtensions.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiServiceCollectionExtensions.cs index 1f8c4236a..c4064bf2e 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiServiceCollectionExtensions.cs +++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiServiceCollectionExtensions.cs @@ -8,35 +8,35 @@ namespace Microsoft.Identity.Web { - /// - /// Extension for IServiceCollection for startup initialization of web APIs. - /// - public static partial class MicrosoftIdentityWebApiServiceCollectionExtensions - { - /// - /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0) - /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - /// - /// Service collection to which to add authentication. - /// The Configuration object. - /// The configuration section with the necessary settings to initialize authentication options. - /// The JwtBearer scheme name to be used. By default it uses "Bearer". - /// - /// Set to true if you want to debug, or just understand the JwtBearer events. - /// The authentication builder to chain extension methods. - public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApiAuthentication( - this IServiceCollection services, - IConfiguration configuration, - string configSectionName = Constants.AzureAd, - string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, - bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) - { - AuthenticationBuilder builder = services.AddAuthentication(jwtBearerScheme); - return builder.AddMicrosoftIdentityWebApi( - configuration, - configSectionName, - jwtBearerScheme, - subscribeToJwtBearerMiddlewareDiagnosticsEvents); - } - } + /// + /// Extension for IServiceCollection for startup initialization of web APIs. + /// + public static partial class MicrosoftIdentityWebApiServiceCollectionExtensions + { + /// + /// Protects the web API with Microsoft identity platform (formerly Azure AD v2.0) + /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + /// + /// Service collection to which to add authentication. + /// The Configuration object. + /// The configuration section with the necessary settings to initialize authentication options. + /// The JwtBearer scheme name to be used. By default it uses "Bearer". + /// + /// Set to true if you want to debug, or just understand the JwtBearer events. + /// The authentication builder to chain extension methods. + public static MicrosoftIdentityWebApiAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApiAuthentication( + this IServiceCollection services, + IConfiguration configuration, + string configSectionName = Constants.AzureAd, + string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme, + bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false) + { + AuthenticationBuilder builder = services.AddAuthentication(jwtBearerScheme); + return builder.AddMicrosoftIdentityWebApi( + configuration, + configSectionName, + jwtBearerScheme, + subscribeToJwtBearerMiddlewareDiagnosticsEvents); + } + } } diff --git a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityAppCallingWebApiAuthenticationBuilder.cs b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityAppCallingWebApiAuthenticationBuilder.cs index 94c4c2b06..9ed87ccd6 100644 --- a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityAppCallingWebApiAuthenticationBuilder.cs +++ b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityAppCallingWebApiAuthenticationBuilder.cs @@ -16,98 +16,98 @@ namespace Microsoft.Identity.Web { - /// - /// Authentication builder returned by the EnableTokenAcquisitionToCallDownstreamApi methods - /// enabling you to decide token cache implementations. - /// - public class MicrosoftIdentityAppCallsWebApiAuthenticationBuilder : MicrosoftIdentityBaseAuthenticationBuilder - { - internal MicrosoftIdentityAppCallsWebApiAuthenticationBuilder( - IServiceCollection services, - IConfigurationSection? configurationSection = null) - : base(services, configurationSection) - { - } + /// + /// Authentication builder returned by the EnableTokenAcquisitionToCallDownstreamApi methods + /// enabling you to decide token cache implementations. + /// + public class MicrosoftIdentityAppCallsWebApiAuthenticationBuilder : MicrosoftIdentityBaseAuthenticationBuilder + { + internal MicrosoftIdentityAppCallsWebApiAuthenticationBuilder( + IServiceCollection services, + IConfigurationSection? configurationSection = null) + : base(services, configurationSection) + { + } - /// - /// Add in memory token caches. - /// - /// to configure. - /// to configure. - /// the service collection. - public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddInMemoryTokenCaches( - Action? configureOptions = null, - Action? memoryCacheOptions = null) - { - if (configureOptions != null) - { - Services.Configure(configureOptions); - } + /// + /// Add in memory token caches. + /// + /// to configure. + /// to configure. + /// the service collection. + public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddInMemoryTokenCaches( + Action? configureOptions = null, + Action? memoryCacheOptions = null) + { + if (configureOptions != null) + { + Services.Configure(configureOptions); + } - if (memoryCacheOptions != null) - { - Services.AddMemoryCache(memoryCacheOptions); - } - else - { - Services.AddMemoryCache(); - } + if (memoryCacheOptions != null) + { + Services.AddMemoryCache(memoryCacheOptions); + } + else + { + Services.AddMemoryCache(); + } - Services.AddHttpContextAccessor(); - Services.AddSingleton(); - return this; - } + Services.AddHttpContextAccessor(); + Services.AddSingleton(); + return this; + } - /// - /// Add distributed token caches. - /// - /// the service collection. - public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddDistributedTokenCaches() - { - Services.AddDistributedTokenCaches(); - return this; - } + /// + /// Add distributed token caches. + /// + /// the service collection. + public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddDistributedTokenCaches() + { + Services.AddDistributedTokenCaches(); + return this; + } - /// - /// Add session token caches. - /// - /// the service collection. - public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddSessionTokenCaches() - { - // Add session if you are planning to use session based token cache - var sessionStoreService = Services.FirstOrDefault(x => x.ServiceType.Name == Constants.ISessionStore); + /// + /// Add session token caches. + /// + /// the service collection. + public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder AddSessionTokenCaches() + { + // Add session if you are planning to use session based token cache + var sessionStoreService = Services.FirstOrDefault(x => x.ServiceType.Name == Constants.ISessionStore); - // If not added already - if (sessionStoreService == null) - { - Services.AddSession(option => - { - option.Cookie.IsEssential = true; - }); - } - else - { - // If already added, ensure the options are set to use Cookies - Services.Configure(option => - { - option.Cookie.IsEssential = true; - }); - } + // If not added already + if (sessionStoreService == null) + { + Services.AddSession(option => + { + option.Cookie.IsEssential = true; + }); + } + else + { + // If already added, ensure the options are set to use Cookies + Services.Configure(option => + { + option.Cookie.IsEssential = true; + }); + } - Services.AddHttpContextAccessor(); - Services.AddScoped(); - Services.TryAddScoped(provider => - { - var httpContext = provider.GetRequiredService().HttpContext; - if (httpContext == null) - { - throw new InvalidOperationException(IDWebErrorMessage.HttpContextIsNull); - } + Services.AddHttpContextAccessor(); + Services.AddScoped(); + Services.TryAddScoped(provider => + { + var httpContext = provider.GetRequiredService().HttpContext; + if (httpContext == null) + { + throw new InvalidOperationException(IDWebErrorMessage.HttpContextIsNull); + } - return httpContext.Session; - }); + return httpContext.Session; + }); - return this; - } - } + return this; + } + } } diff --git a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs index 9093f9dc3..5237bbfb9 100644 --- a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs +++ b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs @@ -13,160 +13,160 @@ namespace Microsoft.Identity.Web { - /// - /// Authentication builder specific for Microsoft identity platform. - /// - public class MicrosoftIdentityWebAppAuthenticationBuilder : MicrosoftIdentityBaseAuthenticationBuilder - { - /// - /// Constructor. - /// - /// The services being configured. - /// Default scheme used for OpenIdConnect. - /// Action called to configure - /// the Microsoft identity options. - /// Optional configuration section. - internal MicrosoftIdentityWebAppAuthenticationBuilder( - IServiceCollection services, - string openIdConnectScheme, - Action configureMicrosoftIdentityOptions, - IConfigurationSection? configurationSection) - : base(services, configurationSection) - { - OpenIdConnectScheme = openIdConnectScheme; - ConfigureMicrosoftIdentityOptions = configureMicrosoftIdentityOptions; - - if (ConfigureMicrosoftIdentityOptions == null) - { - throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); - } - } - - private Action ConfigureMicrosoftIdentityOptions { get; set; } - - private string OpenIdConnectScheme { get; set; } - - /// - /// The web app calls a web API. - /// - /// Initial scopes. - /// The builder itself for chaining. - public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( - IEnumerable? initialScopes = null) - { - return EnableTokenAcquisitionToCallDownstreamApi(null, initialScopes); - } - - /// - /// The web app calls a web API. This override enables you to specify the - /// ConfidentialClientApplicationOptions (from MSAL.NET) programmatically. - /// - /// Action to configure the - /// MSAL.NET confidential client application options. - /// Initial scopes. - /// The builder itself for chaining. - public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( - Action? configureConfidentialClientApplicationOptions, - IEnumerable? initialScopes = null) - { - WebAppCallsWebApiImplementation( - Services, - initialScopes, - ConfigureMicrosoftIdentityOptions, - OpenIdConnectScheme, - configureConfidentialClientApplicationOptions); - return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder( - Services, - ConfigurationSection); - } - - internal static void WebAppCallsWebApiImplementation( - IServiceCollection services, - IEnumerable? initialScopes, - Action configureMicrosoftIdentityOptions, - string openIdConnectScheme, - Action? configureConfidentialClientApplicationOptions) - { - // Ensure that configuration options for MSAL.NET, HttpContext accessor and the Token acquisition service - // (encapsulating MSAL.NET) are available through dependency injection - services.Configure(configureMicrosoftIdentityOptions); - - if (configureConfidentialClientApplicationOptions != null) - { - services.Configure(configureConfidentialClientApplicationOptions); - } - - services.AddHttpContextAccessor(); - - if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) - { - services.AddScoped(); - } - else - { - services.AddTokenAcquisition(); - - services.AddOptions(openIdConnectScheme) - .Configure((options, serviceProvider) => - { - options.ResponseType = OpenIdConnectResponseType.Code; - options.UsePkce = false; - - // This scope is needed to get a refresh token when users sign-in with their Microsoft personal accounts - // It's required by MSAL.NET and automatically provided when users sign-in with work or school accounts - options.Scope.Add(OidcConstants.ScopeOfflineAccess); - if (initialScopes != null) - { - foreach (string scope in initialScopes) - { - if (!options.Scope.Contains(scope)) - { - options.Scope.Add(scope); - } - } - } - - // Handling the auth redemption by MSAL.NET so that a token is available in the token cache - // where it will be usable from Controllers later (through the TokenAcquisition service) - var codeReceivedHandler = options.Events.OnAuthorizationCodeReceived; - options.Events.OnAuthorizationCodeReceived = async context => - { - var tokenAcquisition = context!.HttpContext.RequestServices.GetRequiredService(); - await tokenAcquisition.AddAccountToCacheFromAuthorizationCodeAsync(context, options.Scope).ConfigureAwait(false); - await codeReceivedHandler(context).ConfigureAwait(false); - }; - - // Handling the token validated to get the client_info for cases where tenantId is not present (example: B2C) - var onTokenValidatedHandler = options.Events.OnTokenValidated; - options.Events.OnTokenValidated = async context => - { - string? clientInfo = context!.ProtocolMessage?.GetParameter(ClaimConstants.ClientInfo); - - if (!string.IsNullOrEmpty(clientInfo)) - { - ClientInfo? clientInfoFromServer = ClientInfo.CreateFromJson(clientInfo); - - if (clientInfoFromServer != null) - { - context!.Principal!.Identities.FirstOrDefault()?.AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier)); - context!.Principal!.Identities.FirstOrDefault()?.AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier)); - } - } - - await onTokenValidatedHandler(context).ConfigureAwait(false); - }; - - // Handling the sign-out: removing the account from MSAL.NET cache - var signOutHandler = options.Events.OnRedirectToIdentityProviderForSignOut; - options.Events.OnRedirectToIdentityProviderForSignOut = async context => - { - // Remove the account from MSAL.NET token cache - var tokenAcquisition = context!.HttpContext.RequestServices.GetRequiredService(); - await tokenAcquisition.RemoveAccountAsync(context).ConfigureAwait(false); - await signOutHandler(context).ConfigureAwait(false); - }; - }); - } - } - } + /// + /// Authentication builder specific for Microsoft identity platform. + /// + public class MicrosoftIdentityWebAppAuthenticationBuilder : MicrosoftIdentityBaseAuthenticationBuilder + { + /// + /// Constructor. + /// + /// The services being configured. + /// Default scheme used for OpenIdConnect. + /// Action called to configure + /// the Microsoft identity options. + /// Optional configuration section. + internal MicrosoftIdentityWebAppAuthenticationBuilder( + IServiceCollection services, + string openIdConnectScheme, + Action configureMicrosoftIdentityOptions, + IConfigurationSection? configurationSection) + : base(services, configurationSection) + { + OpenIdConnectScheme = openIdConnectScheme; + ConfigureMicrosoftIdentityOptions = configureMicrosoftIdentityOptions; + + if (ConfigureMicrosoftIdentityOptions == null) + { + throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); + } + } + + private Action ConfigureMicrosoftIdentityOptions { get; set; } + + private string OpenIdConnectScheme { get; set; } + + /// + /// The web app calls a web API. + /// + /// Initial scopes. + /// The builder itself for chaining. + public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( + IEnumerable? initialScopes = null) + { + return EnableTokenAcquisitionToCallDownstreamApi(null, initialScopes); + } + + /// + /// The web app calls a web API. This override enables you to specify the + /// ConfidentialClientApplicationOptions (from MSAL.NET) programmatically. + /// + /// Action to configure the + /// MSAL.NET confidential client application options. + /// Initial scopes. + /// The builder itself for chaining. + public MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( + Action? configureConfidentialClientApplicationOptions, + IEnumerable? initialScopes = null) + { + WebAppCallsWebApiImplementation( + Services, + initialScopes, + ConfigureMicrosoftIdentityOptions, + OpenIdConnectScheme, + configureConfidentialClientApplicationOptions); + return new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder( + Services, + ConfigurationSection); + } + + internal static void WebAppCallsWebApiImplementation( + IServiceCollection services, + IEnumerable? initialScopes, + Action configureMicrosoftIdentityOptions, + string openIdConnectScheme, + Action? configureConfidentialClientApplicationOptions) + { + // Ensure that configuration options for MSAL.NET, HttpContext accessor and the Token acquisition service + // (encapsulating MSAL.NET) are available through dependency injection + services.Configure(configureMicrosoftIdentityOptions); + + if (configureConfidentialClientApplicationOptions != null) + { + services.Configure(configureConfidentialClientApplicationOptions); + } + + services.AddHttpContextAccessor(); + + if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) + { + services.AddScoped(); + } + else + { + services.AddTokenAcquisition(); + + services.AddOptions(openIdConnectScheme) + .Configure((options, serviceProvider) => + { + options.ResponseType = OpenIdConnectResponseType.Code; + options.UsePkce = false; + + // This scope is needed to get a refresh token when users sign-in with their Microsoft personal accounts + // It's required by MSAL.NET and automatically provided when users sign-in with work or school accounts + options.Scope.Add(OidcConstants.ScopeOfflineAccess); + if (initialScopes != null) + { + foreach (string scope in initialScopes) + { + if (!options.Scope.Contains(scope)) + { + options.Scope.Add(scope); + } + } + } + + // Handling the auth redemption by MSAL.NET so that a token is available in the token cache + // where it will be usable from Controllers later (through the TokenAcquisition service) + var codeReceivedHandler = options.Events.OnAuthorizationCodeReceived; + options.Events.OnAuthorizationCodeReceived = async context => + { + var tokenAcquisition = context!.HttpContext.RequestServices.GetRequiredService(); + await tokenAcquisition.AddAccountToCacheFromAuthorizationCodeAsync(context, options.Scope).ConfigureAwait(false); + await codeReceivedHandler(context).ConfigureAwait(false); + }; + + // Handling the token validated to get the client_info for cases where tenantId is not present (example: B2C) + var onTokenValidatedHandler = options.Events.OnTokenValidated; + options.Events.OnTokenValidated = async context => + { + string? clientInfo = context!.ProtocolMessage?.GetParameter(ClaimConstants.ClientInfo); + + if (!string.IsNullOrEmpty(clientInfo)) + { + ClientInfo? clientInfoFromServer = ClientInfo.CreateFromJson(clientInfo); + + if (clientInfoFromServer != null) + { + context!.Principal!.Identities.FirstOrDefault()?.AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier)); + context!.Principal!.Identities.FirstOrDefault()?.AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier)); + } + } + + await onTokenValidatedHandler(context).ConfigureAwait(false); + }; + + // Handling the sign-out: removing the account from MSAL.NET cache + var signOutHandler = options.Events.OnRedirectToIdentityProviderForSignOut; + options.Events.OnRedirectToIdentityProviderForSignOut = async context => + { + // Remove the account from MSAL.NET token cache + var tokenAcquisition = context!.HttpContext.RequestServices.GetRequiredService(); + await tokenAcquisition.RemoveAccountAsync(context).ConfigureAwait(false); + await signOutHandler(context).ConfigureAwait(false); + }; + }); + } + } + } } diff --git a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs index 5f150e795..dfaba83af 100644 --- a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderExtensions.cs @@ -17,447 +17,447 @@ namespace Microsoft.Identity.Web { - /// - /// Extensions for the for startup initialization. - /// - public static partial class MicrosoftIdentityWebAppAuthenticationBuilderExtensions - { - /// - /// Add authentication to a web app with Microsoft identity platform. - /// This method expects the configuration file will have a section, named "AzureAd" as default, - /// with the necessary settings to initialize authentication options. - /// - /// The to which to add this configuration. - /// The configuration instance. - /// The configuration section with the necessary settings to initialize authentication options. - /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - /// The cookie-based scheme name to be used. By default it uses "Cookies". - /// - /// Set to true if you want to debug, or just understand the OpenID Connect events. - /// - /// The builder for chaining. - public static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApp( - this AuthenticationBuilder builder, - IConfiguration configuration, - string configSectionName = Constants.AzureAd, - string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, - string? cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, - bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) - { - if (configuration == null) - { - throw new ArgumentException(nameof(configuration)); - } - - if (string.IsNullOrEmpty(configSectionName)) - { - throw new ArgumentException(nameof(configSectionName)); - } - - IConfigurationSection configurationSection = configuration.GetSection(configSectionName); - - return builder.AddMicrosoftIdentityWebApp( - configurationSection, - openIdConnectScheme, - cookieScheme, - subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); - } - - /// - /// Add authentication with Microsoft identity platform. - /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. - /// - /// The to which to add this configuration. - /// The configuration section from which to get the options. - /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - /// The cookie-based scheme name to be used. By default it uses "Cookies". - /// - /// Set to true if you want to debug, or just understand the OpenID Connect events. - /// - /// The authentication builder for chaining. - public static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApp( - this AuthenticationBuilder builder, - IConfigurationSection configurationSection, - string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, - string? cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, - bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (configurationSection == null) - { - throw new ArgumentException(nameof(configurationSection)); - } - - return builder.AddMicrosoftIdentityWebAppWithConfiguration( - options => configurationSection.Bind(options), - null, - openIdConnectScheme, - cookieScheme, - subscribeToOpenIdConnectMiddlewareDiagnosticsEvents, - configurationSection); - } - - /// - /// Add authentication with Microsoft identity platform. - /// - /// The to which to add this configuration. - /// The action to configure . - /// The action to configure . - /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - /// The cookie-based scheme name to be used. By default it uses "Cookies". - /// - /// Set to true if you want to debug, or just understand the OpenID Connect events. - /// - /// The authentication builder for chaining. - public static MicrosoftIdentityWebAppAuthenticationBuilder AddMicrosoftIdentityWebApp( - this AuthenticationBuilder builder, - Action configureMicrosoftIdentityOptions, - Action? configureCookieAuthenticationOptions = null, - string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, - string? cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, - bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - return builder.AddMicrosoftWebAppWithoutConfiguration( - configureMicrosoftIdentityOptions, - configureCookieAuthenticationOptions, - openIdConnectScheme, - cookieScheme, - subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); - } - - /// - /// Add authentication with Microsoft identity platform. - /// - /// The to which to add this configuration. - /// The action to configure . - /// The action to configure . - /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - /// The cookie-based scheme name to be used. By default it uses "Cookies". - /// - /// Set to true if you want to debug, or just understand the OpenID Connect events. - /// - /// Configuration section. - /// The authentication builder for chaining. - private static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebAppWithConfiguration( - this AuthenticationBuilder builder, - Action configureMicrosoftIdentityOptions, - Action? configureCookieAuthenticationOptions, - string openIdConnectScheme, - string? cookieScheme, - bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents, - IConfigurationSection configurationSection) - { - AddMicrosoftIdentityWebAppInternal( - builder, - configureMicrosoftIdentityOptions, - configureCookieAuthenticationOptions, - openIdConnectScheme, - cookieScheme, - subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); - - return new MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration( - builder.Services, - openIdConnectScheme, - configureMicrosoftIdentityOptions, - configurationSection); - } - - /// - /// Add authentication with Microsoft identity platform. - /// - /// The to which to add this configuration. - /// The action to configure . - /// The action to configure . - /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". - /// The cookie-based scheme name to be used. By default it uses "Cookies". - /// - /// Set to true if you want to debug, or just understand the OpenID Connect events. - /// - /// The authentication builder for chaining. - private static MicrosoftIdentityWebAppAuthenticationBuilder AddMicrosoftWebAppWithoutConfiguration( - this AuthenticationBuilder builder, - Action configureMicrosoftIdentityOptions, - Action? configureCookieAuthenticationOptions, - string openIdConnectScheme, - string? cookieScheme, - bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) - { - if (!AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) - { - AddMicrosoftIdentityWebAppInternal( - builder, - configureMicrosoftIdentityOptions, - configureCookieAuthenticationOptions, - openIdConnectScheme, - cookieScheme, - subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); - } - else - { - builder.Services.AddAuthentication(AppServicesAuthenticationDefaults.AuthenticationScheme) - .AddAppServicesAuthentication(); - } - - return new MicrosoftIdentityWebAppAuthenticationBuilder( - builder.Services, - openIdConnectScheme, - configureMicrosoftIdentityOptions, - null); - } - - private static void AddMicrosoftIdentityWebAppInternal( - AuthenticationBuilder builder, - Action configureMicrosoftIdentityOptions, - Action? configureCookieAuthenticationOptions, - string openIdConnectScheme, - string? cookieScheme, - bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) - { - if (builder == null) - { - throw new ArgumentNullException(nameof(builder)); - } - - if (configureMicrosoftIdentityOptions == null) - { - throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); - } - - builder.Services.Configure(configureMicrosoftIdentityOptions); - builder.Services.AddHttpClient(); - - builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, MicrosoftIdentityOptionsValidation>()); - - if (!string.IsNullOrEmpty(cookieScheme)) - { - Action emptyOption = option => { }; - builder.AddCookie(cookieScheme, configureCookieAuthenticationOptions ?? emptyOption); - } - - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(ctx => - { - // ITempDataDictionaryFactory is not always available, so we don't require it - var tempFactory = ctx.GetService(); - var env = ctx.GetService(); // ex. Azure Functions will not have an env. - - if (env != null) - { - return TempDataLoginErrorAccessor.Create(tempFactory, env.IsDevelopment()); - } - else - { - return TempDataLoginErrorAccessor.Create(tempFactory, false); - } - }); - - if (subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) - { - builder.Services.AddSingleton(); - } - - if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) - { - builder.Services.AddAuthentication(AppServicesAuthenticationDefaults.AuthenticationScheme) - .AddAppServicesAuthentication(); - return; - } - - builder.AddOpenIdConnect(openIdConnectScheme, options => { }); - builder.Services.AddOptions(openIdConnectScheme) - .Configure>((options, serviceProvider, microsoftIdentityOptions) => - { - PopulateOpenIdOptionsFromMicrosoftIdentityOptions(options, microsoftIdentityOptions.Value); - - var b2cOidcHandlers = new AzureADB2COpenIDConnectEventHandlers( - openIdConnectScheme, - microsoftIdentityOptions.Value, - serviceProvider.GetRequiredService()); - - if (!string.IsNullOrEmpty(cookieScheme)) - { - options.SignInScheme = cookieScheme; - } - - if (string.IsNullOrWhiteSpace(options.Authority)) - { - options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions.Value); - } - - // This is a Microsoft identity platform web app - options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority); - - // B2C doesn't have preferred_username claims - if (microsoftIdentityOptions.Value.IsB2C) - { - options.TokenValidationParameters.NameClaimType = ClaimConstants.Name; - } - else - { - options.TokenValidationParameters.NameClaimType = ClaimConstants.PreferredUserName; - } - - // If the developer registered an IssuerValidator, do not overwrite it - if (options.TokenValidationParameters.ValidateIssuer && options.TokenValidationParameters.IssuerValidator == null) - { - // If you want to restrict the users that can sign-in to several organizations - // Set the tenant value in the appsettings.json file to 'organizations', and add the - // issuers you want to accept to options.TokenValidationParameters.ValidIssuers collection - MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory = - serviceProvider.GetRequiredService(); - - options.TokenValidationParameters.IssuerValidator = - microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate; - } - - // Avoids having users being presented the select account dialog when they are already signed-in - // for instance when going through incremental consent - var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider; - options.Events.OnRedirectToIdentityProvider = async context => - { - var login = context.Properties.GetParameter(OpenIdConnectParameterNames.LoginHint); - if (!string.IsNullOrWhiteSpace(login)) - { - context.ProtocolMessage.LoginHint = login; - - // delete the login_hint from the Properties when we are done otherwise - // it will take up extra space in the cookie. - context.Properties.Parameters.Remove(OpenIdConnectParameterNames.LoginHint); - } - - var domainHint = context.Properties.GetParameter(OpenIdConnectParameterNames.DomainHint); - if (!string.IsNullOrWhiteSpace(domainHint)) - { - context.ProtocolMessage.DomainHint = domainHint; - - // delete the domain_hint from the Properties when we are done otherwise - // it will take up extra space in the cookie. - context.Properties.Parameters.Remove(OpenIdConnectParameterNames.DomainHint); - } - - context.ProtocolMessage.SetParameter(Constants.ClientInfo, Constants.One); - context.ProtocolMessage.SetParameter(Constants.TelemetryHeaderKey, IdHelper.CreateTelemetryInfo()); - - // Additional claims - if (context.Properties.Items.ContainsKey(OidcConstants.AdditionalClaims)) - { - context.ProtocolMessage.SetParameter( - OidcConstants.AdditionalClaims, - context.Properties.Items[OidcConstants.AdditionalClaims]); - } - - if (microsoftIdentityOptions.Value.IsB2C) - { - // When a new Challenge is returned using any B2C user flow different than susi, we must change - // the ProtocolMessage.IssuerAddress to the desired user flow otherwise the redirect would use the susi user flow - await b2cOidcHandlers.OnRedirectToIdentityProvider(context).ConfigureAwait(false); - } - - await redirectToIdpHandler(context).ConfigureAwait(false); - }; - - if (microsoftIdentityOptions.Value.IsB2C) - { - var remoteFailureHandler = options.Events.OnRemoteFailure; - options.Events.OnRemoteFailure = async context => - { - // Handles the error when a user cancels an action on the Azure Active Directory B2C UI. - // Handle the error code that Azure Active Directory B2C throws when trying to reset a password from the login page - // because password reset is not supported by a "sign-up or sign-in user flow". - await b2cOidcHandlers.OnRemoteFailure(context).ConfigureAwait(false); - - await remoteFailureHandler(context).ConfigureAwait(false); - }; - } - - if (subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) - { - var diagnostics = serviceProvider.GetRequiredService(); - - diagnostics.Subscribe(options.Events); - } - }); - } - - internal static void PopulateOpenIdOptionsFromMicrosoftIdentityOptions(OpenIdConnectOptions options, MicrosoftIdentityOptions microsoftIdentityOptions) - { - options.Authority = microsoftIdentityOptions.Authority; - options.ClientId = microsoftIdentityOptions.ClientId; - options.ClientSecret = microsoftIdentityOptions.ClientSecret; - options.Configuration = microsoftIdentityOptions.Configuration; - options.ConfigurationManager = microsoftIdentityOptions.ConfigurationManager; - options.GetClaimsFromUserInfoEndpoint = microsoftIdentityOptions.GetClaimsFromUserInfoEndpoint; - foreach (ClaimAction c in microsoftIdentityOptions.ClaimActions) - { - options.ClaimActions.Add(c); - } - - options.RequireHttpsMetadata = microsoftIdentityOptions.RequireHttpsMetadata; - options.MetadataAddress = microsoftIdentityOptions.MetadataAddress; - options.Events = microsoftIdentityOptions.Events; - options.MaxAge = microsoftIdentityOptions.MaxAge; - options.ProtocolValidator = microsoftIdentityOptions.ProtocolValidator; - options.SignedOutCallbackPath = microsoftIdentityOptions.SignedOutCallbackPath; - options.SignedOutRedirectUri = microsoftIdentityOptions.SignedOutRedirectUri; - options.RefreshOnIssuerKeyNotFound = microsoftIdentityOptions.RefreshOnIssuerKeyNotFound; - options.AuthenticationMethod = microsoftIdentityOptions.AuthenticationMethod; - options.Resource = microsoftIdentityOptions.Resource; - options.ResponseMode = microsoftIdentityOptions.ResponseMode; - options.ResponseType = microsoftIdentityOptions.ResponseType; - options.Prompt = microsoftIdentityOptions.Prompt; - - foreach (string scope in microsoftIdentityOptions.Scope) - { - options.Scope.Add(scope); - } - - options.RemoteSignOutPath = microsoftIdentityOptions.RemoteSignOutPath; - options.SignOutScheme = microsoftIdentityOptions.SignOutScheme; - options.StateDataFormat = microsoftIdentityOptions.StateDataFormat; - options.StringDataFormat = microsoftIdentityOptions.StringDataFormat; - options.SecurityTokenValidator = microsoftIdentityOptions.SecurityTokenValidator; - options.TokenValidationParameters = microsoftIdentityOptions.TokenValidationParameters; - options.UseTokenLifetime = microsoftIdentityOptions.UseTokenLifetime; - options.SkipUnrecognizedRequests = microsoftIdentityOptions.SkipUnrecognizedRequests; - options.DisableTelemetry = microsoftIdentityOptions.DisableTelemetry; - options.NonceCookie = microsoftIdentityOptions.NonceCookie; - options.UsePkce = microsoftIdentityOptions.UsePkce; + /// + /// Extensions for the for startup initialization. + /// + public static partial class MicrosoftIdentityWebAppAuthenticationBuilderExtensions + { + /// + /// Add authentication to a web app with Microsoft identity platform. + /// This method expects the configuration file will have a section, named "AzureAd" as default, + /// with the necessary settings to initialize authentication options. + /// + /// The to which to add this configuration. + /// The configuration instance. + /// The configuration section with the necessary settings to initialize authentication options. + /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + /// The cookie-based scheme name to be used. By default it uses "Cookies". + /// + /// Set to true if you want to debug, or just understand the OpenID Connect events. + /// + /// The builder for chaining. + public static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApp( + this AuthenticationBuilder builder, + IConfiguration configuration, + string configSectionName = Constants.AzureAd, + string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, + string? cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, + bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) + { + if (configuration == null) + { + throw new ArgumentException(nameof(configuration)); + } + + if (string.IsNullOrEmpty(configSectionName)) + { + throw new ArgumentException(nameof(configSectionName)); + } + + IConfigurationSection configurationSection = configuration.GetSection(configSectionName); + + return builder.AddMicrosoftIdentityWebApp( + configurationSection, + openIdConnectScheme, + cookieScheme, + subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); + } + + /// + /// Add authentication with Microsoft identity platform. + /// This method expects the configuration file will have a section, named "AzureAd" as default, with the necessary settings to initialize authentication options. + /// + /// The to which to add this configuration. + /// The configuration section from which to get the options. + /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + /// The cookie-based scheme name to be used. By default it uses "Cookies". + /// + /// Set to true if you want to debug, or just understand the OpenID Connect events. + /// + /// The authentication builder for chaining. + public static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebApp( + this AuthenticationBuilder builder, + IConfigurationSection configurationSection, + string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, + string? cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, + bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configurationSection == null) + { + throw new ArgumentException(nameof(configurationSection)); + } + + return builder.AddMicrosoftIdentityWebAppWithConfiguration( + options => configurationSection.Bind(options), + null, + openIdConnectScheme, + cookieScheme, + subscribeToOpenIdConnectMiddlewareDiagnosticsEvents, + configurationSection); + } + + /// + /// Add authentication with Microsoft identity platform. + /// + /// The to which to add this configuration. + /// The action to configure . + /// The action to configure . + /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + /// The cookie-based scheme name to be used. By default it uses "Cookies". + /// + /// Set to true if you want to debug, or just understand the OpenID Connect events. + /// + /// The authentication builder for chaining. + public static MicrosoftIdentityWebAppAuthenticationBuilder AddMicrosoftIdentityWebApp( + this AuthenticationBuilder builder, + Action configureMicrosoftIdentityOptions, + Action? configureCookieAuthenticationOptions = null, + string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, + string? cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, + bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.AddMicrosoftWebAppWithoutConfiguration( + configureMicrosoftIdentityOptions, + configureCookieAuthenticationOptions, + openIdConnectScheme, + cookieScheme, + subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); + } + + /// + /// Add authentication with Microsoft identity platform. + /// + /// The to which to add this configuration. + /// The action to configure . + /// The action to configure . + /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + /// The cookie-based scheme name to be used. By default it uses "Cookies". + /// + /// Set to true if you want to debug, or just understand the OpenID Connect events. + /// + /// Configuration section. + /// The authentication builder for chaining. + private static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebAppWithConfiguration( + this AuthenticationBuilder builder, + Action configureMicrosoftIdentityOptions, + Action? configureCookieAuthenticationOptions, + string openIdConnectScheme, + string? cookieScheme, + bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents, + IConfigurationSection configurationSection) + { + AddMicrosoftIdentityWebAppInternal( + builder, + configureMicrosoftIdentityOptions, + configureCookieAuthenticationOptions, + openIdConnectScheme, + cookieScheme, + subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); + + return new MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration( + builder.Services, + openIdConnectScheme, + configureMicrosoftIdentityOptions, + configurationSection); + } + + /// + /// Add authentication with Microsoft identity platform. + /// + /// The to which to add this configuration. + /// The action to configure . + /// The action to configure . + /// The OpenID Connect scheme name to be used. By default it uses "OpenIdConnect". + /// The cookie-based scheme name to be used. By default it uses "Cookies". + /// + /// Set to true if you want to debug, or just understand the OpenID Connect events. + /// + /// The authentication builder for chaining. + private static MicrosoftIdentityWebAppAuthenticationBuilder AddMicrosoftWebAppWithoutConfiguration( + this AuthenticationBuilder builder, + Action configureMicrosoftIdentityOptions, + Action? configureCookieAuthenticationOptions, + string openIdConnectScheme, + string? cookieScheme, + bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) + { + if (!AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) + { + AddMicrosoftIdentityWebAppInternal( + builder, + configureMicrosoftIdentityOptions, + configureCookieAuthenticationOptions, + openIdConnectScheme, + cookieScheme, + subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); + } + else + { + builder.Services.AddAuthentication(AppServicesAuthenticationDefaults.AuthenticationScheme) + .AddAppServicesAuthentication(); + } + + return new MicrosoftIdentityWebAppAuthenticationBuilder( + builder.Services, + openIdConnectScheme, + configureMicrosoftIdentityOptions, + null); + } + + private static void AddMicrosoftIdentityWebAppInternal( + AuthenticationBuilder builder, + Action configureMicrosoftIdentityOptions, + Action? configureCookieAuthenticationOptions, + string openIdConnectScheme, + string? cookieScheme, + bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configureMicrosoftIdentityOptions == null) + { + throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions)); + } + + builder.Services.Configure(configureMicrosoftIdentityOptions); + builder.Services.AddHttpClient(); + + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, MicrosoftIdentityOptionsValidation>()); + + if (!string.IsNullOrEmpty(cookieScheme)) + { + Action emptyOption = option => { }; + builder.AddCookie(cookieScheme, configureCookieAuthenticationOptions ?? emptyOption); + } + + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(ctx => + { + // ITempDataDictionaryFactory is not always available, so we don't require it + var tempFactory = ctx.GetService(); + var env = ctx.GetService(); // ex. Azure Functions will not have an env. + + if (env != null) + { + return TempDataLoginErrorAccessor.Create(tempFactory, env.IsDevelopment()); + } + else + { + return TempDataLoginErrorAccessor.Create(tempFactory, false); + } + }); + + if (subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) + { + builder.Services.AddSingleton(); + } + + if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) + { + builder.Services.AddAuthentication(AppServicesAuthenticationDefaults.AuthenticationScheme) + .AddAppServicesAuthentication(); + return; + } + + builder.AddOpenIdConnect(openIdConnectScheme, options => { }); + builder.Services.AddOptions(openIdConnectScheme) + .Configure>((options, serviceProvider, microsoftIdentityOptions) => + { + PopulateOpenIdOptionsFromMicrosoftIdentityOptions(options, microsoftIdentityOptions.Value); + + var b2cOidcHandlers = new AzureADB2COpenIDConnectEventHandlers( + openIdConnectScheme, + microsoftIdentityOptions.Value, + serviceProvider.GetRequiredService()); + + if (!string.IsNullOrEmpty(cookieScheme)) + { + options.SignInScheme = cookieScheme; + } + + if (string.IsNullOrWhiteSpace(options.Authority)) + { + options.Authority = AuthorityHelpers.BuildAuthority(microsoftIdentityOptions.Value); + } + + // This is a Microsoft identity platform web app + options.Authority = AuthorityHelpers.EnsureAuthorityIsV2(options.Authority); + + // B2C doesn't have preferred_username claims + if (microsoftIdentityOptions.Value.IsB2C) + { + options.TokenValidationParameters.NameClaimType = ClaimConstants.Name; + } + else + { + options.TokenValidationParameters.NameClaimType = ClaimConstants.PreferredUserName; + } + + // If the developer registered an IssuerValidator, do not overwrite it + if (options.TokenValidationParameters.ValidateIssuer && options.TokenValidationParameters.IssuerValidator == null) + { + // If you want to restrict the users that can sign-in to several organizations + // Set the tenant value in the appsettings.json file to 'organizations', and add the + // issuers you want to accept to options.TokenValidationParameters.ValidIssuers collection + MicrosoftIdentityIssuerValidatorFactory microsoftIdentityIssuerValidatorFactory = + serviceProvider.GetRequiredService(); + + options.TokenValidationParameters.IssuerValidator = + microsoftIdentityIssuerValidatorFactory.GetAadIssuerValidator(options.Authority).Validate; + } + + // Avoids having users being presented the select account dialog when they are already signed-in + // for instance when going through incremental consent + var redirectToIdpHandler = options.Events.OnRedirectToIdentityProvider; + options.Events.OnRedirectToIdentityProvider = async context => + { + var login = context.Properties.GetParameter(OpenIdConnectParameterNames.LoginHint); + if (!string.IsNullOrWhiteSpace(login)) + { + context.ProtocolMessage.LoginHint = login; + + // delete the login_hint from the Properties when we are done otherwise + // it will take up extra space in the cookie. + context.Properties.Parameters.Remove(OpenIdConnectParameterNames.LoginHint); + } + + var domainHint = context.Properties.GetParameter(OpenIdConnectParameterNames.DomainHint); + if (!string.IsNullOrWhiteSpace(domainHint)) + { + context.ProtocolMessage.DomainHint = domainHint; + + // delete the domain_hint from the Properties when we are done otherwise + // it will take up extra space in the cookie. + context.Properties.Parameters.Remove(OpenIdConnectParameterNames.DomainHint); + } + + context.ProtocolMessage.SetParameter(Constants.ClientInfo, Constants.One); + context.ProtocolMessage.SetParameter(Constants.TelemetryHeaderKey, IdHelper.CreateTelemetryInfo()); + + // Additional claims + if (context.Properties.Items.ContainsKey(OidcConstants.AdditionalClaims)) + { + context.ProtocolMessage.SetParameter( + OidcConstants.AdditionalClaims, + context.Properties.Items[OidcConstants.AdditionalClaims]); + } + + if (microsoftIdentityOptions.Value.IsB2C) + { + // When a new Challenge is returned using any B2C user flow different than susi, we must change + // the ProtocolMessage.IssuerAddress to the desired user flow otherwise the redirect would use the susi user flow + await b2cOidcHandlers.OnRedirectToIdentityProvider(context).ConfigureAwait(false); + } + + await redirectToIdpHandler(context).ConfigureAwait(false); + }; + + if (microsoftIdentityOptions.Value.IsB2C) + { + var remoteFailureHandler = options.Events.OnRemoteFailure; + options.Events.OnRemoteFailure = async context => + { + // Handles the error when a user cancels an action on the Azure Active Directory B2C UI. + // Handle the error code that Azure Active Directory B2C throws when trying to reset a password from the login page + // because password reset is not supported by a "sign-up or sign-in user flow". + await b2cOidcHandlers.OnRemoteFailure(context).ConfigureAwait(false); + + await remoteFailureHandler(context).ConfigureAwait(false); + }; + } + + if (subscribeToOpenIdConnectMiddlewareDiagnosticsEvents) + { + var diagnostics = serviceProvider.GetRequiredService(); + + diagnostics.Subscribe(options.Events); + } + }); + } + + internal static void PopulateOpenIdOptionsFromMicrosoftIdentityOptions(OpenIdConnectOptions options, MicrosoftIdentityOptions microsoftIdentityOptions) + { + options.Authority = microsoftIdentityOptions.Authority; + options.ClientId = microsoftIdentityOptions.ClientId; + options.ClientSecret = microsoftIdentityOptions.ClientSecret; + options.Configuration = microsoftIdentityOptions.Configuration; + options.ConfigurationManager = microsoftIdentityOptions.ConfigurationManager; + options.GetClaimsFromUserInfoEndpoint = microsoftIdentityOptions.GetClaimsFromUserInfoEndpoint; + foreach (ClaimAction c in microsoftIdentityOptions.ClaimActions) + { + options.ClaimActions.Add(c); + } + + options.RequireHttpsMetadata = microsoftIdentityOptions.RequireHttpsMetadata; + options.MetadataAddress = microsoftIdentityOptions.MetadataAddress; + options.Events = microsoftIdentityOptions.Events; + options.MaxAge = microsoftIdentityOptions.MaxAge; + options.ProtocolValidator = microsoftIdentityOptions.ProtocolValidator; + options.SignedOutCallbackPath = microsoftIdentityOptions.SignedOutCallbackPath; + options.SignedOutRedirectUri = microsoftIdentityOptions.SignedOutRedirectUri; + options.RefreshOnIssuerKeyNotFound = microsoftIdentityOptions.RefreshOnIssuerKeyNotFound; + options.AuthenticationMethod = microsoftIdentityOptions.AuthenticationMethod; + options.Resource = microsoftIdentityOptions.Resource; + options.ResponseMode = microsoftIdentityOptions.ResponseMode; + options.ResponseType = microsoftIdentityOptions.ResponseType; + options.Prompt = microsoftIdentityOptions.Prompt; + + foreach (string scope in microsoftIdentityOptions.Scope) + { + options.Scope.Add(scope); + } + + options.RemoteSignOutPath = microsoftIdentityOptions.RemoteSignOutPath; + options.SignOutScheme = microsoftIdentityOptions.SignOutScheme; + options.StateDataFormat = microsoftIdentityOptions.StateDataFormat; + options.StringDataFormat = microsoftIdentityOptions.StringDataFormat; + options.SecurityTokenValidator = microsoftIdentityOptions.SecurityTokenValidator; + options.TokenValidationParameters = microsoftIdentityOptions.TokenValidationParameters; + options.UseTokenLifetime = microsoftIdentityOptions.UseTokenLifetime; + options.SkipUnrecognizedRequests = microsoftIdentityOptions.SkipUnrecognizedRequests; + options.DisableTelemetry = microsoftIdentityOptions.DisableTelemetry; + options.NonceCookie = microsoftIdentityOptions.NonceCookie; + options.UsePkce = microsoftIdentityOptions.UsePkce; #if DOTNET_50_AND_ABOVE - options.AutomaticRefreshInterval = microsoftIdentityOptions.AutomaticRefreshInterval; - options.RefreshInterval = microsoftIdentityOptions.RefreshInterval; - options.MapInboundClaims = microsoftIdentityOptions.MapInboundClaims; + options.AutomaticRefreshInterval = microsoftIdentityOptions.AutomaticRefreshInterval; + options.RefreshInterval = microsoftIdentityOptions.RefreshInterval; + options.MapInboundClaims = microsoftIdentityOptions.MapInboundClaims; #endif - options.BackchannelTimeout = microsoftIdentityOptions.BackchannelTimeout; - options.BackchannelHttpHandler = microsoftIdentityOptions.BackchannelHttpHandler; - options.Backchannel = microsoftIdentityOptions.Backchannel; - options.DataProtectionProvider = microsoftIdentityOptions.DataProtectionProvider; - options.CallbackPath = microsoftIdentityOptions.CallbackPath; - options.AccessDeniedPath = microsoftIdentityOptions.AccessDeniedPath; - options.ReturnUrlParameter = microsoftIdentityOptions.ReturnUrlParameter; - options.SignInScheme = microsoftIdentityOptions.SignInScheme; - options.RemoteAuthenticationTimeout = microsoftIdentityOptions.RemoteAuthenticationTimeout; - options.Events = microsoftIdentityOptions.Events; - options.SaveTokens = microsoftIdentityOptions.SaveTokens; - options.CorrelationCookie = microsoftIdentityOptions.CorrelationCookie; - options.ClaimsIssuer = microsoftIdentityOptions.ClaimsIssuer; - options.Events = microsoftIdentityOptions.Events; - options.EventsType = microsoftIdentityOptions.EventsType; - options.ForwardDefault = microsoftIdentityOptions.ForwardDefault; - options.ForwardAuthenticate = microsoftIdentityOptions.ForwardAuthenticate; - options.ForwardChallenge = microsoftIdentityOptions.ForwardChallenge; - options.ForwardForbid = microsoftIdentityOptions.ForwardForbid; - options.ForwardSignIn = microsoftIdentityOptions.ForwardSignIn; - options.ForwardSignOut = microsoftIdentityOptions.ForwardSignOut; - options.ForwardDefaultSelector = microsoftIdentityOptions.ForwardDefaultSelector; - } - } + options.BackchannelTimeout = microsoftIdentityOptions.BackchannelTimeout; + options.BackchannelHttpHandler = microsoftIdentityOptions.BackchannelHttpHandler; + options.Backchannel = microsoftIdentityOptions.Backchannel; + options.DataProtectionProvider = microsoftIdentityOptions.DataProtectionProvider; + options.CallbackPath = microsoftIdentityOptions.CallbackPath; + options.AccessDeniedPath = microsoftIdentityOptions.AccessDeniedPath; + options.ReturnUrlParameter = microsoftIdentityOptions.ReturnUrlParameter; + options.SignInScheme = microsoftIdentityOptions.SignInScheme; + options.RemoteAuthenticationTimeout = microsoftIdentityOptions.RemoteAuthenticationTimeout; + options.Events = microsoftIdentityOptions.Events; + options.SaveTokens = microsoftIdentityOptions.SaveTokens; + options.CorrelationCookie = microsoftIdentityOptions.CorrelationCookie; + options.ClaimsIssuer = microsoftIdentityOptions.ClaimsIssuer; + options.Events = microsoftIdentityOptions.Events; + options.EventsType = microsoftIdentityOptions.EventsType; + options.ForwardDefault = microsoftIdentityOptions.ForwardDefault; + options.ForwardAuthenticate = microsoftIdentityOptions.ForwardAuthenticate; + options.ForwardChallenge = microsoftIdentityOptions.ForwardChallenge; + options.ForwardForbid = microsoftIdentityOptions.ForwardForbid; + options.ForwardSignIn = microsoftIdentityOptions.ForwardSignIn; + options.ForwardSignOut = microsoftIdentityOptions.ForwardSignOut; + options.ForwardDefaultSelector = microsoftIdentityOptions.ForwardDefaultSelector; + } + } } diff --git a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration.cs b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration.cs index 9e4be0c16..75b980c18 100644 --- a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration.cs +++ b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration.cs @@ -8,55 +8,55 @@ namespace Microsoft.Identity.Web { - /// - /// Builder for a Microsoft identity web app authentication where configuration is - /// available for EnableTokenAcquisitionToCallDownstreamApi. - /// - public class MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration : MicrosoftIdentityWebAppAuthenticationBuilder - { - /// - /// Constructor. - /// - /// The services being configured. - /// Default scheme used for OpenIdConnect. - /// Action called to configure - /// the Microsoft identity options. - /// Optional configuration section. - internal MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration( - IServiceCollection services, - string openIdConnectScheme, - Action configureMicrosoftIdentityOptions, - IConfigurationSection configurationSection) - : base(services, openIdConnectScheme, configureMicrosoftIdentityOptions, configurationSection) - { - if (configurationSection == null) - { - throw new ArgumentNullException(nameof(configurationSection)); - } - } + /// + /// Builder for a Microsoft identity web app authentication where configuration is + /// available for EnableTokenAcquisitionToCallDownstreamApi. + /// + public class MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration : MicrosoftIdentityWebAppAuthenticationBuilder + { + /// + /// Constructor. + /// + /// The services being configured. + /// Default scheme used for OpenIdConnect. + /// Action called to configure + /// the Microsoft identity options. + /// Optional configuration section. + internal MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration( + IServiceCollection services, + string openIdConnectScheme, + Action configureMicrosoftIdentityOptions, + IConfigurationSection configurationSection) + : base(services, openIdConnectScheme, configureMicrosoftIdentityOptions, configurationSection) + { + if (configurationSection == null) + { + throw new ArgumentNullException(nameof(configurationSection)); + } + } - /// - /// Add support for the web app to acquire tokens to call an API. - /// - /// Optional initial scopes to request. - /// The authentication builder for chaining. - public new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( - IEnumerable? initialScopes = null) - { - return EnableTokenAcquisitionToCallDownstreamApi( - options => - { - ConfigurationSection.Bind(options); - if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) - { - options.ClientId = AppServicesAuthenticationInformation.ClientId; - options.ClientSecret = AppServicesAuthenticationInformation.ClientSecret; - options.Instance = AppServicesAuthenticationInformation.Issuer; - } + /// + /// Add support for the web app to acquire tokens to call an API. + /// + /// Optional initial scopes to request. + /// The authentication builder for chaining. + public new MicrosoftIdentityAppCallsWebApiAuthenticationBuilder EnableTokenAcquisitionToCallDownstreamApi( + IEnumerable? initialScopes = null) + { + return EnableTokenAcquisitionToCallDownstreamApi( + options => + { + ConfigurationSection.Bind(options); + if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled) + { + options.ClientId = AppServicesAuthenticationInformation.ClientId; + options.ClientSecret = AppServicesAuthenticationInformation.ClientSecret; + options.Instance = AppServicesAuthenticationInformation.Issuer; + } - Services.AddHttpClient(); - }, - initialScopes); - } - } + Services.AddHttpClient(); + }, + initialScopes); + } + } } diff --git a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppServiceCollectionExtensions.cs b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppServiceCollectionExtensions.cs index 5a2e25413..6a621a59d 100644 --- a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppServiceCollectionExtensions.cs +++ b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppServiceCollectionExtensions.cs @@ -9,44 +9,44 @@ namespace Microsoft.Identity.Web { - /// - /// Extension for IServiceCollection for startup initialization. - /// - public static partial class MicrosoftIdentityWebAppServiceCollectionExtensions - { - /// - /// Add authentication with Microsoft identity platform. - /// This method expects the configuration file will have a section, (by default named "AzureAd"), with the necessary settings to - /// initialize the authentication options. - /// - /// Service collection to which to add authentication. - /// The IConfiguration object. - /// The name of the configuration section with the necessary - /// settings to initialize authentication options. - /// Optional name for the open id connect authentication scheme - /// (by default OpenIdConnectDefaults.AuthenticationScheme). This can be specified when you want to support - /// several OpenIdConnect identity providers. - /// Optional name for the cookie authentication scheme - /// (by default OpenIdConnectDefaults.AuthenticationScheme). - /// - /// Set to true if you want to debug, or just understand the OpenIdConnect events. - /// - /// The authentication builder to chain extension methods. - public static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebAppAuthentication( - this IServiceCollection services, - IConfiguration configuration, - string configSectionName = Constants.AzureAd, - string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, - string cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, - bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) - { - AuthenticationBuilder builder = services.AddAuthentication(openIdConnectScheme); - return builder.AddMicrosoftIdentityWebApp( - configuration, - configSectionName, - openIdConnectScheme, - cookieScheme, - subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); - } - } + /// + /// Extension for IServiceCollection for startup initialization. + /// + public static partial class MicrosoftIdentityWebAppServiceCollectionExtensions + { + /// + /// Add authentication with Microsoft identity platform. + /// This method expects the configuration file will have a section, (by default named "AzureAd"), with the necessary settings to + /// initialize the authentication options. + /// + /// Service collection to which to add authentication. + /// The IConfiguration object. + /// The name of the configuration section with the necessary + /// settings to initialize authentication options. + /// Optional name for the open id connect authentication scheme + /// (by default OpenIdConnectDefaults.AuthenticationScheme). This can be specified when you want to support + /// several OpenIdConnect identity providers. + /// Optional name for the cookie authentication scheme + /// (by default OpenIdConnectDefaults.AuthenticationScheme). + /// + /// Set to true if you want to debug, or just understand the OpenIdConnect events. + /// + /// The authentication builder to chain extension methods. + public static MicrosoftIdentityWebAppAuthenticationBuilderWithConfiguration AddMicrosoftIdentityWebAppAuthentication( + this IServiceCollection services, + IConfiguration configuration, + string configSectionName = Constants.AzureAd, + string openIdConnectScheme = OpenIdConnectDefaults.AuthenticationScheme, + string cookieScheme = CookieAuthenticationDefaults.AuthenticationScheme, + bool subscribeToOpenIdConnectMiddlewareDiagnosticsEvents = false) + { + AuthenticationBuilder builder = services.AddAuthentication(openIdConnectScheme); + return builder.AddMicrosoftIdentityWebApp( + configuration, + configSectionName, + openIdConnectScheme, + cookieScheme, + subscribeToOpenIdConnectMiddlewareDiagnosticsEvents); + } + } } diff --git a/stylecop.json b/stylecop.json index 6defb6acb..f763d8dcc 100644 --- a/stylecop.json +++ b/stylecop.json @@ -1,23 +1,23 @@ { - // ACTION REQUIRED: This file was automatically added to your project, but it - // will not take effect until additional steps are taken to enable it. See the - // following page for additional information: - // - // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md + // ACTION REQUIRED: This file was automatically added to your project, but it + // will not take effect until additional steps are taken to enable it. See the + // following page for additional information: + // + // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/EnableConfiguration.md - "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", - "settings": { - "documentationRules": { - "xmlHeader": false, - "companyName": "Microsoft Corporation", - "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName}.", - "variables": { - "licenseName": "MIT License" - }, - "documentInternalElements": false - }, - "orderingRules": { - "usingDirectivesPlacement": "outsideNamespace" - } - } + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "xmlHeader": false, + "companyName": "Microsoft Corporation", + "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName}.", + "variables": { + "licenseName": "MIT License" + }, + "documentInternalElements": false + }, + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace" + } + } }