Storing ASP.NET core identity authorization tickets in Redis.

Normally when using ASP.NET core identity authentication tickets are stored in a cookie, but sometimes we want to store the ticket server side.

Storing the ticket client side in a cooke has the following advantages:

  • Server does not store an session state - no overhead of storing session data in memory.
  • Easier to scale out and load balance between multiple instances as there is no session state to be shared between the service instances.
  • Multiple services don’t need to share session data.
  • Session is not logged out if the service is restarted.

But has the following disadvantages:

  • Difficult to know how many users are logged in at one time.
  • No way to list all the sessions for a user (Useful for remotely logging out devices).
  • Authentication ticket is valid until expiry, no way to easily invalidate or blacklist a session server side.
  • Difficult to implement a session idle timeout (related to above point).

But what if we want to be able to track session information server side and provide the sort of facility that Facebook provides for logging out sessions remotely.

facebook-sessions-information

This requires us to configure ASP.NET core identity to store the authentication ticket server side instead of in a cookie. This storage could be an in memory store or a distributed store such as Redis. When configured this way, only a session id is stored in the client side cookie.

Luckily .NET core has built in support for connecting and storing items in Redis using the RedisCache, so we just need to implement an ITicketStore that uses RedisCache as a storage mechanism and hook it up to ASP.NET identity.

0. Prerequisites

The assumption made is that you have an existing working ASP.NET identity application such as the IdentityDemo project and a running Redis instance.

1. Modify ConfigureServices

The first step is to create the following RedisCacheTicketStore class in you project. This class implements ITicketStore using RedisCache to store the serialized Authentication Tickets.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Caching.Redis;

namespace IdentityDemo
{
    public class RedisCacheTicketStore : ITicketStore
    {
        private const string KeyPrefix = "AuthSessionStore-";
        private IDistributedCache _cache;

        public RedisCacheTicketStore(RedisCacheOptions options)
        {
            _cache = new RedisCache(options);
        }

        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);
        }
    }
}

2. Modify ConfigureServices

Next, in your ConfigureServices just below the call to “services.AddIdentity” configure ASP.NET identity to use the new RedisTicketStore replacing the address and port number with the address and port of your Redis server.

services.ConfigureApplicationCookie(opts =>
	opts.SessionStore = new RedisCacheTicketStore(new RedisCacheOptions()
    	{
        	Configuration = "172.17.0.4:6379"
        })
    );

3. Start your app and test.

Start up your application and log in. Login should work and everything should be working as normal. Use a tool like redis-cli to check and see the auth ticket stored in Redis. Try deleting the key from Redis and notice the application is logged out. That’s it you have sessions stored in Redis.

Written on February 1, 2018