Aspnetcore: DI support for auth handler options

Created on 4 Feb 2020  路  16Comments  路  Source: dotnet/aspnetcore

https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-581489114
https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-581516787

Describe the bug

The authentication handlers are configured in Startup.ConfigureServices and have trouble relying on services for any customization. E.g.

    .AddCookie(o =>
    {
        o.TicketDataFormat = new CustomDataFormat(settings.Secret, /* Add logging? */);
    });

IOptions supports DI for configure callbacks, but the existing overloads aren't compatible with the auth setup pattern.

Workaround:

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

            services.AddTransient<IConfigureOptions<CookieAuthenticationOptions>>(sp =>
                new ConfigureNamedOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, o =>
                {
                    o.TicketDataFormat = new CustomDataFormat("secret", sp.GetRequiredService<ILogger<CustomDataFormat>>());
                }));

Proposals:

Some new overloads could make this easier. We should consider if these apply to all of the auth handlers, not just the Cookie samples given here.

.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, (o, serviceProvider) =>
{
        o.TicketDataFormat = new CustomDataFormat("secret", sp.GetRequiredService<ILogger<CustomDataFormat>>());
});

.AddCookie<ILogger<CustomDataFormat>>(CookieAuthenticationDefaults.AuthenticationScheme, (o, logger) =>
{
        o.TicketDataFormat = new CustomDataFormat("secret", logger);
});

services.Configure<CookieAuthenticationOptions, ILogger<CustomDataFormat>>(CookieAuthenticationDefaults.AuthenticationScheme, o =>
{
       o.TicketDataFormat = new CustomDataFormat("secret", sp.GetRequiredService<ILogger<CustomDataFormat>>());
});
Done area-security enhancement help wanted

Most helpful comment

Oh, right, there's a named overload:

services.AddOptions<JwtBearerOptions>("MyJwt")
             .Configure<ILogger<CustomDataFormat>>((options, logger) =>
             {

             });

All 16 comments

services.Configure>(CookieAuthenticationDefaults.AuthenticationScheme, o =>

This seems to be well-covered by the OptionsBuilder API.

.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, (o, serviceProvider) =>
.AddCookie>(CookieAuthenticationDefaults.AuthenticationScheme, (o, logger) =>

These are also well-covered by the OptionsBuilder API, the biggest discovery issue here is the named options goop. Maybe this could be useful.

ConfigureOptions only runs when a new instance of options is requested and for OptionsMonitor it runs when config is reloaded. So the only suitable lifetime to use for TicketDataFormat is singleton. Could we instead have a TicketDataFormatCreator and also ITicketStoreCreator so we can pass scoped and transient services to also cover #6946?

6946 would benefit from this work but I'd recommend tackling them separately. This issue deals broadly with all auth providers where #6946 is looking for a new feature specific to Cookie Auth.

I think this might help us in what I'm currently refactoring; not exactly, but it's the same concept.

We call out for our Auth0 signing key on startup and in 2.x I was passing through an ILoggerFactory (ctor injected into Startup) so I could use it inside AddJwtBearer to log any issues in the api call.

If there was a AddJwtBearer overload that passed through the service provider, I could resolve the ILoggerFactory in there and continue as I always have.

For now, I've ripped it all out and I hope it'll get logged somewhere? Is the usage of my AddJwtBearer configureOptions action wrapped and logged on exception? We use serilog, which sets up a static logger in Program Main for those early issues, but I don't imagine configureOptions action is called directly on startup.

@benmccallum yes, this would help you get to the logger. The workaround above should help you do this today.

            services.AddTransient<IConfigureOptions<JwtBearerOptions>>(sp =>
                new ConfigureNamedOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, o =>
                {
                    var logger = sp.GetRequiredService<ILogger<CustomDataFormat>>();
                }));

There's a much nicer way to do this since .NET Core 2.1 (I forgot to post it since I last saw it);

```C#
services.AddOptions()
.Configure>((options, logger) =>
{

         });

```

@davidfowl that doesn't get you named options, right?

Oh, right, there's a named overload:

services.AddOptions<JwtBearerOptions>("MyJwt")
             .Configure<ILogger<CustomDataFormat>>((options, logger) =>
             {

             });

Thanks guys. Definitely a stupid question, but it was a little tricky to follow the options stuff last time I looked. Would I be calling this method to "register" the options rather than the AddJwtBearer extension method? And then it just gets picked up by the auth fx?

You still need to call AddJwtBearer for other reasons, it's wiring the handler into the auth service.

@Tratcher I would like to work on this.

@Kahbazi sure. How about starting with this overload and if it works well we can try it for other providers.

.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, (o, serviceProvider) =>
{
        var service = serviceProvider.GetRequiredService<MyService>();
});

@Tratcher I started on AuthenticationBuilder.AddSchemeHelper and noticed there's no Configure method that gets Action<TOptions, IServiceProvider> configureOptions on OptionsBuilder. Should I add that first on Extensions repo or start with your workaround first?

@Kahbazi you should be able to use the existing services.AddOptions<TOptions>(optionsName).Configure<T>(Action<TOptions, T>) where T is the IServiceProvider.

@Tratcher thanks, I didn't know I could get IServiceProvider from IServiceProvider!

ConfigureApplicationCookie and related identity extensions are going to need something similar.

Was this page helpful?
0 / 5 - 0 ratings