diff --git a/.build/release.props b/.build/release.props index 623bc24..85caf25 100644 --- a/.build/release.props +++ b/.build/release.props @@ -6,9 +6,9 @@ DarkLoop DarkLoop - All rights reserved DarkLoop's Azure Functions Authorization - false + true 4.0.0.0 - 4.0.1 + 4.1.0 $(Version).0 https://github.com/dark-loop/functions-authorize https://github.com/dark-loop/functions-authorize/blob/master/LICENSE diff --git a/.gitignore b/.gitignore index 3e759b7..ef34407 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,5 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ +/sample/SampleInProcFunctions.V4/Properties/ServiceDependencies/dl-inproc-func - Zip Deploy +/sample/SampleIsolatedFunctions.V4/Properties/serviceDependencies.dl-isloated-func - Zip Deploy.json diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..a69cff9 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,84 @@ +# Change log +Change log stars with version 3.1.3 + +## 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.
+ One of the effects of this change is the portal not able to interact with the functions app to retrieve runtime information and in some cases not able to retrieve functions information. + In the past this was not an issue and application was able to replace the default `Bearer` configuration to enable the functionality provided by this package.
+ Starting from this version, using the default `AddJwtBearer` with no custom name, will produce an error. You will have 2 options: you can switch your app to use `AddJwtFunctionsBearer` method without providing any name which will map your configuration to the `FunctionsBearer` scheme, or you can use `AddJwtBearer("", ...)` to specify something different. + +## 4.0.1 +Deprecating `DarkLoop.Azure.Functions.Authorize` package in favor of `DarkLoop.Azure.Functions.Authorization.InProcess` package.
+The functionality remains the same, it's just a way to keep package naming in sync. + +## 4.0.0 +Starting from 4.0.0, support for Azure Functions V4 Isolated mode with ASPNET Core integration is added. +The package is now split into two separate packages, one for each mode. + +The package for Azure Functions V3+ In-Proc mode is now called `DarkLoop.Azure.Functions.Authorization.InProcess` and the package for Azure Functions V4 Isolated mode with ASPNET Core integration is called `DarkLoop.Azure.Functions.Authorize.Isolated`. + +- ### .NET 6 support + Starting with version 4.0.0, the package is now targeting .NET 6.0. This means that the package is no longer compatible with .NET 5 or lower. If you are using .NET 5 or lower, you should use version 3.1.3 of the package. + +- ### DarkLoop.Azure.Functions.Authorize v4.0.0 + This package is published but is now deprecated in favor of `DarkLoop.Azure.Functions.Authorization.InProcess`. All it's functionality remains the same. It's just a way to keep package naming in sync. + +- ### Introducing IFunctionsAuthorizationProvider interface + The `IFunctionsAuthorizationProvider` interface is introduced to allow for custom authorization filter provisioning to the framework. + By default the framework relies on decorating the function or type with `[FunctionAuthorize]`. You could skip this decoration and provide the middleware with an authorization filter sourced from your own mechanism, for example a database. + At this moment this can be done only with Isolated mode even when the interface is defined in the shared package.
+ Support for In-Process will be added in a future version, once source generators are introduced, as the in-process framework relies on Invocation Filters to enable authorization. + Replacing the service in the application services would break the authorization for in-process mode at this point. + +## 3.1.3 +3.1.3 and lower versions only support Azure Functions V3 In-Proc mode. Starting from 4.0.0, support for Azure Functions V4 Isolated mode with ASPNET Core integration is added. +- ### Support for disabling `FunctionAuthorize` effect at the application level. + Adding support for disabling the effect of `[FunctionAuthorize]` attribute at the application level. + This is useful when wanting to disable authorization for a specific environment, such as local development. + + When configuring services, you can now configure `FunctionsAuthorizationOptions`. + ```csharp + builder.Services.Configure(options => + options.DisableAuthorization = Configuration.GetValue("AuthOptions:DisableAuthorization")); + ``` + + 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: + ```csharp + builder.Services.Configure( + Configuration.GetSection("FunctionsAuthorization")); + ``` + + For function apps targeting .NET 7 or greater, you can also use `AuthorizationBuilder` to set this value: + ```csharp + builder.Services + .AddAuthorizationBuilder() + .DisableAuthorization(Configuration.GetValue("AuthOptions:DisableAuthorization")); + ``` + + 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: + ```csharp + if (builder.IsLocalAuthorizationContext()) + { + builder.Services.Configure( + options => options.AuthorizationDisabled = true); + } + ``` + + 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: + + ```json + { + "logging": { + "logLevel": { + "DarkLoop": "Warning" + } + } + } + ``` + + Thanks to [BenjaminWang1031](https://github.com/BenjaminWang1031) for the suggestion to add this functionality. + +- #### Remove Functions bult-in JwtBearer configuration by default + 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. + In prior versions, this package has functionality to clear Functions built-in configuration, but it was not enabled by default when using `AddJwtBearer(Action 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`. \ No newline at end of file diff --git a/Functions-Authorize.sln b/Functions-Authorize.sln index 4311cb9..226756e 100644 --- a/Functions-Authorize.sln +++ b/Functions-Authorize.sln @@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".root", ".root", "{3A9A7517 ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore + ChangeLog.md = ChangeLog.md LICENSE = LICENSE NuGet.Config = NuGet.Config README.md = README.md diff --git a/README.md b/README.md index 133d778..07f706e 100644 --- a/README.md +++ b/README.md @@ -17,80 +17,5 @@ This projects is open source and may be redistributed under the terms of the [Ap ### Builds ![master build status](https://dev.azure.com/darkloop/DarkLoop%20Core%20Library/_apis/build/status/Open%20Source/Functions%20Authorize%20-%20Pack?branchName=master) -## Change log -Adding change log starting with version 3.1.3 - -### 4.0.1 -Deprecating `DarkLoop.Azure.Functions.Authorize` package in favor of `DarkLoop.Azure.Functions.Authorization.InProcess` package.
-The functionality remains the same, it's just a way to keep package naming in sync. - -### 4.0.0 -Starting from 4.0.0, support for Azure Functions V4 Isolated mode with ASPNET Core integration is added. -The package is now split into two separate packages, one for each mode. - -The package for Azure Functions V3+ In-Proc mode is now called `DarkLoop.Azure.Functions.Authorization.InProcess` and the package for Azure Functions V4 Isolated mode with ASPNET Core integration is called `DarkLoop.Azure.Functions.Authorize.Isolated`. - -- #### .NET 6 support - Starting with version 4.0.0, the package is now targeting .NET 6.0. This means that the package is no longer compatible with .NET 5 or lower. If you are using .NET 5 or lower, you should use version 3.1.3 of the package. - -- #### DarkLoop.Azure.Functions.Authorize v4.0.0 - This package is published but is now deprecated in favor of `DarkLoop.Azure.Functions.Authorization.InProcess`. All it's functionality remains the same. It's just a way to keep package naming in sync. - -- #### Introducing IFunctionsAuthorizationProvider interface - The `IFunctionsAuthorizationProvider` interface is introduced to allow for custom authorization filter provisioning to the framework. - By default the framework relies on decorating the function or type with `[FunctionAuthorize]`. You could skip this decoration and provide the middleware with an authorization filter sourced from your own mechanism, for example a database. - At this moment this can be done only with Isolated mode even when the interface is defined in the shared package.
- Support for In-Process will be added in a future version, once source generators are introduced, as the in-process framework relies on Invocation Filters to enable authorization. - Replacing the service in the application services would break the authorization for in-process mode at this point. - -### 3.1.3 -3.1.3 and lower versions only support Azure Functions V3 In-Proc mode. Starting from 4.0.0, support for Azure Functions V4 Isolated mode with ASPNET Core integration is added. -- #### Support for disabling `FunctionAuthorize` effect at the application level. - Adding support for disabling the effect of `[FunctionAuthorize]` attribute at the application level. - This is useful when wanting to disable authorization for a specific environment, such as local development. - - When configuring services, you can now configure `FunctionsAuthorizationOptions`. - ```csharp - builder.Services.Configure(options => - options.DisableAuthorization = Configuration.GetValue("AuthOptions:DisableAuthorization")); - ``` - - 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: - ```csharp - builder.Services.Configure( - Configuration.GetSection("FunctionsAuthorization")); - ``` - - For function apps targeting .NET 7 or greater, you can also use `AuthorizationBuilder` to set this value: - ```csharp - builder.Services - .AddAuthorizationBuilder() - .DisableAuthorization(Configuration.GetValue("AuthOptions:DisableAuthorization")); - ``` - - 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: - ```csharp - if (builder.IsLocalAuthorizationContext()) - { - builder.Services.Configure( - options => options.AuthorizationDisabled = true); - } - ``` - - 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: - - ```json - { - "logging": { - "logLevel": { - "DarkLoop": "Warning" - } - } - } - ``` - - Thanks to [BenjaminWang1031](https://github.com/BenjaminWang1031) for the suggestion to add this functionality. - -- #### Remove Functions bult-in JwtBearer configuration by default - 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. - In prior versions, this package has functionality to clear Functions built-in configuration, but it was not enabled by default when using `AddJwtBearer(Action 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`. \ No newline at end of file +## Change Log +You can access the change log [here](https://github.com/dark-loop/functions-authorize/blob/master/ChangeLog.md). \ No newline at end of file diff --git a/sample/SampleInProcFunctions.V4/SampleInProcFunctions.V4.csproj b/sample/SampleInProcFunctions.V4/SampleInProcFunctions.V4.csproj index ac1c932..68e516e 100644 --- a/sample/SampleInProcFunctions.V4/SampleInProcFunctions.V4.csproj +++ b/sample/SampleInProcFunctions.V4/SampleInProcFunctions.V4.csproj @@ -10,16 +10,22 @@ <_FunctionsSkipCleanOutput>true + + + + - + + + PreserveNewest @@ -29,4 +35,5 @@ Never + diff --git a/sample/SampleInProcFunctions.V4/Startup.cs b/sample/SampleInProcFunctions.V4/Startup.cs index 5a14c1d..d95a764 100644 --- a/sample/SampleInProcFunctions.V4/Startup.cs +++ b/sample/SampleInProcFunctions.V4/Startup.cs @@ -24,10 +24,11 @@ public override void Configure(IFunctionsHostBuilder builder) builder.Services .AddFunctionsAuthentication(options => { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtFunctionsBearerDefaults.AuthenticationScheme; + options.DefaultAuthenticateScheme = JwtFunctionsBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtFunctionsBearerDefaults.AuthenticationScheme; }) - .AddJwtBearer(options => + .AddJwtFunctionsBearer(options => { // this line is here to bypass the token validation // and test the functionality of this library. @@ -37,16 +38,16 @@ public override void Configure(IFunctionsHostBuilder builder) // this is what you should look for in a real-world scenario // comment the lines if you cloned this repository and want to test the library - options.Authority = "https://login.microsoftonline.com/"; - options.Audience = ""; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - }; - }, true); + //options.Authority = "https://login.microsoftonline.com/"; + //options.Audience = ""; + //options.TokenValidationParameters = new TokenValidationParameters + //{ + // ValidateIssuer = true, + // ValidateAudience = true, + // ValidateLifetime = true, + // ValidateIssuerSigningKey = true, + //}; + }); builder.Services.AddFunctionsAuthorization(options => { @@ -65,7 +66,7 @@ public override void Configure(IFunctionsHostBuilder builder) public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder) { - builder.ConfigurationBuilder.AddUserSecrets(false, reloadOnChange: true); + builder.ConfigurationBuilder.AddUserSecrets(true, reloadOnChange: true); Configuration = builder.ConfigurationBuilder.Build(); diff --git a/sample/SampleInProcFunctions.V4/host.json b/sample/SampleInProcFunctions.V4/host.json index cb17155..f37a843 100644 --- a/sample/SampleInProcFunctions.V4/host.json +++ b/sample/SampleInProcFunctions.V4/host.json @@ -8,7 +8,8 @@ } }, "logLevel": { - "Darkloop": "Information" + "Darkloop": "Information", + "Microsoft": "Information" } } } \ No newline at end of file diff --git a/sample/SampleIsolatedFunctions.V4/.gitignore b/sample/SampleIsolatedFunctions.V4/.gitignore index ff5b00c..c7b4791 100644 --- a/sample/SampleIsolatedFunctions.V4/.gitignore +++ b/sample/SampleIsolatedFunctions.V4/.gitignore @@ -144,6 +144,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml +*.arm.json # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted #*.pubxml diff --git a/sample/SampleIsolatedFunctions.V4/Program.cs b/sample/SampleIsolatedFunctions.V4/Program.cs index 11ce283..03bb91e 100644 --- a/sample/SampleIsolatedFunctions.V4/Program.cs +++ b/sample/SampleIsolatedFunctions.V4/Program.cs @@ -4,6 +4,7 @@ using System.IdentityModel.Tokens.Jwt; using Common.Tests; +using DarkLoop.Azure.Functions.Authorization; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.DependencyInjection; @@ -29,8 +30,8 @@ .ConfigureServices(services => { services - .AddFunctionsAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => + .AddFunctionsAuthentication(JwtFunctionsBearerDefaults.AuthenticationScheme) + .AddJwtFunctionsBearer(options => { // this line is here to bypass the token validation // and test the functionality of this library. diff --git a/src/abstractions/FunctionsAuthenticationBuilderExtensions.cs b/src/abstractions/FunctionsAuthenticationBuilderExtensions.cs new file mode 100644 index 0000000..7d867a2 --- /dev/null +++ b/src/abstractions/FunctionsAuthenticationBuilderExtensions.cs @@ -0,0 +1,68 @@ +// +// Copyright (c) DarkLoop. All rights reserved. +// + +using System; +using DarkLoop.Azure.Functions.Authorization; +using Microsoft.AspNetCore.Authentication.JwtBearer; + +namespace Microsoft.Extensions.DependencyInjection +{ + + public static class FunctionsAuthenticationBuilderExtensions + { + /// + /// Adds the JWT "FunctionsBearer" scheme to the authentication configuration. + /// + /// The current authentication builder. + /// An action configuring the JWT options for authentication. + /// A instance of the + public static FunctionsAuthenticationBuilder AddJwtFunctionsBearer( + this FunctionsAuthenticationBuilder builder, Action configureOptions) + { + builder.AddJwtBearer(JwtFunctionsBearerDefaults.AuthenticationScheme, configureOptions); + + return builder; + } + + /// + /// Adds the JWT "FunctionsBearer" scheme to the authentication configuration. + /// + /// The current authentication builder. + /// A instance of the + public static FunctionsAuthenticationBuilder AddJwtFunctionsBearer( + this FunctionsAuthenticationBuilder builder) + { + return builder.AddJwtFunctionsBearer(_ => { }); + } + + /// + /// This is a no-op method to prevent conflicts with the built-in AddJwtBearer used by the functions host. + /// + /// The current builder. + /// JWT options configuration. + /// + [Obsolete("This method should not be called without specifying a name, as it would conflict with the framework's built-in setup. Use AddJwtFunctionsBearer instead, or specify a name other than 'Bearer' for scheme.", true)] + public static FunctionsAuthenticationBuilder AddJwtBearer( + this FunctionsAuthenticationBuilder builder, Action configureOptions) + { + // This method should not be called without specifying a name, + // as it would conflict with the framework's built-in setup. + return builder; + } + + /// + /// This is a no-op method to prevent conflicts with the built-in AddJwtBearer used by the functions host. + /// + /// The current builder. + /// + [Obsolete("This method should not be called without specifying a name, as it would conflict with the framework's built-in setup. Use AddJwtFunctionsBearer instead, or specify a name other than 'Bearer' for scheme.", true)] + public static FunctionsAuthenticationBuilder AddJwtBearer( + this FunctionsAuthenticationBuilder builder) + { + // This method should not be called without specifying a name, + // as it would conflict with the framework's built-in setup. + return builder; + } + } +} diff --git a/src/abstractions/FunctionsAuthorizationProvider.cs b/src/abstractions/FunctionsAuthorizationProvider.cs index 1b380d2..da66f2f 100644 --- a/src/abstractions/FunctionsAuthorizationProvider.cs +++ b/src/abstractions/FunctionsAuthorizationProvider.cs @@ -10,6 +10,7 @@ using DarkLoop.Azure.Functions.Authorization.Internal; using DarkLoop.Azure.Functions.Authorization.Properties; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -20,7 +21,7 @@ namespace DarkLoop.Azure.Functions.Authorization internal class FunctionsAuthorizationProvider : IFunctionsAuthorizationProvider { private static readonly IEnumerable __dismissedSchemes = - new[] { Constants.WebJobsAuthScheme, Constants.ArmTokenAuthScheme }; + new[] { Constants.WebJobsAuthScheme, Constants.ArmTokenAuthScheme, JwtBearerDefaults.AuthenticationScheme }; private readonly IAuthenticationSchemeProvider _schemeProvider; private readonly IFunctionsAuthorizationFilterCache _filterCache; diff --git a/src/abstractions/JwtFunctionsBearerDefaults.cs b/src/abstractions/JwtFunctionsBearerDefaults.cs new file mode 100644 index 0000000..d4a4e6d --- /dev/null +++ b/src/abstractions/JwtFunctionsBearerDefaults.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) DarkLoop. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DarkLoop.Azure.Functions.Authorization +{ + /// + /// Default values used for Jwt Functions Bearer authentication. + /// + public class JwtFunctionsBearerDefaults + { + /// + /// The default scheme used for Jwt Functions Bearer authentication. + /// + public const string AuthenticationScheme = "FunctionsBearer"; + } +} diff --git a/src/in-proc/DarkLoop.Azure.Functions.Authorization.InProcess.csproj b/src/in-proc/DarkLoop.Azure.Functions.Authorization.InProcess.csproj index 904f918..6466a51 100644 --- a/src/in-proc/DarkLoop.Azure.Functions.Authorization.InProcess.csproj +++ b/src/in-proc/DarkLoop.Azure.Functions.Authorization.InProcess.csproj @@ -27,10 +27,9 @@ - - - true - + + + all diff --git a/src/in-proc/README.md b/src/in-proc/README.md index 1470f89..9e7b852 100644 --- a/src/in-proc/README.md +++ b/src/in-proc/README.md @@ -31,7 +31,8 @@ namespace MyFunctionAppNamespace options.ClientId = ""; // ... more options here }) - .AddJwtBearer(options => + // This is important as Bearer scheme is used by the platform + .AddJwtFunctionsBearer(options => { options.Audience = ""; // ... more options here @@ -50,6 +51,11 @@ namespace MyFunctionAppNamespace } ``` +> Starting with version 4.1.0, the default Bearer scheme is not supported by this framework. +> You can use a custom scheme or make use of `AddJwtFunctionsBearer(Action)` as shown above. This one +adds the `"FunctionsBearer"` scheme. Clients still submit token for Authorization header in the format: `Bearer `. + + No need to register the middleware the way we do for ASP.NET Core applications. ### Using the attribute diff --git a/src/in-proc/Security/FunctionsAuthenticationBuilderExtensions.cs b/src/in-proc/Security/FunctionsAuthenticationBuilderExtensions.cs index bbd1964..ab55647 100644 --- a/src/in-proc/Security/FunctionsAuthenticationBuilderExtensions.cs +++ b/src/in-proc/Security/FunctionsAuthenticationBuilderExtensions.cs @@ -24,6 +24,8 @@ public static class FunctionsAuthenticationBuilderExtensions /// Bearer scheme is still in place, but Admin level is not set for incoming requests. /// When setting this value to true (default) all existing configuration will be removed. /// A instance of the + /// This method will be removed in future versions. + [Obsolete("This method is obsolete. Using this method might break Azure portal experience. Use the AddJwtFunctionsBearer(Action) method instead.", true)] public static FunctionsAuthenticationBuilder AddJwtBearer( this FunctionsAuthenticationBuilder builder, bool removeBuiltInConfig = true) { @@ -41,6 +43,8 @@ public static FunctionsAuthenticationBuilder AddJwtBearer( /// Bearer scheme is still in place, but Admin level is not set incoming requests. /// When setting this value to (default) all existing configuration will be removed. /// A instance of the + /// This method will be removed in future versions. + [Obsolete("This method is obsolete. Using this method might break Azure portal experience. Use the AddJwtFunctionsBearer(Action) method instead.", true)] public static FunctionsAuthenticationBuilder AddJwtBearer( this FunctionsAuthenticationBuilder builder, Action configureOptions, bool removeBuiltInConfig = true) { diff --git a/src/in-proc/Security/FunctionsAuthenticationServiceCollectionExtensions.cs b/src/in-proc/Security/FunctionsAuthenticationServiceCollectionExtensions.cs index 420511d..ecd0432 100644 --- a/src/in-proc/Security/FunctionsAuthenticationServiceCollectionExtensions.cs +++ b/src/in-proc/Security/FunctionsAuthenticationServiceCollectionExtensions.cs @@ -5,9 +5,7 @@ using System; using DarkLoop.Azure.Functions.Authorization; using DarkLoop.Azure.Functions.Authorization.Internal; -using DarkLoop.Azure.Functions.Authorization.Utils; using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { @@ -45,26 +43,20 @@ private static FunctionsAuthenticationBuilder AddFunctionsAuthentication( { var authBuilder = new FunctionsAuthenticationBuilder(services); - if (HostUtils.IsLocalDevelopment) + if (!string.IsNullOrWhiteSpace(defaultScheme)) { - if (!string.IsNullOrWhiteSpace(defaultScheme)) - { - services.AddAuthentication(defaultScheme!); - } - else - { - services.AddAuthentication(); - } - - LocalHostUtils.AddScriptJwtBearer(authBuilder); - LocalHostUtils.AddScriptAuthLevel(authBuilder); - LocalHostUtils.AddArmToken(authBuilder); + services.AddAuthentication(defaultScheme!); } else { - HostUtils.AddFunctionsBuiltInAuthentication(services); + services.AddAuthentication(); } + authBuilder + .AddArmToken() + .AddScriptAuthLevel() + .AddScriptJwtBearer(); + if (configure is not null) { services.Configure(configure); diff --git a/src/in-proc/Security/FunctionsAuthorizationServiceCollectionExtensions.cs b/src/in-proc/Security/FunctionsAuthorizationServiceCollectionExtensions.cs index 85b3c04..82a1e70 100644 --- a/src/in-proc/Security/FunctionsAuthorizationServiceCollectionExtensions.cs +++ b/src/in-proc/Security/FunctionsAuthorizationServiceCollectionExtensions.cs @@ -3,8 +3,8 @@ // using System; -using DarkLoop.Azure.Functions.Authorization.Utils; using Microsoft.AspNetCore.Authorization; +using Microsoft.Azure.WebJobs.Script.WebHost; namespace Microsoft.Extensions.DependencyInjection { @@ -35,7 +35,7 @@ public static IServiceCollection AddFunctionsAuthorization( if (services is null) throw new ArgumentNullException(nameof(services)); if (configure is null) throw new ArgumentNullException(nameof(configure)); - HostUtils.AddFunctionsBuiltInAuthorization(services); + services.AddWebJobsScriptHostAuthorization(); services.Configure(configure); return services; diff --git a/src/in-proc/Utils/HostUtils.cs b/src/in-proc/Utils/HostUtils.cs index bf084f5..cdce74f 100644 --- a/src/in-proc/Utils/HostUtils.cs +++ b/src/in-proc/Utils/HostUtils.cs @@ -3,10 +3,8 @@ // using System; -using System.Linq.Expressions; using System.Reflection; using DarkLoop.Azure.Functions.Authorization.Properties; -using Microsoft.Extensions.DependencyInjection; namespace DarkLoop.Azure.Functions.Authorization.Utils { @@ -16,12 +14,8 @@ internal class HostUtils // These are a series of publicly available types that are used to interact with the Azure Functions runtime. // We use reflection to access these types to not create a hard dependency on the Azure Functions WebHost. - private static readonly Type? __webHostSvcCollectionExtType; internal static readonly Type? FunctionExecutionFeatureType; - private static readonly Func? __authorizationFunc; - private static readonly Func? __authenticationFunc; - static HostUtils() { WebJobsHostAssembly = Assembly.Load(Strings.WJH_Assembly); @@ -31,56 +25,12 @@ static HostUtils() throw new InvalidOperationException($"{Assembly.GetExecutingAssembly()} cannot be used outside of an Azure Functions environment."); } - __webHostSvcCollectionExtType = WebJobsHostAssembly.GetType(Strings.WJH_WebJovsSvcsExtensions); - FunctionExecutionFeatureType = WebJobsHostAssembly.GetType(Strings.WJH_FuncExecFeature); - var entryAssembly = Assembly.GetEntryAssembly(); var entryFullName = entryAssembly!.FullName; var entryName = entryFullName!.Substring(0, entryFullName.IndexOf(',')); IsLocalDevelopment = !entryName.Equals(Strings.WJH_Assembly, StringComparison.OrdinalIgnoreCase); - - __authenticationFunc = BuildBuiltInAuthenticationFunc(); - __authorizationFunc = BuildBuiltInAuthorizationFunc(); } internal static bool IsLocalDevelopment { get; } - - internal static IServiceCollection AddFunctionsBuiltInAuthentication(IServiceCollection services) - { - return __authenticationFunc?.Invoke(services) ?? services; - } - - internal static IServiceCollection AddFunctionsBuiltInAuthorization(IServiceCollection services) - { - return __authorizationFunc?.Invoke(services) ?? services; - } - - private static Func BuildBuiltInAuthenticationFunc() - { - if (__webHostSvcCollectionExtType is not null) - { - var services = Expression.Parameter(typeof(IServiceCollection), "services"); - var method = Expression.Call(__webHostSvcCollectionExtType, Strings.WJH_AddAuthentication, Type.EmptyTypes, services); - var lambda = Expression.Lambda>(method, services); - - return lambda.Compile(); - } - - return builder => builder; - } - - private static Func BuildBuiltInAuthorizationFunc() - { - if (__webHostSvcCollectionExtType is not null) - { - var services = Expression.Parameter(typeof(IServiceCollection), "services"); - var method = Expression.Call(__webHostSvcCollectionExtType, Strings.WJH_AddAuthorization, Type.EmptyTypes, services); - var lambda = Expression.Lambda>(method, services); - - return lambda.Compile(); - } - - return services => services; - } } } diff --git a/src/in-proc/Utils/LocalHostUtils.cs b/src/in-proc/Utils/LocalHostUtils.cs deleted file mode 100644 index 2a999eb..0000000 --- a/src/in-proc/Utils/LocalHostUtils.cs +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) DarkLoop. All rights reserved. -// - -using System; -using System.Linq.Expressions; -using System.Reflection; -using DarkLoop.Azure.Functions.Authorization.Properties; -using Microsoft.AspNetCore.Authentication; - -namespace DarkLoop.Azure.Functions.Authorization.Utils -{ - internal class LocalHostUtils : HostUtils - { - private static readonly Assembly? __funcAssembly; - private static readonly MethodInfo? __addSchemeMethod; - - // The following types are used to interact with the Azure Functions runtime. - // We use reflection to access these types to not create a hard dependency on the Azure Functions Hosting. - private static readonly Type? __jwtSecurityExtensionsType; - private static readonly Type? __authLevelOptionsType; - private static readonly Type? __armTokenOptionsType; - private static readonly Type? __cliAuthHandlerType; - private static readonly Func? __addBuiltInJwt; - private static readonly Func? __addAuthLevel; - private static readonly Func? __addArmToken; - - static LocalHostUtils() - { - try - { - // this is problematic for testing as the core tools are not loaded and there's not package available - // enclosing in try/catch to avoid breaking the tests - __funcAssembly = Assembly.Load("func"); - } - catch - { - // ignored - } - - if (IsLocalDevelopment) - { - Expression addSchemeExpression = (AuthenticationBuilder b) => - b.AddScheme>("scheme", null); - - __addSchemeMethod = (((LambdaExpression)addSchemeExpression).Body as MethodCallExpression)!.Method.GetGenericMethodDefinition(); - __jwtSecurityExtensionsType = WebJobsHostAssembly.GetType(Strings.WJH_JWTExtensions); - __authLevelOptionsType = WebJobsHostAssembly.GetType(Strings.WJH_AuthLevelOptions); - __armTokenOptionsType = WebJobsHostAssembly.GetType(Strings.WJH_ArmAuthOptions); - __cliAuthHandlerType = __funcAssembly?.GetType(Strings.Func_ClieAuthHandler); - - __addBuiltInJwt = BuildAddBuiltInJwtFunc(); - __addAuthLevel = BuildAuthLevelFunc(); - __addArmToken = BuildArmTokenFunc(); - } - } - - internal static FunctionsAuthenticationBuilder AddScriptJwtBearer(FunctionsAuthenticationBuilder builder) - { - __addBuiltInJwt?.Invoke(builder); - - return builder; - } - - internal static FunctionsAuthenticationBuilder AddScriptAuthLevel(FunctionsAuthenticationBuilder builder) - { - __addAuthLevel?.Invoke(builder); - - return builder; - } - - internal static FunctionsAuthenticationBuilder AddArmToken(FunctionsAuthenticationBuilder builder) - { - __addArmToken?.Invoke(builder); - - return builder; - } - - private static Func BuildAddBuiltInJwtFunc() - { - if (__jwtSecurityExtensionsType is not null) - { - var builder = Expression.Parameter(typeof(AuthenticationBuilder), "builder"); - var method = Expression.Call(__jwtSecurityExtensionsType, "AddScriptJwtBearer", Type.EmptyTypes, builder); - var lambda = Expression.Lambda>(method, builder); - - return lambda.Compile(); - } - - return builder => builder; - } - - private static Func BuildAuthLevelFunc() - { - if (IsLocalDevelopment && __authLevelOptionsType is not null && __cliAuthHandlerType is not null) - { - var builder = Expression.Parameter(typeof(AuthenticationBuilder), "builder"); - var scheme = Expression.Constant(Constants.WebJobsAuthScheme); - var action = Expression.Lambda(Expression.Empty(), Expression.Parameter(__authLevelOptionsType, "options")); - var genMethod = __addSchemeMethod!.MakeGenericMethod(__authLevelOptionsType, __cliAuthHandlerType.MakeGenericType(__authLevelOptionsType)); - var method = Expression.Call(builder, genMethod, scheme, action); - var lambda = Expression.Lambda>(method, builder); - - return lambda.Compile(); - } - - return builder => builder; - } - - private static Func BuildArmTokenFunc() - { - if (IsLocalDevelopment && __armTokenOptionsType is not null && __cliAuthHandlerType is not null) - { - var builder = Expression.Parameter(typeof(AuthenticationBuilder), "builder"); - var scheme = Expression.Constant(Constants.ArmTokenAuthScheme); - var action = Expression.Lambda(Expression.Empty(), Expression.Parameter(__armTokenOptionsType, "options")); - var genMethod = __addSchemeMethod!.MakeGenericMethod(__armTokenOptionsType, __cliAuthHandlerType.MakeGenericType(__armTokenOptionsType)); - var method = Expression.Call(builder, genMethod, scheme, action); - var lambda = Expression.Lambda>(method, builder); - - return lambda.Compile(); - } - - return builder => builder; - } - } -} diff --git a/src/isolated/README.md b/src/isolated/README.md index 78240e3..dd2c018 100644 --- a/src/isolated/README.md +++ b/src/isolated/README.md @@ -28,10 +28,11 @@ var host = new HostBuilder() { services .AddFunctionsAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => + // This is important as Bearer scheme is used by the platform + .AddJwtFunctionsBearer(options => { options.Authority = "https://login.microsoftonline.com/your-tenant-id"; - options.Audience = "your-client-id"; + options.Audience = "your-app-id-uri"; ... }); @@ -47,6 +48,11 @@ var host = new HostBuilder() host.Run(); ``` +> Starting with version 4.1.0, the default Bearer scheme is not supported by this framework. +> You can use a custom scheme or make use of `AddJwtFunctionsBearer(Action)` as shown above. This one +adds the `"FunctionsBearer"` scheme. Clients still submit token for Authorization header in the format: `Bearer `. + + Notice the call to `UseFunctionsAuthorization` in the `ConfigureFunctionsWebAppliction` method. This is required to ensure that the middleware is placed in the pipeline where required function information is available.` diff --git a/test/Abstractions.Tests/Internal/KeyedMonitorTests.cs b/test/Abstractions.Tests/Internal/KeyedMonitorTests.cs index aa597b6..3b1f0d9 100644 --- a/test/Abstractions.Tests/Internal/KeyedMonitorTests.cs +++ b/test/Abstractions.Tests/Internal/KeyedMonitorTests.cs @@ -2,11 +2,6 @@ // Copyright (c) DarkLoop. All rights reserved. // -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using DarkLoop.Azure.Functions.Authorization.Internal; namespace Abstractions.Tests.Internal diff --git a/test/InProc.Tests/FunctionsAuthorizationExecutorTests.cs b/test/InProc.Tests/FunctionsAuthorizationExecutorTests.cs index a809185..c6584c3 100644 --- a/test/InProc.Tests/FunctionsAuthorizationExecutorTests.cs +++ b/test/InProc.Tests/FunctionsAuthorizationExecutorTests.cs @@ -40,7 +40,7 @@ public void Initialize() options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) - .AddJwtBearer(delegate { }, true); + .AddJwtFunctionsBearer(delegate { }); services .AddFunctionsAuthorization() diff --git a/test/Isolated.Tests/FunctionsAuthorizationMiddlewareTests.cs b/test/Isolated.Tests/FunctionsAuthorizationMiddlewareTests.cs index 5f18541..e166c25 100644 --- a/test/Isolated.Tests/FunctionsAuthorizationMiddlewareTests.cs +++ b/test/Isolated.Tests/FunctionsAuthorizationMiddlewareTests.cs @@ -8,7 +8,6 @@ using DarkLoop.Azure.Functions.Authorization.Properties; using Isolated.Tests.Fakes; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Http; @@ -35,8 +34,8 @@ public void Initialize() services .AddSingleton(config) .AddLogging() - .AddFunctionsAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(); + .AddFunctionsAuthentication(JwtFunctionsBearerDefaults.AuthenticationScheme) + .AddJwtFunctionsBearer(); services .AddFunctionsAuthorization()