Skip to content

Commit bf0451e

Browse files
authored
Allow for disabling authorization and removing host config when using custom JWT on Bearer (#26)
* Adding support to disable authorize attribute effect on HTTP functions
1 parent d48a9e5 commit bf0451e

16 files changed

+224
-35
lines changed

.build/release.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66
<PackageId>DarkLoop.Azure.Functions.Authorize</PackageId>
77
<IsPreview>false</IsPreview>
88
<AssemblyVersion>3.0.0.0</AssemblyVersion>
9-
<Version>3.1.2</Version>
9+
<Version>3.1.3</Version>
1010
<FileVersion>$(Version).0</FileVersion>
1111
<RepositoryUrl>https://github.com/dark-loop/functions-authorize</RepositoryUrl>
1212
<License>https://github.com/dark-loop/functions-authorize/blob/master/LICENSE</License>
1313
<RepositoryType>Git</RepositoryType>
14-
<PackageTags>AuthorizeAttribute, Authorize, Azure Functions, Azure, Bearer, JWT</PackageTags>
14+
<PackageTags>AuthorizeAttribute, Authorize, Azure Functions, Azure, Bearer, JWT, Policy based authorization</PackageTags>
1515
<PackageIconUrl>https://en.gravatar.com/userimage/22176525/45f25acea686a783e5b2ca172d72db71.png</PackageIconUrl>
1616
<SignAssembly>true</SignAssembly>
1717
<AssemblyOriginatorKeyFile>dl-sftwr-sn-key.snk</AssemblyOriginatorKeyFile>
1818
<IsPackable>true</IsPackable>
19-
<Description>Azure Functions V3 authentication extensions to enable authentication and authorization on a per function basis.</Description>
19+
<Description>Azure Functions V3 authentication extensions to enable authentication and authorization on a per function basis based on ASPNET Core frameworks.</Description>
2020
<PackageReadmeFile>README.md</PackageReadmeFile>
2121
</PropertyGroup>
2222

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,57 @@ public class Functions
8989

9090
### Builds
9191
![master build status](https://dev.azure.com/darkloop/DarkLoop%20Core%20Library/_apis/build/status/Open%20Source/Functions%20Authorize%20-%20Pack?branchName=master)
92+
93+
## Change log
94+
Adding change log starting with version 3.1.3
95+
96+
### 3.1.3
97+
- #### Support for disabling `FunctionAuthorize` effect at the application level.
98+
Adding support for disabling the effect of `[FunctionAuthorize]` attribute at the application level.
99+
This is useful when wanting to disable authorization for a specific environment, such as local development.
100+
101+
When configuring services, you can now configure `FunctionsAuthorizationOptions`.
102+
```c#
103+
builder.Services.Configure<FunctionsAuthorizationOptions>(options =>
104+
options.DisableAuthorization = Configuration.GetValue<bool>("AuthOptions:DisableAuthorization"));
105+
```
106+
107+
Optionally you can bind it to configuration to rely on providers like User Secrets or Azure App Configuration to disable and re-enable without having to restart your application:
108+
```c#
109+
builder.Services.Configure<FunctionsAuthorizationOptions>(
110+
Configuration.GetSection("FunctionsAuthorization"));
111+
```
112+
113+
For function apps targeting .NET 7 or greater, you can also use `AuthorizationBuilder` to set this value:
114+
```c#
115+
builder.Services
116+
.AddAuthorizationBuilder()
117+
.DisableAuthorization(Configuration.GetValue<bool>("AuthOptions:DisableAuthorization"));
118+
```
119+
120+
It's always recommended to encapsulate this logic within checks for environments to ensure that if the configuration setting is unintentionally moved to a non-desired environment, it would not affect security of our HTTP triggered functions. This change adds a helper method to identify if you are running the function app in the local environment:
121+
```c#
122+
if (builder.IsLocalAuthorizationContext())
123+
{
124+
builder.Services.Configure<FunctionsAuthorizationOptions>(
125+
options => options.AuthorizationDisabled = true);
126+
}
127+
```
128+
129+
If you want to output warnings emitted by the library remember to set the log level to `Warning` or lower for `Darkloop` category in your `host.json` file:
130+
131+
```json
132+
{
133+
"logging": {
134+
"logLevel": {
135+
"DarkLoop": "Warning"
136+
}
137+
}
138+
}
139+
```
140+
141+
Thanks to [BenjaminWang1031](https://github.com/BenjaminWang1031) for the suggestion to add this functionality.
142+
143+
- #### Remove Functions bult-in JwtBearer configuration by default
144+
Azure Functions recently [added configuration](https://github.com/Azure/azure-functions-host/pull/9678) for issuer and audience validation for the default authentication flows, not the one supported by this package through `FunctionAuthorizeAttribute`, which interferes with token validation when using our own Bearer scheme token configuration.
145+
In prior versions, this package has functionality to clear Functions built-in configuration, but it was not enabled by default when using `AddJwtBearer(Action<JwtBearerOptions> configure, bool removeBuiltInConfig = false)`. Since the use of this package is commonly used for custom JWT token, the default value of `removeBuiltInConfig` is now `true`.

sample/Darkloop.Azure.Functions.Authorize.SampleFunctions.V4/Darkloop.Azure.Functions.Authorize.SampleFunctions.V4.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
</PropertyGroup>
77
<ItemGroup>
88
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
9-
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="5.0.0" />
9+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
1010
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.0.1" />
1111
</ItemGroup>
1212
<ItemGroup>

sample/Darkloop.Azure.Functions.Authorize.SampleFunctions.V4/Startup.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using DarkLoop.Azure.Functions.Authorize.SampleFunctions.V4;
2+
using DarkLoop.Azure.Functions.Authorize.Security;
23
using Microsoft.AspNetCore.Authentication.JwtBearer;
4+
using Microsoft.AspNetCore.Authorization;
35
using Microsoft.AspNetCore.Http;
46
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
57
using Microsoft.Extensions.Configuration;
68
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Logging;
710
using System;
811
using System.Collections.Generic;
912
using System.Text;
@@ -56,11 +59,20 @@ public override void Configure(IFunctionsHostBuilder builder)
5659
}, true);
5760

5861
builder.Services.AddFunctionsAuthorization();
62+
63+
// If you want to disable authorization for all functions
64+
// decorated with FunctionAuthorizeAttribute you can add the following configuration.
65+
// If you bind it to configuration, you can modify the setting remotely using
66+
// Azure App Configuration or other configuration providers without the need to restart app.
67+
if (builder.IsLocalAuthorizationContext())
68+
{
69+
builder.Services.Configure<FunctionsAuthorizationOptions>(Configuration.GetSection("AuthOptions"));
70+
}
5971
}
6072

6173
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
6274
{
63-
builder.ConfigurationBuilder.AddUserSecrets<Startup>();
75+
builder.ConfigurationBuilder.AddUserSecrets<Startup>(false, reloadOnChange: true);
6476

6577
Configuration = builder.ConfigurationBuilder.Build();
6678

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
{
22
"version": "2.0",
33
"logging": {
4-
"applicationInsights": {
5-
"samplingSettings": {
6-
"isEnabled": true,
7-
"excludedTypes": "Request"
8-
}
4+
"applicationInsights": {
5+
"samplingSettings": {
6+
"isEnabled": true,
7+
"excludedTypes": "Request"
98
}
9+
},
10+
"logLevel": {
11+
"Darkloop": "Information"
12+
}
1013
}
1114
}

src/DarkLoop.Azure.Functions.Authorize/DarkLoop.Azure.Functions.Authorize.csproj

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>netstandard2.1</TargetFramework>
3+
<TargetFrameworks>netstandard2.1;net7.0</TargetFrameworks>
44
<Version>0.0.1-preview</Version>
55
<Company>DarkLoop</Company>
66
<Copyright>DarkLoop - All rights reserved</Copyright>
@@ -25,7 +25,7 @@
2525
<None Include="..\..\.editorconfig" Link=".editorconfig" />
2626
</ItemGroup>
2727

28-
<ItemGroup>
28+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
2929
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.2.0" />
3030
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.0.3" />
3131
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
@@ -36,6 +36,17 @@
3636
</IncludeAssets>
3737
</PackageReference>
3838
</ItemGroup>
39+
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
40+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.0" />
41+
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="7.0.0" />
42+
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
43+
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.2" />
44+
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.12">
45+
<IncludeAssets>
46+
<IsPackable>true</IsPackable>
47+
</IncludeAssets>
48+
</PackageReference>
49+
</ItemGroup>
3950
<ItemGroup>
4051
<None Update="local.settings.json">
4152
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

src/DarkLoop.Azure.Functions.Authorize/Filters/FunctionAuthorizationFilterIndex.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Reflection;
5+
using DarkLoop.Azure.Functions.Authorize.Security;
56
using Microsoft.AspNetCore.Authentication;
67
using Microsoft.AspNetCore.Authorization;
78
using Microsoft.Azure.WebJobs;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Options;
811

912
namespace DarkLoop.Azure.Functions.Authorize.Filters
1013
{
1114
class FunctionAuthorizationFilterIndex : IFunctionsAuthorizationFilterIndex
1215
{
1316
private readonly ConcurrentDictionary<string, IFunctionsAuthorizeFilter> _index =
1417
new ConcurrentDictionary<string, IFunctionsAuthorizeFilter>();
15-
private readonly IAuthorizationPolicyProvider _policyProvider;
16-
private readonly IAuthenticationSchemeProvider _schemeProvider;
18+
19+
private readonly IServiceProvider _serviceProvider;
20+
private readonly ObjectFactory _filterFactory;
1721

1822
public FunctionAuthorizationFilterIndex(
19-
IAuthenticationSchemeProvider schemeProvider,
20-
IAuthorizationPolicyProvider policyProvider)
23+
IServiceProvider serviceProvider)
2124
{
22-
_schemeProvider = schemeProvider;
23-
_policyProvider = policyProvider;
25+
_serviceProvider = serviceProvider;
26+
_filterFactory = ActivatorUtilities.CreateFactory(typeof(FunctionsAuthorizeFilter), new[] { typeof(IEnumerable<IAuthorizeData>) });
2427
}
2528

2629
public void AddAuthorizationFilter(MethodInfo functionMethod, FunctionNameAttribute nameAttribute, IEnumerable<IAuthorizeData> authorizeData)
@@ -29,15 +32,15 @@ public void AddAuthorizationFilter(MethodInfo functionMethod, FunctionNameAttrib
2932
if (authorizeData is null) throw new ArgumentNullException(nameof(authorizeData));
3033

3134
var name = this.GetFunctionName(functionMethod, nameAttribute);
32-
var filter = new FunctionsAuthorizeFilter(_schemeProvider, _policyProvider, authorizeData);
35+
var filter = _filterFactory.Invoke(_serviceProvider, new[] { authorizeData }) as FunctionsAuthorizeFilter;
3336

34-
if (!this._index.TryAdd(name, filter))
37+
if (!this._index.TryAdd(name, filter!))
3538
{
3639
throw new InvalidOperationException($"An authorization filter for function {name} has already been processed. Make sure function names are unique within your Functions App.");
3740
}
3841
}
3942

40-
public IFunctionsAuthorizeFilter GetAuthorizationFilter(string functionName)
43+
public IFunctionsAuthorizeFilter? GetAuthorizationFilter(string functionName)
4144
{
4245
_index.TryGetValue(functionName, out var stored);
4346

@@ -48,12 +51,14 @@ private string GetFunctionName(MethodInfo method, FunctionNameAttribute nameAttr
4851
{
4952
if (nameAttribute is null)
5053
{
51-
return $"{method.DeclaringType.Name}.{method.Name}";
54+
return $"{method.DeclaringType!.Name}.{method.Name}";
5255
}
5356
else
5457
{
5558
return nameAttribute.Name;
5659
}
5760
}
61+
62+
5863
}
5964
}

src/DarkLoop.Azure.Functions.Authorize/Filters/FunctionsAuthorizeFilter.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
using Microsoft.AspNetCore.Authentication;
77
using Microsoft.AspNetCore.Authorization;
88
using Microsoft.AspNetCore.Authorization.Policy;
9+
using Microsoft.AspNetCore.Http.Extensions;
910
using Microsoft.AspNetCore.Mvc;
1011
using Microsoft.Extensions.DependencyInjection;
12+
using Microsoft.Extensions.Logging;
13+
using Microsoft.Extensions.Options;
1114

1215
namespace DarkLoop.Azure.Functions.Authorize.Filters
1316
{
@@ -18,6 +21,9 @@ internal class FunctionsAuthorizeFilter : IFunctionsAuthorizeFilter
1821
new[] { Constants.WebJobsAuthScheme } :
1922
new[] { Constants.WebJobsAuthScheme, Constants.ArmTokenAuthScheme };
2023

24+
private readonly IOptionsMonitor<FunctionsAuthorizationOptions> _authOptionsMonitor;
25+
private readonly ILogger<FunctionsAuthorizeFilter> _logger;
26+
2127
public IEnumerable<IAuthorizeData> AuthorizeData { get; }
2228

2329
public IAuthenticationSchemeProvider SchemeProvider { get; }
@@ -29,8 +35,12 @@ internal class FunctionsAuthorizeFilter : IFunctionsAuthorizeFilter
2935
public FunctionsAuthorizeFilter(
3036
IAuthenticationSchemeProvider schemeProvider,
3137
IAuthorizationPolicyProvider policyProvider,
32-
IEnumerable<IAuthorizeData> authorizeData)
38+
IEnumerable<IAuthorizeData> authorizeData,
39+
IOptionsMonitor<FunctionsAuthorizationOptions> authorizationOptions,
40+
ILogger<FunctionsAuthorizeFilter> logger)
3341
{
42+
this._authOptionsMonitor = authorizationOptions;
43+
this._logger = logger;
3444
this.SchemeProvider = schemeProvider;
3545
this.PolicyProvider = policyProvider;
3646
this.AuthorizeData = authorizeData;
@@ -59,6 +69,14 @@ from scheme in schemes
5969

6070
public async Task AuthorizeAsync(FunctionAuthorizationContext context)
6171
{
72+
if (this._authOptionsMonitor.CurrentValue.AuthorizationDisabled)
73+
{
74+
_logger.LogWarning(
75+
$"Authorization through FunctionAuthorizeAttribute is disabled at the application level. Skipping authorization for {context.HttpContext.Request.GetDisplayUrl()}.");
76+
77+
return;
78+
}
79+
6280
if (context is null) throw new ArgumentNullException(nameof(context));
6381

6482
if (context.HttpContext.Items.ContainsKey(Constants.AuthInvokedKey))
@@ -92,7 +110,7 @@ private Task<AuthorizationPolicy> ComputePolicyAsync()
92110
throw new InvalidOperationException("Policy cannot be created.");
93111
}
94112

95-
return AuthorizationPolicy.CombineAsync(this.PolicyProvider, this.AuthorizeData);
113+
return AuthorizationPolicy.CombineAsync(this.PolicyProvider, this.AuthorizeData)!;
96114
}
97115
}
98116
}

src/DarkLoop.Azure.Functions.Authorize/Filters/IFunctionsAuthorizationFilterIndex.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace DarkLoop.Azure.Functions.Authorize.Filters
77
{
88
interface IFunctionsAuthorizationFilterIndex
99
{
10-
IFunctionsAuthorizeFilter GetAuthorizationFilter(string functionName);
10+
IFunctionsAuthorizeFilter? GetAuthorizationFilter(string functionName);
1111

1212
void AddAuthorizationFilter(MethodInfo functionMethod, FunctionNameAttribute nameAttribute, IEnumerable<IAuthorizeData> authorizeData);
1313
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using DarkLoop.Azure.Functions.Authorize.Security;
2+
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Text;
6+
7+
namespace Microsoft.Azure.Functions.Extensions.DependencyInjection
8+
{
9+
/// <summary>
10+
/// Extension methods for <see cref="IFunctionsHostBuilder"/>.
11+
/// </summary>
12+
public static class FunctionsHostBuilderExtensions
13+
{
14+
/// <summary>
15+
/// Returns a value indicating whether the current environment is local development.
16+
/// </summary>
17+
/// <param name="builder">The current builder.</param>
18+
/// <returns></returns>
19+
public static bool IsLocalAuthorizationContext(this IFunctionsHostBuilder builder)
20+
{
21+
return AuthHelper.IsLocalDevelopment;
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)