OWIN Authentication Middleware for Hawk in Thinktecture.IdentityModel.45

This is continuation of my previous post Basic Authentication with ASP.NET Web API Using OWIN Middleware, where I implemented HTTP basic authentication in a custom OWIN middleware class AuthenticationMiddleware that derives from the OwinMiddleware class. However, when it comes to implementing authentication in an OWIN middleware, the recommended approach is to use the authentication micro-framework that Katana has and derive from the out-of-box middleware class AuthenticationMiddleware<T>. Of course, this special authentication middleware also derives from OwinMiddleware, like so.

namespace Microsoft.Owin.Security.Infrastructure
{
   public abstract class AuthenticationMiddleware<TOptions>
               : OwinMiddleware
                    where TOptions : AuthenticationOptions
   {
      public AuthenticationMiddleware(OwinMiddleware next,
                                           TOptions options);

      public TOptions Options { get; set; }

      protected abstract AuthenticationHandler<TOptions> CreateHandler();

      public override Task Invoke(OwinRequest request,
                                      OwinResponse response);
   }
}

In this post, I implement Hawk authentication using this micro-framework by deriving from the above AuthenticationMiddleware<TOptions>. Thinktecture.IdentityModel.45 already has support for implementing Hawk authentication and I need to just call the AuthenticateAsync method of the HawkServer class from the middleware.

So, I have the following requirements. First up, I need to be able to read all parts of the request message – URI, headers, and most importantly the request body. Hawk authentication allows a client to send ‘hash’ field in the HTTP Authorization header in hawk scheme. To validate the payload integrity, ‘hash’ must be compared with the hash computed on the request body. Similarly, for the response, I need to be able to read all parts of the response message, including the response body to generate the ‘hash’ field in Server-Authorization header. While doing this in an ASP.NET Web API message handler is simple and straight forward, with OWIN middleware, it involves a bit more work of buffering the streams that represent the request and response bodies but let’s get on with the work.

The first step is to create the options class. AuthenticationOptions is the base class, the type argument of the generic class AuthenticationMiddleware<T> is constrained to derive from. I define a property of type Thinktecture.IdentityModel.Http.Hawk.Core.Options in this class so that Hawk options are available where I need them.

public class HawkAuthenticationOptions : AuthenticationOptions
{
    public HawkAuthenticationOptions(Options hawkOptions)
                                  : base(HawkConstants.Scheme)
    {
        this.HawkOptions = hawkOptions;
    }

    public Options HawkOptions { get; set; }
}

Next, is to derive from AuthenticationMiddleware like so. In the CreateHandler method, I just return an instance of HawkAuthenticationHandler (this is not the Web API message handler, though it has the same name).

public class HawkAuthenticationMiddleware
               : AuthenticationMiddleware<HawkAuthenticationOptions>
{
    public HawkAuthenticationMiddleware(
                           OwinMiddleware next,
                              IAppBuilder app,
                                  HawkAuthenticationOptions options)
        : base(next, options) { }

    protected override AuthenticationHandler<HawkAuthenticationOptions>
                                   CreateHandler()
    {
        return new HawkAuthenticationHandler();
    }
}

HawkAuthenticationHandler derives from AuthenticationHandler<T> and is the business end where most of the action happens. The overridden AuthenticateCore method does all the authentication work, as the name suggests. Here is the first-cut version of the handler class sans Hawk specific code, for you to get a feel of how the handler class is structured.

public class HawkAuthenticationHandler
                      : AuthenticationHandler<HawkAuthenticationOptions>
{
    private HawkServer server = null;

    protected async override
                        Task<AuthenticationTicket> AuthenticateCore()
    {
        // Do all your authentication work here
        bool isAuthentic = true;
        if(isAuthentic)
            return new AuthenticationTicket(
                           principal.Identity as ClaimsIdentity,
                               (AuthenticationExtra)null);
        else
            return new AuthenticationTicket(null,
                                 (AuthenticationExtra)null);
    }

    protected override async Task ApplyResponseChallenge()
    {
        // Add response headers such as WWW-Authenticate
    }
}

In the AuthenticateCore method, I do the actual authentication and return AuthenticationTicket object with the identity, if authentication is successful. So, for implementing Hawk authentication using the Hawk core classes from Thinktecture.IdentityModel.45, the the AuthenticateCore method and the ApplyResponseChallenge method will be like this.

