From 22db7fc61d6d13de84324be5bba636e8926d100b Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Mon, 16 Jun 2025 15:55:53 -0400 Subject: [PATCH 1/6] split middleware, so ClaimsEnrichment middleware can be implemented between AuthN and AuthZ --- .../FunctionsAuthenticationMiddleware.cs | 119 ++++++++++++ .../FunctionsAuthorizationMiddleware.cs | 177 +++++++++--------- ...AuthorizationWorkerAppBuilderExtensions.cs | 51 +++-- 3 files changed, 244 insertions(+), 103 deletions(-) create mode 100644 src/isolated/FunctionsAuthenticationMiddleware.cs diff --git a/src/isolated/FunctionsAuthenticationMiddleware.cs b/src/isolated/FunctionsAuthenticationMiddleware.cs new file mode 100644 index 0000000..49b3d81 --- /dev/null +++ b/src/isolated/FunctionsAuthenticationMiddleware.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) DarkLoop. All rights reserved. +// + +using DarkLoop.Azure.Functions.Authorization.Internal; +using DarkLoop.Azure.Functions.Authorization.Properties; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Http.Features.Authentication; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Middleware; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Extensions; + +namespace DarkLoop.Azure.Functions.Authorization; + +internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddleware +{ + #region Private Fields + + private readonly IFunctionsAuthorizationProvider _authorizationProvider; + private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; + private readonly IOptionsMonitor _configOptions; + private readonly ILogger _logger; + private readonly IPolicyEvaluator _policyEvaluator; + private readonly IAuthorizationPolicyProvider _policyProvider; + + #endregion Private Fields + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + /// Functions authorization provider to retrieve filters. + /// Authorization handler. + /// ASP.NET Core's authorization policy provider. + /// ASP.NET Core's policy evaluator. + /// Functions authorization configure options. + /// A logger object for diagnostics. + public FunctionsAuthenticationMiddleware( + IFunctionsAuthorizationProvider authorizationProvider, + IAuthorizationPolicyProvider policyProvider, + IPolicyEvaluator policyEvaluator, + IOptionsMonitor configOptions, + ILogger logger) + { + Check.NotNull(authorizationProvider, nameof(authorizationProvider)); + Check.NotNull(policyProvider, nameof(policyProvider)); + Check.NotNull(policyEvaluator, nameof(policyEvaluator)); + Check.NotNull(configOptions, nameof(configOptions)); + Check.NotNull(logger, nameof(logger)); + + _authorizationProvider = authorizationProvider; + _policyProvider = policyProvider; + _policyEvaluator = policyEvaluator; + _configOptions = configOptions; + _logger = logger; + } + + #endregion Public Constructors + + #region Public Methods + + /// + public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) + { + var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); + + if (this._configOptions.CurrentValue.AuthorizationDisabled) + { + var displayUrl = httpContext.Request.GetDisplayUrl(); + + _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); + + await next(context); + return; + } + + var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); + + if (filter.Policy is null) + { + await next(context); + return; + } + + var authenticateResult = await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); + + var authenticateFeature = httpContext.Features.SetAuthenticationFeatures(authenticateResult); + + // We also make the features available in the FunctionContext + context.Features.Set(authenticateFeature); + context.Features.Set(authenticateFeature); + + if (filter.AllowAnonymous) + { + await next(context); + return; + } + + if (authenticateResult is not null && !authenticateResult.Succeeded) + { + _logger.LogDebug( + IsolatedMessages.AuthenticationFailed, + filter.Policy.AuthenticationSchemes.Count > 0 + ? " for " + string.Join(", ", filter.Policy.AuthenticationSchemes) + : string.Empty); + } + + await next(context); + } + + #endregion Public Methods +} \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationMiddleware.cs b/src/isolated/FunctionsAuthorizationMiddleware.cs index 7490e79..741aa56 100644 --- a/src/isolated/FunctionsAuthorizationMiddleware.cs +++ b/src/isolated/FunctionsAuthorizationMiddleware.cs @@ -2,115 +2,108 @@ // Copyright (c) DarkLoop. All rights reserved. // -using System; -using System.Threading.Tasks; using DarkLoop.Azure.Functions.Authorization.Internal; using DarkLoop.Azure.Functions.Authorization.Properties; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Middleware; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; namespace DarkLoop.Azure.Functions.Authorization { - /// - internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddleware - { - private readonly IFunctionsAuthorizationProvider _authorizationProvider; - private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; - private readonly IAuthorizationPolicyProvider _policyProvider; - private readonly IPolicyEvaluator _policyEvaluator; - private readonly IOptionsMonitor _configOptions; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// Functions authorization provider to retrieve filters. - /// Authorization handler. - /// ASP.NET Core's authorization policy provider. - /// ASP.NET Core's policy evaluator. - /// Functions authorization configure options. - /// A logger object for diagnostics. - public FunctionsAuthorizationMiddleware( + /// + internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddleware + { + #region Private Fields + + private readonly IFunctionsAuthorizationProvider _authorizationProvider; + private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; + private readonly IOptionsMonitor _configOptions; + private readonly ILogger _logger; + private readonly IPolicyEvaluator _policyEvaluator; + private readonly IAuthorizationPolicyProvider _policyProvider; + + #endregion Private Fields + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + /// Functions authorization provider to retrieve filters. + /// Authorization handler. + /// ASP.NET Core's authorization policy provider. + /// ASP.NET Core's policy evaluator. + /// Functions authorization configure options. + /// A logger object for diagnostics. + public FunctionsAuthorizationMiddleware( IFunctionsAuthorizationProvider authorizationProvider, IFunctionsAuthorizationResultHandler authorizationHandler, IAuthorizationPolicyProvider policyProvider, IPolicyEvaluator policyEvaluator, IOptionsMonitor configOptions, ILogger logger) - { - Check.NotNull(authorizationProvider, nameof(authorizationProvider)); - Check.NotNull(authorizationHandler, nameof(authorizationHandler)); - Check.NotNull(policyProvider, nameof(policyProvider)); - Check.NotNull(policyEvaluator, nameof(policyEvaluator)); - Check.NotNull(configOptions, nameof(configOptions)); - Check.NotNull(logger, nameof(logger)); - - _authorizationProvider = authorizationProvider; - _authorizationResultHandler = authorizationHandler; - _policyProvider = policyProvider; - _policyEvaluator = policyEvaluator; - _configOptions = configOptions; - _logger = logger; - } - - /// - public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) - { - var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); - - if (this._configOptions.CurrentValue.AuthorizationDisabled) - { - var displayUrl = httpContext.Request.GetDisplayUrl(); - - _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); - - await next(context); - return; - } - - var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); - - if (filter.Policy is null) - { - await next(context); - return; - } - - var authenticateResult = await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); - - var authenticateFeature = httpContext.Features.SetAuthenticationFeatures(authenticateResult); - - // We also make the features available in the FunctionContext - context.Features.Set(authenticateFeature); - context.Features.Set(authenticateFeature); - - if (filter.AllowAnonymous) - { - await next(context); - return; - } - - if (authenticateResult is not null && !authenticateResult.Succeeded) - { - _logger.LogDebug( - IsolatedMessages.AuthenticationFailed, - filter.Policy.AuthenticationSchemes.Count > 0 - ? " for " + string.Join(", ", filter.Policy.AuthenticationSchemes) - : string.Empty); - } - - var authorizeResult = await _policyEvaluator.AuthorizeAsync(filter.Policy, authenticateResult!, httpContext, httpContext); - var authContext = new FunctionAuthorizationContext( - context.FunctionDefinition.Name, context, filter.Policy, authorizeResult); - - await _authorizationResultHandler.HandleResultAsync(authContext, httpContext, async (ctx) => await next(ctx)); - } + { + Check.NotNull(authorizationProvider, nameof(authorizationProvider)); + Check.NotNull(authorizationHandler, nameof(authorizationHandler)); + Check.NotNull(policyProvider, nameof(policyProvider)); + Check.NotNull(policyEvaluator, nameof(policyEvaluator)); + Check.NotNull(configOptions, nameof(configOptions)); + Check.NotNull(logger, nameof(logger)); + + _authorizationProvider = authorizationProvider; + _authorizationResultHandler = authorizationHandler; + _policyProvider = policyProvider; + _policyEvaluator = policyEvaluator; + _configOptions = configOptions; + _logger = logger; } -} + + #endregion Public Constructors + + #region Public Methods + + /// + public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) + { + var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); + + if (this._configOptions.CurrentValue.AuthorizationDisabled) + { + var displayUrl = httpContext.Request.GetDisplayUrl(); + + _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); + + await next(context); + return; + } + + var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); + + if (filter.Policy is null) + { + await next(context); + return; + } + + var authenticateFeature = context.Features.Get(); + + var authenticateResult = authenticateFeature?.AuthenticateResult ?? + await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); + + var authorizeResult = await _policyEvaluator.AuthorizeAsync(filter.Policy, authenticateResult!, httpContext, httpContext); + var authContext = new FunctionAuthorizationContext( + context.FunctionDefinition.Name, context, filter.Policy, authorizeResult); + + await _authorizationResultHandler.HandleResultAsync(authContext, httpContext, async (ctx) => await next(ctx)); + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs index ec20163..afd616f 100644 --- a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs +++ b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs @@ -8,19 +8,48 @@ namespace Microsoft.Azure.Functions.Worker { + /// + /// Extension methods for adding the to the application pipeline. + /// + public static class FunctionsAuthorizationWorkerAppBuilderExtensions + { + #region Public Methods + /// - /// Extension methods for adding the to the application pipeline. + /// Adds both DarkLoop's Functions authentication and authorization middleware to the application pipeline. /// - public static class FunctionsAuthorizationWorkerAppBuilderExtensions + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuth(this IFunctionsWorkerApplicationBuilder builder) { - /// - /// Adds DarkLoop's Functions authorization middleware to the application pipeline. - /// - /// The current builder. - public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this IFunctionsWorkerApplicationBuilder builder) - { - return builder.UseWhen(context => + builder.UseWhen(context => + context.Features.Get() is not null); + + builder.UseWhen(context => context.Features.Get() is not null); - } + + return builder; + } + + /// + /// Adds DarkLoop's Functions authentication middleware to the application pipeline. + /// + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthentication(this IFunctionsWorkerApplicationBuilder builder) + { + return builder.UseWhen(context => + context.Features.Get() is not null); } -} + + /// + /// Adds DarkLoop's Functions authorization middleware to the application pipeline. + /// + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this IFunctionsWorkerApplicationBuilder builder) + { + return builder.UseWhen(context => + context.Features.Get() is not null); + } + + #endregion Public Methods + } +} \ No newline at end of file From d10a7e1bb73bed5a62c54fc378e1f1a85c9a587a Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 4 Jul 2025 10:15:43 -0400 Subject: [PATCH 2/6] removing regions --- .../FunctionsAuthenticationMiddleware.cs | 18 ++++++------------ .../FunctionsAuthorizationMiddleware.cs | 18 ++++++------------ ...sAuthorizationWorkerAppBuilderExtensions.cs | 6 ++---- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/isolated/FunctionsAuthenticationMiddleware.cs b/src/isolated/FunctionsAuthenticationMiddleware.cs index 49b3d81..91c0285 100644 --- a/src/isolated/FunctionsAuthenticationMiddleware.cs +++ b/src/isolated/FunctionsAuthenticationMiddleware.cs @@ -20,8 +20,7 @@ namespace DarkLoop.Azure.Functions.Authorization; internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddleware { - #region Private Fields - + private readonly IFunctionsAuthorizationProvider _authorizationProvider; private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; private readonly IOptionsMonitor _configOptions; @@ -29,10 +28,8 @@ internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddle private readonly IPolicyEvaluator _policyEvaluator; private readonly IAuthorizationPolicyProvider _policyProvider; - #endregion Private Fields - - #region Public Constructors - + + /// /// Initializes a new instance of the class. /// @@ -62,10 +59,8 @@ public FunctionsAuthenticationMiddleware( _logger = logger; } - #endregion Public Constructors - - #region Public Methods - + + /// public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { @@ -115,5 +110,4 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next await next(context); } - #endregion Public Methods -} \ No newline at end of file + } \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationMiddleware.cs b/src/isolated/FunctionsAuthorizationMiddleware.cs index 741aa56..5a25caf 100644 --- a/src/isolated/FunctionsAuthorizationMiddleware.cs +++ b/src/isolated/FunctionsAuthorizationMiddleware.cs @@ -20,8 +20,7 @@ namespace DarkLoop.Azure.Functions.Authorization /// internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddleware { - #region Private Fields - + private readonly IFunctionsAuthorizationProvider _authorizationProvider; private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; private readonly IOptionsMonitor _configOptions; @@ -29,10 +28,8 @@ internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddlew private readonly IPolicyEvaluator _policyEvaluator; private readonly IAuthorizationPolicyProvider _policyProvider; - #endregion Private Fields - - #region Public Constructors - + + /// /// Initializes a new instance of the class. /// @@ -65,10 +62,8 @@ public FunctionsAuthorizationMiddleware( _logger = logger; } - #endregion Public Constructors - - #region Public Methods - + + /// public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { @@ -104,6 +99,5 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next await _authorizationResultHandler.HandleResultAsync(authContext, httpContext, async (ctx) => await next(ctx)); } - #endregion Public Methods - } + } } \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs index afd616f..4f77078 100644 --- a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs +++ b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs @@ -13,8 +13,7 @@ namespace Microsoft.Azure.Functions.Worker /// public static class FunctionsAuthorizationWorkerAppBuilderExtensions { - #region Public Methods - + /// /// Adds both DarkLoop's Functions authentication and authorization middleware to the application pipeline. /// @@ -50,6 +49,5 @@ public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this context.Features.Get() is not null); } - #endregion Public Methods - } + } } \ No newline at end of file From 32d02a9c7b8a4d1df1c08d84b5b81eb8e89ac9f8 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 4 Jul 2025 10:18:23 -0400 Subject: [PATCH 3/6] change spacing for indentations --- .../FunctionsAuthenticationMiddleware.cs | 158 +++++++++--------- .../FunctionsAuthorizationMiddleware.cs | 142 ++++++++-------- ...AuthorizationWorkerAppBuilderExtensions.cs | 68 ++++---- 3 files changed, 184 insertions(+), 184 deletions(-) diff --git a/src/isolated/FunctionsAuthenticationMiddleware.cs b/src/isolated/FunctionsAuthenticationMiddleware.cs index 91c0285..6e6c905 100644 --- a/src/isolated/FunctionsAuthenticationMiddleware.cs +++ b/src/isolated/FunctionsAuthenticationMiddleware.cs @@ -20,94 +20,94 @@ namespace DarkLoop.Azure.Functions.Authorization; internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddleware { - - private readonly IFunctionsAuthorizationProvider _authorizationProvider; - private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; - private readonly IOptionsMonitor _configOptions; - private readonly ILogger _logger; - private readonly IPolicyEvaluator _policyEvaluator; - private readonly IAuthorizationPolicyProvider _policyProvider; - - - - /// - /// Initializes a new instance of the class. - /// - /// Functions authorization provider to retrieve filters. - /// Authorization handler. - /// ASP.NET Core's authorization policy provider. - /// ASP.NET Core's policy evaluator. - /// Functions authorization configure options. - /// A logger object for diagnostics. - public FunctionsAuthenticationMiddleware( - IFunctionsAuthorizationProvider authorizationProvider, - IAuthorizationPolicyProvider policyProvider, - IPolicyEvaluator policyEvaluator, - IOptionsMonitor configOptions, - ILogger logger) - { - Check.NotNull(authorizationProvider, nameof(authorizationProvider)); - Check.NotNull(policyProvider, nameof(policyProvider)); - Check.NotNull(policyEvaluator, nameof(policyEvaluator)); - Check.NotNull(configOptions, nameof(configOptions)); - Check.NotNull(logger, nameof(logger)); - - _authorizationProvider = authorizationProvider; - _policyProvider = policyProvider; - _policyEvaluator = policyEvaluator; - _configOptions = configOptions; - _logger = logger; - } - - - - /// - public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) - { - var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); - - if (this._configOptions.CurrentValue.AuthorizationDisabled) - { - var displayUrl = httpContext.Request.GetDisplayUrl(); - - _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); - await next(context); - return; + private readonly IFunctionsAuthorizationProvider _authorizationProvider; + private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; + private readonly IOptionsMonitor _configOptions; + private readonly ILogger _logger; + private readonly IPolicyEvaluator _policyEvaluator; + private readonly IAuthorizationPolicyProvider _policyProvider; + + + + /// + /// Initializes a new instance of the class. + /// + /// Functions authorization provider to retrieve filters. + /// Authorization handler. + /// ASP.NET Core's authorization policy provider. + /// ASP.NET Core's policy evaluator. + /// Functions authorization configure options. + /// A logger object for diagnostics. + public FunctionsAuthenticationMiddleware( + IFunctionsAuthorizationProvider authorizationProvider, + IAuthorizationPolicyProvider policyProvider, + IPolicyEvaluator policyEvaluator, + IOptionsMonitor configOptions, + ILogger logger) + { + Check.NotNull(authorizationProvider, nameof(authorizationProvider)); + Check.NotNull(policyProvider, nameof(policyProvider)); + Check.NotNull(policyEvaluator, nameof(policyEvaluator)); + Check.NotNull(configOptions, nameof(configOptions)); + Check.NotNull(logger, nameof(logger)); + + _authorizationProvider = authorizationProvider; + _policyProvider = policyProvider; + _policyEvaluator = policyEvaluator; + _configOptions = configOptions; + _logger = logger; } - var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); - if (filter.Policy is null) + + /// + public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { - await next(context); - return; - } + var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); - var authenticateResult = await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); + if (this._configOptions.CurrentValue.AuthorizationDisabled) + { + var displayUrl = httpContext.Request.GetDisplayUrl(); - var authenticateFeature = httpContext.Features.SetAuthenticationFeatures(authenticateResult); + _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); - // We also make the features available in the FunctionContext - context.Features.Set(authenticateFeature); - context.Features.Set(authenticateFeature); + await next(context); + return; + } - if (filter.AllowAnonymous) - { - await next(context); - return; - } + var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); - if (authenticateResult is not null && !authenticateResult.Succeeded) - { - _logger.LogDebug( - IsolatedMessages.AuthenticationFailed, - filter.Policy.AuthenticationSchemes.Count > 0 - ? " for " + string.Join(", ", filter.Policy.AuthenticationSchemes) - : string.Empty); - } + if (filter.Policy is null) + { + await next(context); + return; + } + + var authenticateResult = await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); + + var authenticateFeature = httpContext.Features.SetAuthenticationFeatures(authenticateResult); + + // We also make the features available in the FunctionContext + context.Features.Set(authenticateFeature); + context.Features.Set(authenticateFeature); - await next(context); - } + if (filter.AllowAnonymous) + { + await next(context); + return; + } + + if (authenticateResult is not null && !authenticateResult.Succeeded) + { + _logger.LogDebug( + IsolatedMessages.AuthenticationFailed, + filter.Policy.AuthenticationSchemes.Count > 0 + ? " for " + string.Join(", ", filter.Policy.AuthenticationSchemes) + : string.Empty); + } + + await next(context); + } - } \ No newline at end of file +} \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationMiddleware.cs b/src/isolated/FunctionsAuthorizationMiddleware.cs index 5a25caf..97798ce 100644 --- a/src/isolated/FunctionsAuthorizationMiddleware.cs +++ b/src/isolated/FunctionsAuthorizationMiddleware.cs @@ -17,87 +17,87 @@ namespace DarkLoop.Azure.Functions.Authorization { - /// - internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddleware - { - - private readonly IFunctionsAuthorizationProvider _authorizationProvider; - private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; - private readonly IOptionsMonitor _configOptions; - private readonly ILogger _logger; - private readonly IPolicyEvaluator _policyEvaluator; - private readonly IAuthorizationPolicyProvider _policyProvider; - - - - /// - /// Initializes a new instance of the class. - /// - /// Functions authorization provider to retrieve filters. - /// Authorization handler. - /// ASP.NET Core's authorization policy provider. - /// ASP.NET Core's policy evaluator. - /// Functions authorization configure options. - /// A logger object for diagnostics. - public FunctionsAuthorizationMiddleware( - IFunctionsAuthorizationProvider authorizationProvider, - IFunctionsAuthorizationResultHandler authorizationHandler, - IAuthorizationPolicyProvider policyProvider, - IPolicyEvaluator policyEvaluator, - IOptionsMonitor configOptions, - ILogger logger) + /// + internal sealed class FunctionsAuthorizationMiddleware : IFunctionsWorkerMiddleware { - Check.NotNull(authorizationProvider, nameof(authorizationProvider)); - Check.NotNull(authorizationHandler, nameof(authorizationHandler)); - Check.NotNull(policyProvider, nameof(policyProvider)); - Check.NotNull(policyEvaluator, nameof(policyEvaluator)); - Check.NotNull(configOptions, nameof(configOptions)); - Check.NotNull(logger, nameof(logger)); - - _authorizationProvider = authorizationProvider; - _authorizationResultHandler = authorizationHandler; - _policyProvider = policyProvider; - _policyEvaluator = policyEvaluator; - _configOptions = configOptions; - _logger = logger; - } - - - /// - public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) - { - var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); + private readonly IFunctionsAuthorizationProvider _authorizationProvider; + private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; + private readonly IOptionsMonitor _configOptions; + private readonly ILogger _logger; + private readonly IPolicyEvaluator _policyEvaluator; + private readonly IAuthorizationPolicyProvider _policyProvider; - if (this._configOptions.CurrentValue.AuthorizationDisabled) - { - var displayUrl = httpContext.Request.GetDisplayUrl(); - _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); - await next(context); - return; - } + /// + /// Initializes a new instance of the class. + /// + /// Functions authorization provider to retrieve filters. + /// Authorization handler. + /// ASP.NET Core's authorization policy provider. + /// ASP.NET Core's policy evaluator. + /// Functions authorization configure options. + /// A logger object for diagnostics. + public FunctionsAuthorizationMiddleware( + IFunctionsAuthorizationProvider authorizationProvider, + IFunctionsAuthorizationResultHandler authorizationHandler, + IAuthorizationPolicyProvider policyProvider, + IPolicyEvaluator policyEvaluator, + IOptionsMonitor configOptions, + ILogger logger) + { + Check.NotNull(authorizationProvider, nameof(authorizationProvider)); + Check.NotNull(authorizationHandler, nameof(authorizationHandler)); + Check.NotNull(policyProvider, nameof(policyProvider)); + Check.NotNull(policyEvaluator, nameof(policyEvaluator)); + Check.NotNull(configOptions, nameof(configOptions)); + Check.NotNull(logger, nameof(logger)); - var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); + _authorizationProvider = authorizationProvider; + _authorizationResultHandler = authorizationHandler; + _policyProvider = policyProvider; + _policyEvaluator = policyEvaluator; + _configOptions = configOptions; + _logger = logger; + } - if (filter.Policy is null) - { - await next(context); - return; - } - var authenticateFeature = context.Features.Get(); - var authenticateResult = authenticateFeature?.AuthenticateResult ?? - await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); + /// + public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) + { + var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); - var authorizeResult = await _policyEvaluator.AuthorizeAsync(filter.Policy, authenticateResult!, httpContext, httpContext); - var authContext = new FunctionAuthorizationContext( - context.FunctionDefinition.Name, context, filter.Policy, authorizeResult); + if (this._configOptions.CurrentValue.AuthorizationDisabled) + { + var displayUrl = httpContext.Request.GetDisplayUrl(); - await _authorizationResultHandler.HandleResultAsync(authContext, httpContext, async (ctx) => await next(ctx)); - } + _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); + + await next(context); + return; + } + + var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); + + if (filter.Policy is null) + { + await next(context); + return; + } - } + var authenticateFeature = context.Features.Get(); + + var authenticateResult = authenticateFeature?.AuthenticateResult ?? + await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); + + var authorizeResult = await _policyEvaluator.AuthorizeAsync(filter.Policy, authenticateResult!, httpContext, httpContext); + var authContext = new FunctionAuthorizationContext( + context.FunctionDefinition.Name, context, filter.Policy, authorizeResult); + + await _authorizationResultHandler.HandleResultAsync(authContext, httpContext, async (ctx) => await next(ctx)); + } + + } } \ No newline at end of file diff --git a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs index 4f77078..1b56efd 100644 --- a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs +++ b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs @@ -8,46 +8,46 @@ namespace Microsoft.Azure.Functions.Worker { - /// - /// Extension methods for adding the to the application pipeline. - /// - public static class FunctionsAuthorizationWorkerAppBuilderExtensions - { - /// - /// Adds both DarkLoop's Functions authentication and authorization middleware to the application pipeline. + /// Extension methods for adding the to the application pipeline. /// - /// The current builder. - public static IFunctionsWorkerApplicationBuilder UseFunctionsAuth(this IFunctionsWorkerApplicationBuilder builder) + public static class FunctionsAuthorizationWorkerAppBuilderExtensions { - builder.UseWhen(context => - context.Features.Get() is not null); - builder.UseWhen(context => - context.Features.Get() is not null); + /// + /// Adds both DarkLoop's Functions authentication and authorization middleware to the application pipeline. + /// + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuth(this IFunctionsWorkerApplicationBuilder builder) + { + builder.UseWhen(context => + context.Features.Get() is not null); - return builder; - } + builder.UseWhen(context => + context.Features.Get() is not null); - /// - /// Adds DarkLoop's Functions authentication middleware to the application pipeline. - /// - /// The current builder. - public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthentication(this IFunctionsWorkerApplicationBuilder builder) - { - return builder.UseWhen(context => - context.Features.Get() is not null); - } + return builder; + } - /// - /// Adds DarkLoop's Functions authorization middleware to the application pipeline. - /// - /// The current builder. - public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this IFunctionsWorkerApplicationBuilder builder) - { - return builder.UseWhen(context => - context.Features.Get() is not null); - } + /// + /// Adds DarkLoop's Functions authentication middleware to the application pipeline. + /// + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthentication(this IFunctionsWorkerApplicationBuilder builder) + { + return builder.UseWhen(context => + context.Features.Get() is not null); + } - } + /// + /// Adds DarkLoop's Functions authorization middleware to the application pipeline. + /// + /// The current builder. + public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this IFunctionsWorkerApplicationBuilder builder) + { + return builder.UseWhen(context => + context.Features.Get() is not null); + } + + } } \ No newline at end of file From 8a8f7fdf11c1fff2beb72d80f33b1eda572ffbe1 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 4 Jul 2025 14:15:56 -0400 Subject: [PATCH 4/6] follow aspnetcore AuthenticationMiddleware more closely. --- .../FunctionsAuthenticationMiddleware.cs | 96 ++++++------------- 1 file changed, 31 insertions(+), 65 deletions(-) diff --git a/src/isolated/FunctionsAuthenticationMiddleware.cs b/src/isolated/FunctionsAuthenticationMiddleware.cs index 6e6c905..d1bf1ae 100644 --- a/src/isolated/FunctionsAuthenticationMiddleware.cs +++ b/src/isolated/FunctionsAuthenticationMiddleware.cs @@ -5,106 +5,72 @@ using DarkLoop.Azure.Functions.Authorization.Internal; using DarkLoop.Azure.Functions.Authorization.Properties; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Middleware; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using System; +using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http.Extensions; namespace DarkLoop.Azure.Functions.Authorization; internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddleware { - - private readonly IFunctionsAuthorizationProvider _authorizationProvider; - private readonly IFunctionsAuthorizationResultHandler _authorizationResultHandler; - private readonly IOptionsMonitor _configOptions; private readonly ILogger _logger; - private readonly IPolicyEvaluator _policyEvaluator; - private readonly IAuthorizationPolicyProvider _policyProvider; - - /// /// Initializes a new instance of the class. /// - /// Functions authorization provider to retrieve filters. - /// Authorization handler. - /// ASP.NET Core's authorization policy provider. - /// ASP.NET Core's policy evaluator. - /// Functions authorization configure options. /// A logger object for diagnostics. public FunctionsAuthenticationMiddleware( - IFunctionsAuthorizationProvider authorizationProvider, - IAuthorizationPolicyProvider policyProvider, - IPolicyEvaluator policyEvaluator, - IOptionsMonitor configOptions, ILogger logger) { - Check.NotNull(authorizationProvider, nameof(authorizationProvider)); - Check.NotNull(policyProvider, nameof(policyProvider)); - Check.NotNull(policyEvaluator, nameof(policyEvaluator)); - Check.NotNull(configOptions, nameof(configOptions)); Check.NotNull(logger, nameof(logger)); - _authorizationProvider = authorizationProvider; - _policyProvider = policyProvider; - _policyEvaluator = policyEvaluator; - _configOptions = configOptions; _logger = logger; } - - /// public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); - if (this._configOptions.CurrentValue.AuthorizationDisabled) - { - var displayUrl = httpContext.Request.GetDisplayUrl(); - - _logger.LogWarning(IsolatedMessages.FunctionAuthIsDisabled, displayUrl); - - await next(context); - return; - } - - var filter = await _authorizationProvider.GetAuthorizationAsync(context.FunctionDefinition.Name, _policyProvider); - - if (filter.Policy is null) - { - await next(context); - return; - } - - var authenticateResult = await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext); - - var authenticateFeature = httpContext.Features.SetAuthenticationFeatures(authenticateResult); - - // We also make the features available in the FunctionContext - context.Features.Set(authenticateFeature); - context.Features.Set(authenticateFeature); - - if (filter.AllowAnonymous) + var schemes = context.InstanceServices.GetRequiredService(); + var handlers = context.InstanceServices.GetRequiredService(); + foreach (var scheme in await schemes.GetRequestHandlerSchemesAsync()) { - await next(context); - return; + var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler; + if (handler != null && await handler.HandleRequestAsync()) + { + return; + } } - if (authenticateResult is not null && !authenticateResult.Succeeded) + var defaultAuthenticate = await schemes.GetDefaultAuthenticateSchemeAsync(); + if (defaultAuthenticate != null) { - _logger.LogDebug( - IsolatedMessages.AuthenticationFailed, - filter.Policy.AuthenticationSchemes.Count > 0 - ? " for " + string.Join(", ", filter.Policy.AuthenticationSchemes) + var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); + if (result?.Principal != null) + { + httpContext.User = result.Principal; + } + if (result?.Succeeded ?? false) + { + var authFeatures = httpContext.Features.SetAuthenticationFeatures(result); + context.Features.Set(authFeatures); + context.Features.Set(authFeatures); + } + else + { + var allSchemes = (await schemes.GetAllSchemesAsync()).ToList(); + _logger.LogDebug( + IsolatedMessages.AuthenticationFailed, + allSchemes.Count > 0 + ? " for " + string.Join(", ", allSchemes) : string.Empty); + } } await next(context); From 82c6edcd3008dcc1fbab8dbf84b796cf298685bb Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 4 Jul 2025 14:20:03 -0400 Subject: [PATCH 5/6] use DI --- .../FunctionsAuthenticationMiddleware.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/isolated/FunctionsAuthenticationMiddleware.cs b/src/isolated/FunctionsAuthenticationMiddleware.cs index d1bf1ae..b472276 100644 --- a/src/isolated/FunctionsAuthenticationMiddleware.cs +++ b/src/isolated/FunctionsAuthenticationMiddleware.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Middleware; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; using System.Linq; @@ -19,16 +18,26 @@ namespace DarkLoop.Azure.Functions.Authorization; internal sealed class FunctionsAuthenticationMiddleware : IFunctionsWorkerMiddleware { private readonly ILogger _logger; + private readonly IAuthenticationHandlerProvider _authenticationHandlerProvider; + private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; /// /// Initializes a new instance of the class. /// + /// authentication handler provider. + /// authentication scheme provider. /// A logger object for diagnostics. public FunctionsAuthenticationMiddleware( + IAuthenticationHandlerProvider authenticationHandlerProvider, + IAuthenticationSchemeProvider authenticationSchemeProvider, ILogger logger) { + Check.NotNull(authenticationHandlerProvider, nameof(authenticationHandlerProvider)); + Check.NotNull(authenticationSchemeProvider, nameof(authenticationSchemeProvider)); Check.NotNull(logger, nameof(logger)); + _authenticationHandlerProvider = authenticationHandlerProvider; + _authenticationSchemeProvider = authenticationSchemeProvider; _logger = logger; } @@ -37,18 +46,16 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next { var httpContext = context.GetHttpContext() ?? throw new NotSupportedException(IsolatedMessages.NotSupportedIsolatedMode); - var schemes = context.InstanceServices.GetRequiredService(); - var handlers = context.InstanceServices.GetRequiredService(); - foreach (var scheme in await schemes.GetRequestHandlerSchemesAsync()) + foreach (var scheme in await _authenticationSchemeProvider.GetRequestHandlerSchemesAsync()) { - var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler; + var handler = await _authenticationHandlerProvider.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler; if (handler != null && await handler.HandleRequestAsync()) { return; } } - var defaultAuthenticate = await schemes.GetDefaultAuthenticateSchemeAsync(); + var defaultAuthenticate = await _authenticationSchemeProvider.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); @@ -64,7 +71,7 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next } else { - var allSchemes = (await schemes.GetAllSchemesAsync()).ToList(); + var allSchemes = (await _authenticationSchemeProvider.GetAllSchemesAsync()).ToList(); _logger.LogDebug( IsolatedMessages.AuthenticationFailed, allSchemes.Count > 0 From f4dfcb95961e8f4d98fe671a705f7e264d05b08f Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Fri, 4 Jul 2025 14:30:07 -0400 Subject: [PATCH 6/6] add trigger check to middleware, so non-HttpTrigger functions don't throw errors on missing HttpContext --- ...sAuthorizationWorkerAppBuilderExtensions.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs index 1b56efd..b2ae536 100644 --- a/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs +++ b/src/isolated/FunctionsAuthorizationWorkerAppBuilderExtensions.cs @@ -3,6 +3,7 @@ // using DarkLoop.Azure.Functions.Authorization; +using DarkLoop.Azure.Functions.Authorization.Extensions; using DarkLoop.Azure.Functions.Authorization.Features; using Microsoft.Extensions.Hosting; @@ -21,33 +22,38 @@ public static class FunctionsAuthorizationWorkerAppBuilderExtensions public static IFunctionsWorkerApplicationBuilder UseFunctionsAuth(this IFunctionsWorkerApplicationBuilder builder) { builder.UseWhen(context => - context.Features.Get() is not null); + context.IsHttpTrigger() && + context.Features.Get() is not null); builder.UseWhen(context => - context.Features.Get() is not null); + context.IsHttpTrigger() && + context.Features.Get() is not null); return builder; } /// /// Adds DarkLoop's Functions authentication middleware to the application pipeline. + /// if the function is an HTTP trigger and the is available. /// /// The current builder. public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthentication(this IFunctionsWorkerApplicationBuilder builder) { return builder.UseWhen(context => - context.Features.Get() is not null); + context.IsHttpTrigger() && + context.Features.Get() is not null); } /// /// Adds DarkLoop's Functions authorization middleware to the application pipeline. + /// if the function is an HTTP trigger and the is available. /// /// The current builder. public static IFunctionsWorkerApplicationBuilder UseFunctionsAuthorization(this IFunctionsWorkerApplicationBuilder builder) { return builder.UseWhen(context => - context.Features.Get() is not null); + context.IsHttpTrigger() && + context.Features.Get() is not null); } - } -} \ No newline at end of file +}