Thinktecture.IdentityModel.Hawk – Nonce Storage for Replay Protection – Redis Example

Thinktecture.IdentityModel.Hawk now stores nonce values and validates the incoming nonce to detect replays. In Hawk authentication, the timestamp (ts) field is the fundamental protection mechanism against replay attacks. By default, Hawk uses a time window of 1 minute and this value is stored in the ClockSkewSeconds property of the Thinktecture.IdentityModel.Hawk.Core.Options class. This basically means the replay window is 2 minutes (accounting +1 and -1). If the server and the client clocks are reasonably in sync, ClockSkewSeconds can be set to a much lower value, say 10 seconds and that will bring the window of opportunity for replay to a very small time period.

If even that tiny window is unacceptable for you, you can now let Thinktecture.IdentityModel.Hawk store the nonce values and validate the same to detect replays. This feature is supported starting version 2.1.1. The great thing about nonce storage from the Hawk standpoint is that the time stamp in the request obviates the need to store huge amount of nonce values. By making the time window small, we will need to store the nonce values received only within that time window. Older nonce values can be simply evicted from the nonce storage, since time stamp check will reject those old requests anyways.

Nonce storing behavior is the default behavior now and if you want to disable it, you have to opt out by setting the Func delegates of the Options class like this.

var options = new Options()
{
    ClockSkewSeconds = 15,
    LocalTimeOffsetMillis = 0,
    // Other settings here,

    // Always return null indicating nonce is fresh
    DetermineNonceReplayCallback = (nonce) => null,

    // Do not store nonce
    StoreNonceCallback = (nonce, id, life) => { }
};

app.UseHawkAuthentication(new HawkAuthenticationOptions(options));

Out of box, Thinktecture.IdentityModel.Hawk uses System.Runtime.Caching.MemoryCache to store nonce values. This will not prevent replays when the server is scaled out through multiple process instances or load-balanced servers. But by using the DetermineNonceReplayCallback and StoreNonceCallback, you will be able to plugin any key value store of your choice or even use SQL server so that all instances can use a common store.

To use Redis, for example, here are the steps.

First of all, get Redis using the redis-64 NuGet package. Since I use a x64 Windows, I’m getting Redis-64, BTW.

Go to packages\Redis-64.* folder. Edit redis.windows.conf and locate the commented line #requirepass, uncomment it, and change it to include a strong password, like this.

requirepass th@nk$f()rflyin6H@wK!

Open a command window as admin and go to packages\Redis-64.* folder and run this command.

redis-server --service-install redis.windows.conf --loglevel verbose

If you open services.msc, you will see that “Redis” service is installed. It will not be running at this point and you can just start it now.

Add another NuGet package StackExchange.Redis to the project where you are using Hawk server side and create a class like this.

using StackExchange.Redis;

public class RedisNonceStore
{
    private static Lazy<ConnectionMultiplexer> conn = new Lazy<ConnectionMultiplexer>(() =>
    {
        var config = new ConfigurationOptions
        {
            EndPoints =
            {
                { "localhost", 6379 } // Default port is 6379
            },
            Password = "th@nk$f()rflyin6H@wK!"
        };

        return ConnectionMultiplexer.Connect(config);
    });

    public static ConnectionMultiplexer Connection
    {
        get
        {
            return conn.Value;
        }
    }

    internal static void StoreNonce(string nonce, string id, int lifeSeconds)
    {
        Connection
            .GetDatabase()
            .StringSet(nonce, id, expiry: TimeSpan.FromSeconds(lifeSeconds));
    }

    internal static string GetLastUsedId(string nonce)
    {
        return Connection.GetDatabase().StringGet(nonce);
    }
}

Now, all that is remaining is to set the Options Func delegates.

var options = new Options()
{
    ClockSkewSeconds = 15,
    LocalTimeOffsetMillis = 0,
    // Other settings here,
    DetermineNonceReplayCallback = RedisNonceStore.GetLastUsedId,
    StoreNonceCallback = RedisNonceStore.StoreNonce
};

app.UseHawkAuthentication(new HawkAuthenticationOptions(options));

With that, issue a few requests to your server and capture the traffic in Fiddler. As soon as the request is captured and you see the response available, press R to replay the request. You will see that the replayed request is getting rejected with a 401. On the other hand, if you disable the nonce storage, replay will work. If you keep pressing R at some point, once the time period as defined by ClockSkewSeconds elapses, you will start to get 401.

Finally, if you do not want Redis any more, you can just uninstall the service like this and you can see that “Redis” service is not listed anymore in services.msc.

redis-server --service-uninstall
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s