protected async override Task<AuthenticationTicket> AuthenticateCore()
{
    IRequestMessage requestMessage = new OwinRequestMessage(Request);

    server = new HawkServer(requestMessage, Options.HawkOptions);

    var principal = await server.AuthenticateAsync();

    if (principal != null && principal.Identity.IsAuthenticated)
    {
        return new AuthenticationTicket(
                         principal.Identity as ClaimsIdentity,
                             (AuthenticationExtra)null);
    }

    return new AuthenticationTicket(null,
                             (AuthenticationExtra)null);
}

protected override async Task ApplyResponseChallenge()
{
    IResponseMessage responseMessage = 
                             new OwinResponseMessage(Response);

    var header = await server.CreateServerAuthorizationAsync(
                                                responseMessage);

    if (header != null)
        responseMessage.AddHeader(header.Item1, header.Item2);
}

IRequestMessage and IResponseMessage are the abstractions the Hawk core classes depend on. The abstraction is needed because the same set of Hawk core classes will need to work when called from OWIN middleware (OwinRequest/OwinResponse) as well as the ASP.NET Web API message handler (HttpRequestMessage/HttpResponseMessage).

Now, the complications with respect to reading the message body streams.

First of all, the assumption I make is that the client sends ‘hash’ field in the request only for the buffered requests. For streamed requests, ‘hash’ is not sent in. Similarly, in the response, ‘hash’ field is set in the Server-Authorization header only for buffered responses. With that assumption, I copy the request stream onto a MemoryStream, if the request contains the ‘hash’ field, for which the extension method IsPayloadHashPresent on OwinRequest returns true. For the response part, if the ResponsePayloadHashabilityCallback returns true, I change the response body to MemoryStream on the way in and switch the stream back on the way out. For writing the memory stream back into the original network stream, the TeardownCore method is used. So, here is the complete handler.

public class HawkAuthenticationHandler
                    : AuthenticationHandler<HawkAuthenticationOptions>
{
    private HawkServer server = null;
    private Stream stream = null;
    private MemoryStream requestBuffer = null, responseBuffer = null;

    protected async override
                       Task<AuthenticationTicket> AuthenticateCore()
    {
        if(Request.IsPayloadHashPresent())
        {
            // buffer the request body
            requestBuffer = new MemoryStream();
            await Request.Body.CopyToAsync(requestBuffer);
            Request.Body = requestBuffer;
        }

        IRequestMessage requestMessage = 
                                 new OwinRequestMessage(Request);

        server = new HawkServer(requestMessage, Options.HawkOptions);

        var principal = await server.AuthenticateAsync();

        if (principal != null && principal.Identity.IsAuthenticated)
        {
            var callback = Options.HawkOptions
                                 .ResponsePayloadHashabilityCallback;
            if (callback != null && callback(requestMessage))
            {
                // Buffer the response body
                stream = Response.Body;
                responseBuffer = new MemoryStream();
                Response.Body = responseBuffer;
            }

            return new AuthenticationTicket
                         (principal.Identity as ClaimsIdentity,
                              (AuthenticationExtra)null);
        }

        return new AuthenticationTicket(null,
                                     (AuthenticationExtra)null);
    }

    protected override async Task TeardownCore()
    {
        if (responseBuffer != null)
        {
            responseBuffer.Seek(0, SeekOrigin.Begin);
            await responseBuffer.CopyToAsync(stream);
        }
    }

    protected override async Task ApplyResponseChallenge()
    {
        IResponseMessage responseMessage = 
                             new OwinResponseMessage(Response);

        var header = await server.CreateServerAuthorizationAsync(
                                                  responseMessage);

        if (header != null)
            responseMessage.AddHeader(
                                header.Item1, header.Item2);
    }
}

With the middleware and the handler in place, we need to plug them into the pipeline and that can be done using an extension method, as done in Katana for other authentication mechanisms.

public static IAppBuilder UseHawkAuthentication(
                             this IAppBuilder app,
                                  HawkAuthenticationOptions options)
{
    app.Use(typeof(HawkAuthenticationMiddleware), app, options);
    app.UseStageMarkerAuthenticate();
    return app;
}

