diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs index a621466b1..0d5c6ced1 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApi.cs @@ -80,7 +80,6 @@ public async Task CallWebApiForUserAsync( effectiveOptions.TokenAcquisitionOptions) .ConfigureAwait(false); - HttpResponseMessage response; using (HttpRequestMessage httpRequestMessage = new HttpRequestMessage( effectiveOptions.HttpMethod, apiUrl)) @@ -93,10 +92,8 @@ public async Task CallWebApiForUserAsync( httpRequestMessage.Headers.Add( Constants.Authorization, authResult.CreateAuthorizationHeader()); - response = await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); + return await _httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); } - - return response; } /// diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs index 1271d781f..c252676c3 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/DownstreamWebApiOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Net.Http; namespace Microsoft.Identity.Web @@ -9,7 +10,7 @@ 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 + public class DownstreamWebApiOptions : ICloneable { /// /// Base URL for the called downstream web API. For instance "https://graph.microsoft.com/beta/".. @@ -97,5 +98,14 @@ public string[] GetScopes() { return string.IsNullOrWhiteSpace(Scopes) ? new string[0] : Scopes.Split(' '); } + + /// + /// Clone the options (to be able to override them). + /// + /// A clone of the options. + object ICloneable.Clone() + { + return Clone(); + } } } diff --git a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs index c771bc0c5..2c98be76d 100644 --- a/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs +++ b/src/Microsoft.Identity.Web/DownstreamWebApiSupport/IDownstreamWebApi.cs @@ -54,9 +54,9 @@ public Task CallWebApiForUserAsync( /// /// A list method that returns an IEnumerable<MyItem>>. /// - /// public async Task<IEnumerable<MyItem>> GetAsync() + /// public Task<IEnumerable<MyItem>> GetAsync() /// { - /// return await _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( + /// return _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( /// ServiceName, /// null, /// options => @@ -68,9 +68,9 @@ public Task CallWebApiForUserAsync( /// /// Example of editing. /// - /// public async Task<MyItem> EditAsync(MyItem myItem) + /// public Task<MyItem> EditAsync(MyItem myItem) /// { - /// return await _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( + /// return _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( /// ServiceName, /// nyItem, /// options => diff --git a/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs b/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs index 230046588..cb8e7547d 100644 --- a/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs +++ b/src/Microsoft.Identity.Web/IncrementalConsentAndConditionalAccessHelper.cs @@ -60,7 +60,7 @@ public static AuthenticationProperties BuildAuthenticationProperties( scopes ??= new string[0]; var properties = new AuthenticationProperties(); - // Set the scopes, including the scopes that ADAL.NET / MSAL.NET need for the token cache + // Set the scopes, including the scopes that MSAL.NET needs for the token cache string[] additionalBuiltInScopes = { OidcConstants.ScopeOpenId, @@ -68,9 +68,9 @@ public static AuthenticationProperties BuildAuthenticationProperties( OidcConstants.ScopeProfile, }; - properties.SetParameter>( - OpenIdConnectParameterNames.Scope, - scopes.Union(additionalBuiltInScopes).ToList()); + HashSet oidcParams = new HashSet(scopes); + oidcParams.UnionWith(additionalBuiltInScopes); + properties.SetParameter(OpenIdConnectParameterNames.Scope, oidcParams); // Attempts to set the login_hint to avoid the logged-in user to be presented with an account selection dialog var loginHint = user.GetLoginHint(); diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index 1bf9ee7ff..c5d466661 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -1015,6 +1015,12 @@ Scopes. + + + Clone the options (to be able to override them). + + A clone of the options. + Interface used to call a downstream web API, for instance from controllers. @@ -1058,9 +1064,9 @@ A list method that returns an IEnumerable<MyItem>>. - public async Task<IEnumerable<MyItem>> GetAsync() + public Task<IEnumerable<MyItem>> GetAsync() { - return await _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( + return _downstreamWebApi.CallWebApiForUserAsync<object, IEnumerable<MyItem>>( ServiceName, null, options => @@ -1072,9 +1078,9 @@ Example of editing. - public async Task<MyItem> EditAsync(MyItem myItem) + public Task<MyItem> EditAsync(MyItem myItem) { - return await _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( + return _downstreamWebApi.CallWebApiForUserAsync<MyItem, MyItem>( ServiceName, nyItem, options => @@ -1685,22 +1691,22 @@ 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. @@ -1746,7 +1752,7 @@ 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 @@ -1754,49 +1760,49 @@ 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. diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs index 7aa4214ff..40fb4faee 100644 --- a/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityCircuitHandler.cs @@ -60,214 +60,4 @@ public static IServiceCollection AddMicrosoftIdentityConsentHandler( return services; } } - - /// - /// 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 -#pragma warning restore SA1402 // File may only contain a single type - { - 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; -#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(); - } - - /// - /// Boolean to determine if server is Blazor. - /// - public bool IsBlazorServer { get; set; } - - /// - /// 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 : -#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; - } - } - - /// - /// 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) : -#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; - } - } - - 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; - - if (microsoftIdentityWebChallengeUserException == null) - { -#pragma warning disable CA1062 // Validate arguments of public methods - 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); - - 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; - } - } - - /// - /// 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, - }; - - effectiveScopes = effectiveScopes.Union(additionalBuiltInScopes).ToArray(); - - 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; -#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('/')); - } - - 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 - { -#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. - _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!; - } - -#pragma warning disable SA1402 // File may only contain a single type - 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 MicrosoftIdentityConsentAndConditionalAccessHandler Service { get; } - - public AuthenticationStateProvider Provider { 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); - } - } } diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityConsentAndConditionalAccessHandler.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityConsentAndConditionalAccessHandler.cs new file mode 100644 index 000000000..c6dee66a4 --- /dev/null +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityConsentAndConditionalAccessHandler.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Identity.Web +{ + /// + /// Handler for Blazor specific APIs to handle incremental consent + /// and conditional access. + /// + public class MicrosoftIdentityConsentAndConditionalAccessHandler + { + private ClaimsPrincipal? _user; + private string? _baseUri; + private readonly IHttpContextAccessor? _httpContextAccessor; + + /// + /// 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; } + + /// + /// 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 : +#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; + } + } + + /// + /// 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) : +#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; + } + } + + 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; + + if (microsoftIdentityWebChallengeUserException == null) + { +#pragma warning disable CA1062 // Validate arguments of public methods + 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); + + 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; + } + } + + /// + /// 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, + }; + + effectiveScopes = effectiveScopes.Union(additionalBuiltInScopes); + + 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; +#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('/')); + } + + 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 + { +#pragma warning disable CS8602 // Dereference of a possibly null reference. HttpContext will not be null in this case. + _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!; + } +} diff --git a/src/Microsoft.Identity.Web/MicrosoftIdentityServiceHandler.cs b/src/Microsoft.Identity.Web/MicrosoftIdentityServiceHandler.cs new file mode 100644 index 000000000..4e351319c --- /dev/null +++ b/src/Microsoft.Identity.Web/MicrosoftIdentityServiceHandler.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server.Circuits; + +namespace Microsoft.Identity.Web +{ + internal class MicrosoftIdentityServiceHandler : CircuitHandler + { + public MicrosoftIdentityServiceHandler(MicrosoftIdentityConsentAndConditionalAccessHandler service, AuthenticationStateProvider provider, NavigationManager manager) + { + Service = service; + Provider = provider; + Manager = manager; + } + + public MicrosoftIdentityConsentAndConditionalAccessHandler Service { get; } + + public AuthenticationStateProvider Provider { 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); + } + } +} diff --git a/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs b/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs index 952b610d6..9d3f8af92 100644 --- a/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs +++ b/src/Microsoft.Identity.Web/Resource/JwtBearerMiddlewareDiagnostics.cs @@ -29,22 +29,22 @@ public JwtBearerMiddlewareDiagnostics(ILogger lo /// /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. /// - private Func s_onAuthenticationFailed = null!; + private Func _onAuthenticationFailed = null!; /// /// Invoked when a protocol message is first received. /// - private Func s_onMessageReceived = null!; + private Func _onMessageReceived = null!; /// /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. /// - private Func s_onTokenValidated = null!; + private Func _onTokenValidated = null!; /// /// Invoked before a challenge is sent back to the caller. /// - private Func s_onChallenge = null!; + private Func _onChallenge = null!; /// /// Subscribes to all the JwtBearer events, to help debugging, while @@ -56,16 +56,16 @@ public JwtBearerEvents Subscribe(JwtBearerEvents events) { events ??= new JwtBearerEvents(); - s_onAuthenticationFailed = events.OnAuthenticationFailed; + _onAuthenticationFailed = events.OnAuthenticationFailed; events.OnAuthenticationFailed = OnAuthenticationFailedAsync; - s_onMessageReceived = events.OnMessageReceived; + _onMessageReceived = events.OnMessageReceived; events.OnMessageReceived = OnMessageReceivedAsync; - s_onTokenValidated = events.OnTokenValidated; + _onTokenValidated = events.OnTokenValidated; events.OnTokenValidated = OnTokenValidatedAsync; - s_onChallenge = events.OnChallenge; + _onChallenge = events.OnChallenge; events.OnChallenge = OnChallengeAsync; return events; @@ -77,7 +77,7 @@ private async Task OnMessageReceivedAsync(MessageReceivedContext context) // 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); + await _onMessageReceived(context).ConfigureAwait(false); _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnMessageReceivedAsync))); } @@ -86,21 +86,21 @@ private async Task OnAuthenticationFailedAsync(AuthenticationFailedContext conte _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnAuthenticationFailedAsync))); // Place a breakpoint here and examine context.Exception - await s_onAuthenticationFailed(context).ConfigureAwait(false); + await _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); + await _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); + await _onChallenge(context).ConfigureAwait(false); _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnChallengeAsync))); } } diff --git a/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs b/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs index b69db5827..5b1866849 100644 --- a/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs +++ b/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs @@ -34,55 +34,55 @@ public OpenIdConnectMiddlewareDiagnostics(ILogger - private Func s_onRedirectToIdentityProvider = null!; + private Func _onRedirectToIdentityProvider = null!; /// /// Invoked when a protocol message is first received. /// - private Func s_onMessageReceived = null!; + private Func _onMessageReceived = null!; /// /// Invoked after security token validation if an authorization code is present /// in the protocol message. /// - private Func s_onAuthorizationCodeReceived = null!; + private Func _onAuthorizationCodeReceived = null!; /// /// Invoked after "authorization code" is redeemed for tokens at the token endpoint. /// - private Func s_onTokenResponseReceived = null!; + private Func _onTokenResponseReceived = null!; /// /// Invoked when an IdToken has been validated and produced an AuthenticationTicket. /// - private Func s_onTokenValidated = null!; + private Func _onTokenValidated = null!; /// /// Invoked when user information is retrieved from the UserInfoEndpoint. /// - private Func s_onUserInformationReceived = null!; + private Func _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!; + private Func _onAuthenticationFailed = null!; /// /// Invoked when a request is received on the RemoteSignOutPath. /// - private Func s_onRemoteSignOut = null!; + private Func _onRemoteSignOut = null!; /// /// Invoked before redirecting to the identity provider to sign out. /// - private Func s_onRedirectToIdentityProviderForSignOut = null!; + private Func _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!; + private Func _onSignedOutCallbackRedirect = null!; /// /// Subscribes to all the OpenIdConnect events, to help debugging, while @@ -93,34 +93,34 @@ public void Subscribe(OpenIdConnectEvents events) { events ??= new OpenIdConnectEvents(); - s_onRedirectToIdentityProvider = events.OnRedirectToIdentityProvider; + _onRedirectToIdentityProvider = events.OnRedirectToIdentityProvider; events.OnRedirectToIdentityProvider = OnRedirectToIdentityProviderAsync; - s_onMessageReceived = events.OnMessageReceived; + _onMessageReceived = events.OnMessageReceived; events.OnMessageReceived = OnMessageReceivedAsync; - s_onAuthorizationCodeReceived = events.OnAuthorizationCodeReceived; + _onAuthorizationCodeReceived = events.OnAuthorizationCodeReceived; events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync; - s_onTokenResponseReceived = events.OnTokenResponseReceived; + _onTokenResponseReceived = events.OnTokenResponseReceived; events.OnTokenResponseReceived = OnTokenResponseReceivedAsync; - s_onTokenValidated = events.OnTokenValidated; + _onTokenValidated = events.OnTokenValidated; events.OnTokenValidated = OnTokenValidatedAsync; - s_onUserInformationReceived = events.OnUserInformationReceived; + _onUserInformationReceived = events.OnUserInformationReceived; events.OnUserInformationReceived = OnUserInformationReceivedAsync; - s_onAuthenticationFailed = events.OnAuthenticationFailed; + _onAuthenticationFailed = events.OnAuthenticationFailed; events.OnAuthenticationFailed = OnAuthenticationFailedAsync; - s_onRemoteSignOut = events.OnRemoteSignOut; + _onRemoteSignOut = events.OnRemoteSignOut; events.OnRemoteSignOut = OnRemoteSignOutAsync; - s_onRedirectToIdentityProviderForSignOut = events.OnRedirectToIdentityProviderForSignOut; + _onRedirectToIdentityProviderForSignOut = events.OnRedirectToIdentityProviderForSignOut; events.OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOutAsync; - s_onSignedOutCallbackRedirect = events.OnSignedOutCallbackRedirect; + _onSignedOutCallbackRedirect = events.OnSignedOutCallbackRedirect; events.OnSignedOutCallbackRedirect = OnSignedOutCallbackRedirectAsync; } @@ -128,7 +128,7 @@ private async Task OnRedirectToIdentityProviderAsync(RedirectContext context) { _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodBegin, nameof(OnRedirectToIdentityProviderAsync))); - await s_onRedirectToIdentityProvider(context).ConfigureAwait(false); + await _onRedirectToIdentityProvider(context).ConfigureAwait(false); _logger.LogDebug(" Sending OpenIdConnect message:"); DisplayProtocolMessage(context.ProtocolMessage); @@ -152,63 +152,63 @@ 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); + await _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); + await _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); + await _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); + await _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); + await _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); + await _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); + await _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); + await _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); + await _onSignedOutCallbackRedirect(context).ConfigureAwait(false); _logger.LogDebug(string.Format(CultureInfo.InvariantCulture, LogMessages.MethodEnd, nameof(OnSignedOutCallbackRedirectAsync))); } } diff --git a/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs b/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs index 45857d6e0..61af440fe 100644 --- a/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs +++ b/src/Microsoft.Identity.Web/Resource/RequiredScopeFilter.cs @@ -50,7 +50,7 @@ public void OnAuthorization(AuthorizationFilterContext context) private void ValidateEffectiveScopes(AuthorizationFilterContext context) { - if (_effectiveAcceptedScopes == null || !_effectiveAcceptedScopes.Any()) + if (_effectiveAcceptedScopes == null || _effectiveAcceptedScopes.Length == 0) { throw new InvalidOperationException(IDWebErrorMessage.MissingRequiredScopesForAuthorizationFilter); } diff --git a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs index 7b5a75460..874145b75 100644 --- a/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs +++ b/src/Microsoft.Identity.Web/WebApiExtensions/MicrosoftIdentityWebApiAuthenticationBuilderExtensions.cs @@ -222,10 +222,11 @@ private static void AddMicrosoftIdentityWebApiImplementation( 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)) + if (!microsoftIdentityOptions.AllowWebApiToBeAuthorizedByACL + && !context!.Principal!.Claims.Any(x => x.Type == ClaimConstants.Scope + || x.Type == ClaimConstants.Scp + || x.Type == ClaimConstants.Roles + || x.Type == ClaimConstants.Role)) { throw new UnauthorizedAccessException(IDWebErrorMessage.NeitherScopeOrRolesClaimFoundInToken); }