diff --git a/.build/release.props b/.build/release.props
index 6ec2fe7..d101a20 100644
--- a/.build/release.props
+++ b/.build/release.props
@@ -6,9 +6,9 @@
DarkLoopDarkLoop - All rights reservedDarkLoop's Azure Functions Authorization
- false
+ true4.0.0.0
- 4.1.0
+ 4.1.1$(Version).0https://github.com/dark-loop/functions-authorizehttps://github.com/dark-loop/functions-authorize/blob/master/LICENSE
diff --git a/ChangeLog.md b/ChangeLog.md
index dfc3db3..93c4d43 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,9 @@
# Change log
Change log stars with version 3.1.3
+## 4.1.1
+After authenticate but before authorize IAuthenticateResultFeature and IHttpAuthenticationFeature are now both set in HttpContext.Features and (for isolated Azure Functions) FunctionContext.Features.
+
## 4.1.0
- ### [Breaking] Removing support for `Bearer` scheme and adding `FunctionsBearer`
Recent security updates in the Azure Functions runtime are clashing with the use of the default, well known `Bearer` scheme.
diff --git a/src/abstractions/FunctionAuthorizationFeature.cs b/src/abstractions/FunctionAuthorizationFeature.cs
new file mode 100644
index 0000000..f5edee2
--- /dev/null
+++ b/src/abstractions/FunctionAuthorizationFeature.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) DarkLoop. All rights reserved.
+//
+
+using DarkLoop.Azure.Functions.Authorization.Internal;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using System.Security.Claims;
+
+namespace DarkLoop.Azure.Functions.Authorization
+{
+ // This was designed with maximum compatibility with ASP.NET core. It keeps
+ // two separate features in sync with each other automatically.
+ internal sealed class FunctionAuthorizationFeature : IAuthenticateResultFeature, IHttpAuthenticationFeature
+ {
+ private ClaimsPrincipal? _principal;
+ private AuthenticateResult? _authenticateResult;
+
+ ///
+ /// Construct an instance of the feature with the given AuthenticateResult
+ ///
+ ///
+ public FunctionAuthorizationFeature(AuthenticateResult result)
+ {
+ Check.NotNull(result, nameof(result));
+
+ AuthenticateResult = result;
+ }
+
+ ///
+ public AuthenticateResult? AuthenticateResult
+ {
+ get => _authenticateResult;
+ set
+ {
+ _authenticateResult = value;
+ _principal = value?.Principal;
+ }
+ }
+
+ ///
+ public ClaimsPrincipal? User
+ {
+ get => _principal;
+ set
+ {
+ _authenticateResult = null;
+ _principal = value;
+ }
+ }
+ }
+}
diff --git a/src/abstractions/Internal/FunctionsFeatureCollectionExtension.cs b/src/abstractions/Internal/FunctionsFeatureCollectionExtension.cs
new file mode 100644
index 0000000..db64504
--- /dev/null
+++ b/src/abstractions/Internal/FunctionsFeatureCollectionExtension.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) DarkLoop. All rights reserved.
+//
+
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+
+namespace DarkLoop.Azure.Functions.Authorization.Internal
+{
+ // This functionality is used internally to emulate Asp.net's treatment of AuthenticateResult
+ internal static class FunctionsFeatureCollectionExtension
+ {
+ ///
+ /// Store the given AuthenticateResult in the IFeatureCollection accessible via
+ /// IAuthenticateResultFeature and IHttpAuthenticationFeature
+ ///
+ /// The feature collection to add to
+ /// The authentication to expose in the feature collection
+ /// The object associated with the features
+ public static FunctionAuthorizationFeature SetAuthenticationFeatures(this IFeatureCollection features, AuthenticateResult result)
+ {
+ // A single object is used to handle both of these features so that they stay in sync.
+ // This is in line with what asp core normally does.
+ var feature = new FunctionAuthorizationFeature(result);
+
+ features.Set(feature);
+ features.Set(feature);
+
+ return feature;
+ }
+ }
+}
diff --git a/src/in-proc/FunctionsAuthorizationExecutor.cs b/src/in-proc/FunctionsAuthorizationExecutor.cs
index cb656b3..3ed1667 100644
--- a/src/in-proc/FunctionsAuthorizationExecutor.cs
+++ b/src/in-proc/FunctionsAuthorizationExecutor.cs
@@ -79,6 +79,8 @@ public async Task ExecuteAuthorizationAsync(FunctionExecutingContext context, Ht
var authenticateResult = await _policyEvaluator.AuthenticateAsync(filter.Policy, httpContext);
+ httpContext.Features.SetAuthenticationFeatures(authenticateResult);
+
// still authenticating in case token is sent to set context user but skipping authorization
if (filter.AllowAnonymous)
{
diff --git a/src/isolated/FunctionsAuthorizationMiddleware.cs b/src/isolated/FunctionsAuthorizationMiddleware.cs
index dc2f58a..7490e79 100644
--- a/src/isolated/FunctionsAuthorizationMiddleware.cs
+++ b/src/isolated/FunctionsAuthorizationMiddleware.cs
@@ -6,9 +6,11 @@
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;
@@ -83,6 +85,12 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next
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);
diff --git a/test/Common.Tests/HttpUtils.cs b/test/Common.Tests/HttpUtils.cs
index f15de2b..12808f1 100644
--- a/test/Common.Tests/HttpUtils.cs
+++ b/test/Common.Tests/HttpUtils.cs
@@ -25,11 +25,12 @@ public static HttpContext SetupHttpContext(IServiceProvider services)
var streamReader = PipeReader.Create(requestStream);
var requestHeaders = new HeaderDictionary();
var streamWriter = PipeWriter.Create(responseStream);
+ var features = new FeatureCollection();
httpContextMock.SetupGet(x => x.RequestServices).Returns(services);
httpContextMock.SetupGet(x => x.Request).Returns(requestMock.Object);
httpContextMock.SetupGet(x => x.Response).Returns(responseMock.Object);
- httpContextMock.SetupGet(x => x.Features).Returns(Mock.Of());
+ httpContextMock.SetupGet(x => x.Features).Returns(features);
httpContextMock.SetupGet(x => x.Items).Returns(new Dictionary