I’ll use OWIN self-hosted ASP.NET Web API to test our middleware. Here is the Startup. The ResponsePayloadHashabilityCallback callback in Options blindly returns true here because I do not use streaming responses. Ideally, the request object passed in can be inspected to determine the URI, HTTP method, etc to determine if response body hash must be included in Server-Authorization.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var credentialStorage = new List<Credential>()
        {
            new Credential()
            {
                Id = "dh37fgj492je",
                Algorithm = SupportedAlgorithms.SHA256,
                User = "Steve",
                Key = "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn"
            }
        };

        var options = new Options()
        {
            ClockSkewSeconds = 60,
            LocalTimeOffsetMillis = 0,
            ResponsePayloadHashabilityCallback = (r) => true
            CredentialsCallback = (id) => 
                         credentialStorage.FirstOrDefault(
                                               c => c.Id == id)
        };

        app.UseHawkAuthentication(
                           new HawkAuthenticationOptions(options));

        var config = new HttpConfiguration();

        config.Routes.MapHttpRoute(
            "DefaultWebApi",
            "{controller}/{id}",
            new { id = RouteParameter.Optional });

        app.UseWebApi(config);
    }
}

Here is the Main method.

class Program
{
    static void Main(string[] args)
    {
        const string baseUrl = "http://localhost:12345/";

        using (WebApp.Start<Startup>(new StartOptions(baseUrl)))
        {
            Console.WriteLine("Press Enter to terminate.");
            Console.Read();
        }
    }
}

Here is the ApiController class.

[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get()
    {
        return Request.CreateResponse<string>(
                 HttpStatusCode.OK, "Hello, " + User.Identity.Name);
    }

    public HttpResponseMessage Post([FromBody]string name)
    {
        string message = String.Format(
                            "Hello, {0}. Thanks for flying Hawk",
                                 name);
        return Request.CreateResponse<string>(
                                  HttpStatusCode.OK, message);
    }
}

Finally, the client application, which is also a Console App. The RequestPayloadHashabilityCallback in ClientOptions also blindly returns true for the ‘hash’ field to be included in HTTP Authorization header but you can inspect the request and return true only if your request body is buffered.

class Program
{
    static void Main(string[] args)
    {
        string uri = "http://localhost:12345/values";

        var credential = new Credential()
        {
            Id = "dh37fgj492je",
            Algorithm = SupportedAlgorithms.SHA256,
            User = "Steve",
            Key = "werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn"
        };

        // GET and POST using the Authorization header
        var options = new ClientOptions()
        {
            CredentialsCallback = () => credential,
            RequestPayloadHashabilityCallback = (r) => true
        };

        var handler = new HawkValidationHandler(options);

        HttpClient client = HttpClientFactory.Create(handler);

        var response = client.GetAsync(uri).Result;
        Console.WriteLine(response.Content.ReadAsStringAsync().Result);

        response = client.PostAsJsonAsync(uri, credential.User).Result;
        Console.WriteLine(response.Content.ReadAsStringAsync().Result);

        Console.Read();
    }
}

The source code corresponding to this blog post is part of the Hawk sample in Thinktecture.IdentityModel.45. Once the OWIN related NuGet packages are moved to stable versions from the current pre-release state, this middleware will become part of Thinktecture.IdentityModel.45.


Acknowledgements: A big thanks to both Dominick Baier and Pedro Felix for all the help and guidance. Thanks to Chris Ross (Tratcher) for answering my questions in Stack Overflow and through email. Thanks also to Kiran Challa for answering my question in Stack Overflow.


Advertisements

6 thoughts on “OWIN Authentication Middleware for Hawk in Thinktecture.IdentityModel.45

  1. Can you please create an issue in github (thinktecture identity model) so that I don’t lose track? I’ll update it as soon as I can. Thanks.

  2. Thanks for your article however your HawkAuthenticationHandler in “Thinktecture.IdentityModel.45-master > Samples > Hawk > Hawk.Samples.WebApi.Owin” no longer compiles as soon I install package Thinktecture.IdentityModel 3.6.1. As I can see you are using Thinktecture.IdentityModel.45 version 3.6.1.0. Although there is an entry for this package in nuget store but it also warns : “The owner has unlisted this package. This could mean that the package is deprecated or shouldn’t be used anymore.”. Could you please update the code to use the latest Thinktecture.IdentityModel 3.6.1 rather than the Thinktecture.IdentityModel.45 or give me a hint of what I am doing wrong.

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