Azure / azure-functions-host

The host/runtime that powers Azure Functions
https://functions.azure.com
MIT License
1.92k stars 441 forks source link

Add support for custom authentication handlers #6805

Open brettsam opened 3 years ago

brettsam commented 3 years ago

Today if you try to overwrite a service that we've registered, it can cause problems. We have a fairly vague explanation of this here: https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#overriding-host-services.

Customers that try to add their own authentication in a FunctionsStartup can run into odd errors later like

System.InvalidOperationException : No authentication handler is registered for the scheme 'ArmToken'. The registered schemes are: WebJobsAuthLevelIdentityServerAuthenticationJwt, WebJobsAuthLevelIdentityServerAuthenticationIntrospection, WebJobsAuthLevel, BearerIdentityServerAuthenticationJwt, BearerIdentityServerAuthenticationIntrospection, Bearer. Did you forget to call AddAuthentication().Add[SomeAuthHandler]("ArmToken",...)?

One example would be doing this in your startup:

builder.Services.AddAuthentication()
    .AddCookie();

We should add a way to allow auth handlers from Startup compose with the built-in functions handlers. Or another alternative may be to prevent custom handlers from impacting /admin routes.

espray commented 3 years ago

YES this would be helpful. A construction func with the AuthenticationBuilder or something similar to IFunctionsConfigurationBuilder See https://github.com/Azure/azure-functions-host/issues/4485

espray commented 3 years ago

@brettsam any update?

hajekj commented 3 years ago

@anthonychu @jeffhollan Would you be able to take a look at this one please? Or eventually point me to someone to discuss this with? I am more than willing to contribute to the runtime to make this work as expected (since I believe it's quite crucial for some of our use-cases of Functions). Thanks!

kamalsivalingam commented 3 years ago

I am getting this error with the latest package as well, Is there any known workaround for this issue?

nazar-kuzo commented 3 years ago

@brettsam @kamalsivalingam @espray I have a workaround for you.

I drilled down investigating what is causing this issue and found out that Azure Function Host has internal logic that is calling "builder.Services.AddAuthentication()" after the client Startup.cs is executed and since in your Startup.cs is already present "builder.Services.AddAuthentication()" the internal one will be ignored.

Here is the link to internal authentication registration: https://github.com/Azure/azure-functions-host/blob/852eed9ceef6ef56b431428a8eb31f1bd9c97f3b/src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs#L46

Here is a "AddAuthentication()" internal logic that ignores the subsequent services registration: https://github.com/dotnet/aspnetcore/blob/0ca2ed9af69e7e334b8e3c1de1d015017f138988/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs#L20

So workaround would be to use dynamic Authentication Schema registration and not to call "builder.Services.AddAuthentication()" at startup.

Dynamic schema registration example: https://github.com/aspnet/AuthSamples/tree/master/samples/DynamicSchemes

My use case: I hate using out of the box EasyAuth AAD authentication and want to use "Microsoft.Identity.Web" authentication. In order to do that I have to register all logic in startap without calling "builder.Services.AddAuthentication()" and then register extension where I inject "IAuthenticationSchemeProvider" and dynamically add custom scheme.

My old code in Startup.cs:

            builder.Services
                .AddMicrosoftIdentityWebApiAuthentication(configuration, configSectionName: nameof(AuthenticationSettings))
                .EnableTokenAcquisitionToCallDownstreamApi()
                .AddInMemoryTokenCaches();

My new code in Startup.cs:

            // avoid calling builder.Services.AddAuthentication() since it overrides internal Azure Function authentication
            new AuthenticationBuilder(builder.Services)
                .AddMicrosoftIdentityWebApi(
                    configuration,
                    configSectionName: nameof(AuthenticationSettings),
                    jwtBearerScheme: CustomAuthenticationSchemes.AadBearer)
                .EnableTokenAcquisitionToCallDownstreamApi()
                .AddInMemoryTokenCaches();

            builder.Services.AddWebJobs(options => { }).AddExtension<AuthenticationExtensionConfigProvider>();

Please note that I use custom scheme since Azure Function uses 3 default schemes (including Bearer) so I have to use different one for my custom authentication

AuthenticationExtensionConfigProvider.cs

public class AuthenticationExtensionConfigProvider : IExtensionConfigProvider
    {
        private readonly IAuthenticationSchemeProvider authenticationSchemeProvider;

        public AuthenticationExtensionConfigProvider(IAuthenticationSchemeProvider authenticationSchemeProvider)
        {
            this.authenticationSchemeProvider = authenticationSchemeProvider;
        }

        public void Initialize(ExtensionConfigContext context)
        {
            this.InitializeAsync().GetAwaiter().GetResult();
        }

        private async Task InitializeAsync()
        {
            var jwtScheme = await this.authenticationSchemeProvider.GetSchemeAsync(JwtBearerDefaults.AuthenticationScheme);

            if (jwtScheme == null)
            {
                this.authenticationSchemeProvider.AddScheme(new AuthenticationScheme())
            }
        }
    }

Now you can authenticate to your function with custom scheme

espray commented 3 years ago

@nazar-kuzo Awesome ✨ I have not looked at this for a few months. I was trying to host Identity Server in an AzFunc, Yes I used Dynamic Schema https://github.com/Azure/azure-functions-host/issues/4485#issuecomment-496268259 as well. But, I did not use IExtensionConfigProvider like you did with adding the schema, as I had the same problem with adding the schema to the IAuthenticationSchemeProvider.

nazar-kuzo commented 3 years ago

@espray

Event Hubs - https://github.com/Azure/azure-functions-eventhubs-extension/blob/dev/src/Microsoft.Azure.WebJobs.Extensions.EventHubs/EventHubsWebJobsStartup.cs

Basically it is the same API but they implement interface so you dont need to explicitly call it

nazar-kuzo commented 3 years ago

@brettsam @espray Published a nuget extension that solves Authentication/Authorization problems https://www.nuget.org/packages/AzureFunctions.Authentication/