Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Conversation

@fredrikhr
Copy link

A CookieAuthenticationHandler will use a ITicketStore from DI if and only if the following conditions are met:

  1. The UseSessionStoreFromDI property of the handler's CookieAuthenticationOptions is true.
  2. The DI container has a registered service for the ITicketStore interface, and the returned instance is non-null.

Since UseSessionStoreFromDI is a new property and new behaviour only is applied when its value is true, these changes remain fully backwards compatible.

Code Changes:

  • Adds a new boolean property UseSessionStoreFromDI to Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationOptions.
  • Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler requests an ITicketStore in its constructor with an optional argument.
    Because the argument is optional, DI can provide the argument as null if no ITicketStore service is registered. This will ensure backwards-compatability.
  • In every case where CookieAuthenticationHandler previously got the ITicketStore from its Options.SessionStore, it now first checks the new Options.UseSessionStoreFromDI property and uses the non-null DI-injected ITicketStore instance if UseSessionStoreFromDI is true.

fixes #581

@Tratcher Tratcher self-requested a review April 10, 2018 16:50
Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to need tests.

? _diSessionStore ?? Options.SessionStore
: Options.SessionStore;

public CookieAuthenticationHandler(IOptionsMonitor<CookieAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, ITicketStore diSessionStore = default)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No API signature changes are allowed in 2.1. You'll have to add a new constructor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pakrym does DI support optional parameters like this? We don't usually use them. We tend to use IEnumerable<T> for optional services.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, usually, we use multiple constructors to achieve optional args. Usage of IEnumerable optional services is generally discouraged.

public MemoryCacheTicketStore(IMemoryCache cache)
{
_cache = new MemoryCache(new MemoryCacheOptions());
_cache = cache ?? new MemoryCache(new MemoryCacheOptions());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this ctor gets injected into cache would never be null.

@pakrym
Copy link
Contributor

pakrym commented Apr 10, 2018

Did we ever put ITicketStore to DI? Can't we just enable the feature without the flag? If options.Store == null resolve from DI

@Tratcher
Copy link
Member

Tratcher commented Apr 10, 2018

We never did put it in DI though users might have. There's also the issue of having multiple cookie middleware, they don't all necessarily want the same store. The option may still be redundant though, it may be sufficient to prefer the instance on Options over the instance from DI.
Edit: Wait, that doesn't provide for an opt-out. We still need the option.

@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

Do we really need this functionality at all? You can already use a custom IConfigureNamedOptions that gets ITicketStore from DI and use that to set the instance...

 public class CookieConfig : IConfigureNamedOptions<CookieAuthenticationOptions> { public CookieConfig(ITicketStore store) => _store = store; public void Configure(string name, CookieAuthenticationOptions o) { o.TicketStore = store; } } services.AddTransient<IConfigureOptions<CookieAuthenticationOptions>, CookieConfig>();
@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

That seems good enough to me

@Tratcher
Copy link
Member

Defining your own class and registering that in DI is more work and less discoverable than just registering the ITokenStore in DI. Though if you also have to set an option then the discoverability suffers there too.

@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

That's just the general pattern of how to get things from DI onto options, its not particularly specific to cookies, @scottaddie @Rick-Anderson I think @Tratcher has a good point that we should have an explicit section recommending this pattern of how to configure options instances with things from DI in this doc maybe? https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options

public class UseSomethingFromDI : IConfigureNamedOptions/IConfigureOptions<YourOptions> { public CookieConfig(ISomethingFromDI thing) => _thing = thing; public void Configure([string name,] YourOptions o) { o.Thing = thing; } } services.AddTransient<IConfigureOptions<YourOptions>, UseSomethingFromDI>();
@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

Also Its certainly more work in the long run if we adopted something like this everywhere (useXyzFromDI flags on all services?), instead of just always using the options instance, and relying on configuring the options instance appropriately.

@Rick-Anderson
Copy link

dotnet/AspNetCore.Docs#5922 Add doc section - Enable using a Cookie Session Store from DI

@kevinchalet
Copy link
Contributor

Instead of a UseSessionStoreFromDI option, why not SessionStoreType, modeled after EventsType? This way, you could register multiple stores in the DI and each handler would pick the one set in the options.

@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

I still say we should do nothing special here, since this already can be done via options already

@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

EventsType was added before named options was fully baked, I don't think we'd have added that either since you can accomplish that via a custom named IConfigureOptions as well

@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

So this is actually even easier in 2.1:

services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme).Configure<ITicketStore>(o,t => o.TicketStore = t);

@kevinchalet
Copy link
Contributor

EventsType was added before named options was fully baked, I don't think we'd have added that either since you can accomplish that via a custom named IConfigureOptions as well

No, it was added because there was a need for scoped "services as options", that you can't do with options, where everything is a singleton.

@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

Ah right events are recreated per request, but that's not what ticket store needs here

@kevinchalet
Copy link
Contributor

If the ticket stored uses EF Core, it needs to be scoped, since DbContext itself must be scoped.

@HaoK
Copy link
Member

HaoK commented Apr 10, 2018

You probably can make a particular options type transient for an IOptionsMonitor by plugging in a no-op cache:

https://github.com/aspnet/Options/blob/dev/src/Microsoft.Extensions.Options/IOptionsMonitorCache.cs

services.AddSingleton<IOptionsMonitorCache<CookieAuthenticationOptions>, AlwaysCallFactoryCache>(); 
@kevinchalet
Copy link
Contributor

You probably can make a particular options type scoped for an IOptionsMonitor by plugging in a no-op cache:

Heh, that means the entire options and all the "services" attached are re-created per request, including the IDataFormat and the underlying data protector. Rather inefficient.

The real question is: is using scoped ticket stores common enough to introduce DI support?

@natemcmaster natemcmaster changed the base branch from dev to master July 2, 2018 17:59
@Eilon
Copy link
Contributor

Eilon commented Nov 15, 2018

Closing because we are not planning to add direct support for this feature.

To work around this, you can use the service located pattern to get the required services imperatively instead of through typical DI.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

7 participants