Writing an Owin Authentication Middleware

Owin and Katana offers a flexible pipeline for external authentication with existing providers for authentication by Google, Facebook, Twitter and more. It is also possible to write your own custom authentication provider and get full integration with the Owin external authentication pipeline and ASP.NET Identity.

Anatomy of an Owin Authentication Middleware

For this post I’ve created a dummy authentication middleware that interacts properly with the authentication pipeline, but always returns the same user name. From now on I will use the names from that dummy for the different classes.

A typical Katana middleware is made up of 5 classes.

  1. The main DummyAuthenticationMiddleware class.
  2. The internal DummyAuthenticationHandler class doing the actual work.
  3. A DummyAuthenticationOptions class for handling settings.
  4. An extension method in DummyAuthenticationExtensions for easy setup of the middleware by the client application.
  5. An simple internal Constants class holding constants for the middleware.

The DummyAuthenticationExtensions class

I’ll go through the classes in the order that they are called by an application using them. In the Startup.Auth.cs file of my sample mvc application there’s a call to register the dummy authentication Owin middleware.

app.UseDummyAuthentication(new DummyAuthenticationOptions("John Doe", "42"));

The UseDummyAuthentication extension method is just a one line wrapper to make registration of the middleware convenient.

return app.Use(typeof(DummyAuthenticationMiddleware), app, options);

The IAppBuilder.Use method takes an object as the first parameter, which has the advantage that a middleware won’t need to have a reference to a particular assembly containing a specific middleware interface. But using an untyped object makes it more confusing for callers. This is where the extension method comes into the play, it makes it easy to register the middleware in the Owin pipeline.

The DummyAuthenticationOptions class

Any middleware will need some kind of configurations. It is stored in an options class. To work with the Katana base classes, it must inherit from Microsoft.Owin.Security.AuthenticationOptions that contains some mandatory options that are used by the framework.

The DummyAuthenticationMiddleware class

This is the actual middleware registered with the owin application. The middleware is a factory that creates instances of the handler that does the actual work of processing requests. Only one instance of the middleware is instantiated. The middleware constructor checks the configuration in the options class and if certain properties are null, it adds some defaults that are dependent on the previous middlewares in the pipeline. At first sight it looks a bit odd – why not set the defaults in the options constructor? The reason is that the options constructor has no access to the current IAppBuilder instance, but that is available in the middleware constructor.

The core method of the middleware class is the CreateHandler factory method that creates an instance of the DummyAuthenticationHandler.

protected override AuthenticationHandler<DummyAuthenticationOptions> CreateHandler()
{
  return new DummyAuthenticationHandler();
}

The DummyAuthenticationHandler class

Finally we’ve come to the class doing the actual work, the DummyAuthenticationHandler.

The dummy authentication middleware I’ve implemented is a passive one, which means that it doesn’t do anything to the incoming requests until asked so by the presence of a AuthenticationResponseChallenge. (See my previous post Understanding the Owin External Authentication Pipeline for details).

The first method to be invoked on the handler is the ApplyResponseChallengeAsync method. It will be called for all requests after the downstream middleware have been run. It is activated if two conditions are true:

  • The status code is 401
  • There is an AuthenticationResponseChallenge for the authentication type of the current middleware.

If both of these conditions are true, the dummy middleware will change the response to a redirect to the callback path. If this was a real authentication middleware, it would instead be a redirect to the external authentication provider’s authentication page.

protected override Task ApplyResponseChallengeAsync()
{
  if (Response.StatusCode == 401)
  {
    var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
 
    // Only react to 401 if there is an authentication challenge for the authentication
    // type of this handler.
    if (challenge != null)
    {
      var state = challenge.Properties;
 
      if (string.IsNullOrEmpty(state.RedirectUri))
      {
        state.RedirectUri = Request.Uri.ToString();
      }
 
      var stateString = Options.StateDataFormat.Protect(state);
 
      Response.Redirect(WebUtilities.AddQueryString(Options.CallbackPath.Value, "state", stateString));
    }
  }
 
  return Task.FromResult<object>(null);
}

The handler also monitors all incoming requests to see if it is a request for the callback path, by overriding the InvokeAsync method.

public override async Task<bool> InvokeAsync()
{
  // This is always invoked on each request. For passive middleware, only do anything if this is
  // for our callback path when the user is redirected back from the authentication provider.
  if(Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path)
  {
    var ticket = await AuthenticateAsync();
 
    if(ticket != null)
    {
      Context.Authentication.SignIn(ticket.Properties, ticket.Identity);
 
      Response.Redirect(ticket.Properties.RedirectUri);
 
      // Prevent further processing by the owin pipeline.
      return true;
    }
  }
  // Let the rest of the pipeline run.
  return false;
}

If the path is indeed the callback path of the authentication middleware, the AuthenticateAsync method of the base class is called. It ensures that some lazy loaded properties of the base class are loaded and then calls AuthenticateCoreAsync. This is where a real handler would inspect the incoming authentication ticket from the external authentication server. The dummy middleware just creates an identity with the values from the configuration.

protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
  // ASP.Net Identity requires the NameIdentitifer field to be set or it won't  
  // accept the external login (AuthenticationManagerExtensions.GetExternalLoginInfo)
  var identity = new ClaimsIdentity(Options.SignInAsAuthenticationType);
  identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, Options.UserId, null, Options.AuthenticationType));
  identity.AddClaim(new Claim(ClaimTypes.Name, Options.UserName));
 
  var properties = Options.StateDataFormat.Unprotect(Request.Query["state"]);
 
  return Task.FromResult(new AuthenticationTicket(identity, properties));
}

Experiencing Writing Middleware

Writing an authentication middleware for the Katana framework is quite simple, once all the details of how the authentication pipeline works are sorted out. That’s why I wrote these blog posts, to document my research on what Owin is, how the authentication pipeline works, how it interacts with ASP.NET Identity and finally what building blocks that are needed for creating an authentication middleware.

The source code for the dummy authentication middleware is available on my github acccount under a permissive MIT license, so feel free to use it as a starter kit for your own authentication middleware.

This post is part of the Owin Authentication series.<< Understanding the Owin External Authentication PipelineUsing Owin External Login without ASP.NET Identity >>

  • Sohel on 2015-07-13

    Great post!! Extremely helpful. I just have one question. If the external authentication provider could not authenticate a user, i.e returns unsuccessful, how to you suggest we handle that. At point we possibly want to display some message and redirect back to the login screen. Could you recommend how to handle this situation?

    Thanks.

    • Anders Abel on 2015-07-22

      Unfortunately I don’t have a good answer to that. Looking at the source of the existing Katana middleware they don’t pass any errors back and I haven’t found a good way to do it. Hopefully this is one of the things that will be improved in ASP.NET 5, where the middleware are more integrated into the framework rather than bolted-on as is the case with OWIN.

  • Sous on 2015-07-22

    Very Good post thank you! I am implementing a custom authentication provider and I am struggling to implement and integrate the custom login page with my custom OWIN middleware, do you have any idea on how this can be done? thank you

    • Anders Abel on 2015-07-23

      Implementing an actual UI in a middleware would require that you use some kind of view rendering engine. I haven’t done it myself, but I’d recommend that you look at (Thinktecture) Identity Server 3 or HangFire as they have obviously done that.

  • Sohel on 2015-08-10

    I had issues running your example under local iis. I get HTTP Error 404.0 – Not Found error when it tries to redirect to the CallbackPath — Response.Redirect(WebUtilities.AddQueryString(Options.CallbackPath.Value, “state”, stateString));

    The Detail error looks like this:

    Module IIS Web Core
    Notification MapRequestHandler
    Handler StaticFile
    Error Code 0x80070002
    Requested URL http://localhost:80/signin-EPExternal?state=7KDSV7E3SkZGGEwntMMhxqMKEk9J7jiDXj5Q-3eFFab7LVHAM9tBSt3SbKQODBWWHQm6cirMgIH5xjZ3WXDBQcvQYzAVz_Rywekxdc6PCvbGaiIN9lPeIs8a9LXqQC7BLVgxcl5uLxXpPegaPFWblQ
    Physical Path C:\inetpub\wwwroot\signin-EPExternal
    Logon Method Anonymous
    Logon User Anonymous

    Everything, however, works like charm when I run under “IIS Express”.

    Any idea what do i need to do to run under IIS?

    Thanks.

  • Dong Nguyen on 2015-10-19

    Hi Abel,
    I have problem
    – create 2 project mvc in my pc (local, difference DB).
    – register same email in 2 project.
    – (1) login on project 1 ok => go to changepassword page ok….
    – (2) login on project 2 ok => go to changepassword page ok
    – go back project 1, press F5, redirect to login page … I login => ok
    – go back project 2, press F5, redirect to login page … I login => ok

    I do’nt known why diffrence project web…why i can’t use same email to login in 2 project at same time.

    Can you help me??

    • Anders Abel on 2015-10-20

      If you’re running them on the same time on your local machine they will read each others cookies which makes things really messed up. To avoid that, you would have to configure them to use different cookie names (can be set in the options to the cookie middleware).

  • Dawid on 2016-08-26

    Thanks a million!

    I’ve been struggling with this for way too long, but persistence led me to your blog, which led me to a solution.

  • SG on 2017-03-09

    This whole authentication pipeline is typical microsoftish convoluted nonsense. And undocumented as well. Engineering nightmare.

  • Leave a Reply

    Your name as it will be displayed on the posted comment.
    Your e-mail address will not be published. It is only used if I want to get in touch during comment moderation.
    Your name will be a link to this address.
Software Development is a Job – Coding is a Passion

I'm Anders Abel, a systems architect and developer working for Kentor in Stockholm, Sweden.

profile for Anders Abel at Stack Overflow, Q&A for professional and enthusiast programmers

Code for most posts is available on my GitHub account.

Popular Posts

Archives

Series

Powered by WordPress with the Passion for Coding theme.