skip to Main Content

I have a .NET Core 3 project (recently upgraded from 2.2) that uses a Redis distributed cache and cookie authentication.

It currently looks something like this:

public void ConfigureServices(IServiceCollection services)
{
    // Set up Redis distributed cache
    services.AddStackExchangeRedisCache(...);

    ...

    services.ConfigureApplicationCookie(options =>
    {
        ...
        // Get a service provider to get the distributed cache set up above
        var cache = services.BuildServiceProvider().GetService<IDistributedCache>();

         options.SessionStore = new MyCustomStore(cache, ...);
    }):
}

The problem is that BuildServiceProvider() causes a build error:

Startup.cs(…): warning ASP0000: Calling ‘BuildServiceProvider’ from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to ‘Configure’.

This doesn’t appear to be an option – ConfigureApplicationCookie is in Startup.ConfigureServices and can only configure new services, Startup.Configure can use the new services, but can’t override CookieAuthenticationOptions.SessionStore to be my custom store.

I’ve tried adding services.AddSingleton<ITicketStore>(p => new MyCustomRedisStore(cache, ...)) before ConfigureApplicationCookie, but this is ignored.

Explicitly setting CookieAuthenticationOptions.SessionStore appears to be the only way to get it to use anything other than the local memory store.

Every example I’ve found online uses BuildServiceProvider();

Ideally I want to do something like:

services.ConfigureApplicationCookieStore(provider => 
{
    var cache = provider.GetService<IDistributedCache>();
    return new MyCustomStore(cache, ...);
});

Or

public void Configure(IApplicationBuilder app, ... IDistributedCache cache)
{
    app.UseApplicationCookieStore(new MyCustomStore(cache, ...));
}

And then CookieAuthenticationOptions.SessionStore should just use whatever I’ve configured there.

How do I make the application cookie use an injected store?

2

Answers


  1. Reference Use DI services to configure options

    If all the dependencies of your custom store are injectable, then just register your store and required dependencies with the service collection and use DI services to configure options

    public void ConfigureServices(IServiceCollection services) {
        // Set up Redis distributed cache
        services.AddStackExchangeRedisCache(...);
    
        //register my custom store
        services.AddSingleton<ITicketStore, MyCustomRedisStore>();
    
        //...
    
        //Use DI services to configure options
        services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
            .Configure<ITicketStore>((options, store) => {
                options.SessionStore = store;
            });
    
        services.ConfigureApplicationCookie(options => {
            //do nothing
        }):
    }
    

    If not then work around what is actually registered

    For example

    //Use DI services to configure options
    services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
        .Configure<IDistributedCache>((options, cache) => {
            options.SessionStore = new MyCustomRedisStore(cache, ...);
        });
    

    Note:

    ConfigureApplicationCookie uses a named options instance. – @KirkLarkin

    public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
            => services.Configure(IdentityConstants.ApplicationScheme, configure);
    

    The option would need to include the name when adding it to services.

    Login or Signup to reply.
  2. To implement Redis Tickets in .NET Core 3.0 we did the following which is the above in a bit more of a final form::

    services.AddSingleton<ITicketStore, RedisTicketStore>();
    services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
         .Configure<ITicketStore>((options, store) => {
             options.SessionStore = store;
         });
    
    
    services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
        .AddIdentityServerAuthentication(options =>
        {
               // ...configure identity server options
        }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
    

    Here is a Redis implementation:

    public class RedisTicketStore : ITicketStore
    {
        private const string KeyPrefix = "AuthSessionStore-";
        private IDistributedCache cache;
    
        public RedisTicketStore(IDistributedCache cache)
        {
            this.cache = cache;
        }
    
        public async Task<string> StoreAsync(AuthenticationTicket ticket)
        {
            var guid = Guid.NewGuid();
            var key = KeyPrefix + guid.ToString();
            await RenewAsync(key, ticket);
            return key;
        }
    
        public Task RenewAsync(string key, AuthenticationTicket ticket)
        {
            var options = new DistributedCacheEntryOptions();
            var expiresUtc = ticket.Properties.ExpiresUtc;
            if (expiresUtc.HasValue)
            {
                options.SetAbsoluteExpiration(expiresUtc.Value);
            }
            byte[] val = SerializeToBytes(ticket);
            cache.Set(key, val, options);
            return Task.FromResult(0);
        }
    
        public Task<AuthenticationTicket> RetrieveAsync(string key)
        {
            AuthenticationTicket ticket;
            byte[] bytes = null;
            bytes = cache.Get(key);
            ticket = DeserializeFromBytes(bytes);
            return Task.FromResult(ticket);
        }
    
        public Task RemoveAsync(string key)
        {
            cache.Remove(key);
            return Task.FromResult(0);
        }
    
        private static byte[] SerializeToBytes(AuthenticationTicket source)
        {
            return TicketSerializer.Default.Serialize(source);
        }
    
        private static AuthenticationTicket DeserializeFromBytes(byte[] source)
        {
            return source == null ? null : TicketSerializer.Default.Deserialize(source);
        }
    }
    

    Redis implementation from: https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search