As some of you might have noticed, I really like OWIN. I like the simplicity, and the extremely powerful things that you can do with it in a simple way. And the fact that I don’t have to create an IHttpModule implementation, and figure out the ASP.NET event to hook into, like I had to to do the same thing before OWIN.
Katana, Microsoft’s implementation of OWIN, also offers a standardized way to handle authentication. And it is really easy to use, and not too hard to extend to work with your own identity providers. However, being me, I want to know how it works “under the hood”, and not just read a “how to build an authentication middlware” blog post…
Remember, knowing how things work “under the hood”, or “under the bonnet” if you speak the Queens English, makes it possible to do more things than just use it. By knowing how a combustion engine works (under the hood/bonnet of your car), makes it possible to add a turbo or two to it, or a compressor, or at maybe tweak the fuel consumption and horse power you get from it. But let’s leave the car analogies and look at Katana authentication middleware.
Building, and using, authentication middleware means integrating with a few baseclasses, and obviously specific implementations for the authentication provider you choose. The baseclasses are all located in the assembly Microsoft.Owin.Security, in the namespace Microsoft.Owin.Security.Infrastructure, which is available in the NuGet package with the same name.
The classes we are interested in are the AuthenticationMiddleware<TOptions>, which inherits from OwinMiddleware, and the AuthenticationHandler and its subclass AuthenticationHandler<TOptions>. AuthenticationHandler<TOptions> actually does very little except hard type the options used by the handler.
Most information you find about Katana authentication middleware will only explain how to inherit these classes to get the desired behavior. Something that this post will also do…at the end… But I want to dig a little deeper as I am curious about how completely independent middleware can work together like they do, and enable an extendable authentication environment.
To be honest, is it mostly the CookieAuthenticationMiddleware that works together with the others, but seeing that CookieAuthenticationMiddleware is just another authentication middleware still means that completely separated middlewares manage to co-operate in some way…
Let’s start at the middleware end of things as that is what we plug in to the OWIN pipeline. The AuthenticationMiddleware<TOptions> is just another piece of OWIN middleware, and as such, its constructor takes the “standard” next middelware and “options” object. But being that AuthenticationMiddleware<TOptions> I generic, it types the options, and forces it to inherit from AuthenticationOptions.
Side note: The AuthenticationOptions baseclass contains 3 properties, AuthenticationType, AuthenticationMode and Description. AuthenticationType is the name of the authentication type, which is pretty much the “identifier”of it. AuthenticationMode defines whether the authentication is Active or Passive. Active means that it should authentication the user actively on each request, while Passive will wait until told to do so. (Active would be something like Windows authentication that can be performed “actively” without redirecting the user and so on). And finally the Description is used by an application that wants to look at what authentication methods are available.
Note: It is worth nothing that the Caption on the Description object is used by the default ASP.NET MVC templates when creating the list of external login providers. If the caption is not set, the provider will not be shown in the list…
Being that AuthenticationMiddleware<TOptions> inherits from OwinMiddleware, it is required to override the Invoke() method. In this override, it creates a new AuthenticationHandler<TOptions> by calling the abstract method CreateHandler(). So the implementing class will override this, and return a suitable AuthenticationHandler<TOptions>.
Once it has an authentication handler instance, it initializes the handler using some internal stuff. The internal stuff is actually quite interesting though, and if we just ignore that some of it is done in AuthenticationHandler<TOptions> and some of it in its baseclass, this is what is being done (simplified).
First, the options object is stored in a protected property so that we can access it from our sub class. So is the Owin context, together with a few other protected properties. The only one really worth noting is the one called Helper though. The Helper property holds an object that can help out with some of the things our handle needs to do, for example set the current identity and verify a challenge request has been issued for our handler (more about that later).
Next, it hooks the handler into 2 things. First, it uses the IOwinRequest.RegisterAuthenticationHandler() method to “add the handler to the system”. This can then be used by IOwinContext.Authentication to authenticate requests, as well as figure out what authentication middleware is available. Secondly it adds a callback to IOwinResponse.OnSendingHeaders(), which is called when headers are about to be sent out, which is the last point when we can change the request.
Once those hooks have been added, InitializeCoreAsync() is called. This makes it possible for our subclass to initialize itself if needed. Then it checks to see if the AuthenticationMode is set to Active. If it is, the handler is requested to authenticate the user straight away, which is done by calling AuthenticateCoreAsync (by way of the AuthenticateAsync method).
If AuthenticateCoreAsync returns an AuthenticationTicket with an Identity set, that identity is then set as the current user for the request. If not, the request just carries on unauthenticated.
Note: A handler with the AuthenticationMode set to Active will run on every request, setting up the user based on some form of evidence available in the request. It will not cause a cookie to be issued by the CookieAuthenticationMiddleware, and instead will expect the evidence used to do the authentication to be available in every request.
Once the handler has been initialized, the middleware calls InvokeAsync(), which needs to be overridden to actually do something. By default, it just returns false, which will cause the next middleware to be executed. However, if InvokeAsync() returns true, it stop the request execution and starts travelling “back out” the pipeline.
So, what should we be doing in InvokeAsync() considering that the default implementation doesn’t actually do anything? Well, generally, the handler will see if the current request is aimed at the configured callback path. If not, it will just return false, request execution will just carry on.
If the request really is aimed at the callback path, it calls AuthenticateAsync, which causes AuthenticateCoreAsync() to be called. Inside AuthenticateCoreAsync(), which we need to override, we should use the current request to figure out who the user is. Once that is done, we should return an AuthenticationTicket containing a ClaimsIdentity if the user was authenticated. The ClaimsIdentity’s AuthenticationType should be set to the AuthenticationType defined in the handlers options.
Note: Most authentication middleware has a SignInAsAuthenticationType property on the options object. If this is set, the middleware will sign in using another authentication type than the default for the current middleware. If this is set, the ClaimsIdentity will need to have its AuthenticationType replaced before being sent to SignIn().
Note 2: If SignInAsAuthenticationType isn’t set manually, the middleware will in most cases set this to IAppBuilder.GetDefaultSignInAsAuthenticationType(), causing it sign in as the default.
If the returned AuthenticationTicket includes an Identity, it indicates that the user has been authenticated, and we need to do something about it. And by “do something about it”, I mean that we should call IOwinContext.Authentication.SignIn() to sign in the user. Or rather prepare the system to sign in the user… SignIn() doesn’t acutally do anything but place an AuthenticationResponseGrant instance in the OWIN environment dictionary.
If the authentication fails for some reason, InvokeAsync() should handle this in some way. Most likely by setting a corresponding HTTP status code, or set up a redirect, and return true to cause the execution of the request to stop.
As the request is turning back around, either through InvokeAsync() returning true, or by the next middleware returning, the handler is “torn down”, which causes 3 things to happen.
First it calls ApplyResponseAsync()/ApplyResponseCoreAsync, which I will get back to in a little while. After that, it calls TeardownCoreAsync() allowing us to remove anything that isn’t needed anymore. And finally it unregisters the handler using Request.UnregisterAuthenticationHandler().
One thing to note is that if InvokeAsync() returns true, and the request is “short circuited”, nothing is actually written to the response. It is up to the ApplyResponseCoreAsync(), which is called in the teardown of the handler, to do that if needed.
So what should we be doing in the ApplyResponseCoreAsync()? Well, normally we shouldn’t even override it as the default implementation will delegate the call to 2 other methods that we are more interested in. At least one of them…
First it calls ApplyResponseGrantAsync() and then it calls ApplyResponseChallengeAsync(). In most cases, we can ignore ApplyResponseGrantAsync(). This is used by for example the CookieAuthenticationMiddleware to take the AuthenticationResponseGrant that we added during user authentication, and turn that into a cookie that can be used in subsequent calls to authenticate the user.
Side note: And there it is! The way the middlewares communicate. The individual authentication middleware puts defined data in the Owin environment dictionary using IOwinContext.Authentication.SignIn() for example, and other middlewares can then pick up that data and act on it.
Side note 2: This is why the CookieAuthenticationMiddleware should always be registered before all other authentication middlewares in the OWIN pipelinet. It needs to be able to look at the authentication responses that the other middlewares have added to the enrionment, and turn it in to a cookie (or remove the cookie) if needed. And this means it needs to be the last authentication middleware to look at the response going out through the pipeline, and thus be the first registered.
Back to the ApplyResponseChallengeAsync() method, which we actually need to care about. Inside this method, we should check the response to see if it has a 401 status code. If it does, something further down the pipeline has said that the user is unauthorized, and needs to be authenticated. In that case, we need to see if there is an AuthenticationResponseChallenge placed in the OWIN Environment dictionary, and whether or not that challenge defines the current authentication method.
Tip: This is quite easily done by calling Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode)
If there is such a challenge, with the correct authentication method defined, the handler should find a way to authenticate the user. Normally this means redirecting the user to some identity provider, which will then redirect the user back once the authentication has been performed.
Note: It is customary to use a correlation id to disable CSRF when doing OAuth2 for example. This correlation id is easily created using GenerateCorrelationId() method that is defined in the AuthenticationHandler baseclass. This method also has a corresponding ValidateCorrelationId() that can be used to verify the correlation id in the AuthenticateCoreAsync() when the response from the identity provider is evaluated.
Remember that ApplyResponseAsync(), and subsequently ApplyResponseGrantAsync() and ApplyResponseChallengeAsync(), will also be called once when the headers are being written to the client due to the AuthenticationHandler hooking up a callback using Response.OnSendingHeaders() during the initialization phase. This makes sure that all handlers have a chance to create a challenge, or handle a response grant, right before the headers are sent to the user, which, as mentioned before, is the last point in time that we can modify the response. At least modify it by returning a redirect if needed…
Ok, that is pretty much all you need to know!
So, what do we need to do to create a new authentication middleware to support some new identity provider? Well, if we ignore the somewhat deep walkthrough above, and just do the quick walkthrough, it looks like this.
First we need to create an authentication middleware that inherits from AuthenticationMiddleware<TOptions>, as well as an options class that inherits from AuthenticationOptions. Inside the new middleware’s constructor, we need to check the passed in options, and set any defaults that we might want to use.
Note: One thing we might want to set, unless already set by the client, is the AuthenticationOptions.Description.Caption. This is, as mentioned before, the caption that will be displayed by an application that lists the available authentication options.
Next, we need to override the CreateHandler() method, and implement it so that it creates a new handler and returns it.
Obviously we need to create this handler as well… The handler needs to be a class that inherits from AuthenticationHandler<TOptions>, with the TOptions generic parameter being the same as the one used by the newly created middleware.
Inside the new handler, we need to override at least 3 things. First we need to override InvokeAsync(). Inside this override, we need to check if Request.Path is the same as the defined callback path. Normally this callback path is added to the options so that the client can configure it so it doesn’t collide with something else.
Note: The callback path is purely “virtual”. It will never have an MVC route or anything backing it. It is just for the authentication middleware to figure out that a request is actually the identity provider passing back information about an authenticated user.
If the Request.Path is not equal to the callback path, we just return false and let the request go on through the pipeline. Otherwise we need to call AuthenticateAsync() to get hold of an AuthenticationTicket. If the ticket contains an identity, the user has been properly authenticated, and we should sign in the user using IOwinContext.Authentication.SignIn() before returning true. If the ticket does not contain an identity, something has gone wrong and we need to handle that by redirecting the user or something similar.
Next we need to override AuthenticateCoreAsync(). In the override we need to look at the current request to figure out who the user is. The callback generally includes data from the identity provider in some form. This data should be verified, a ClaimsIdentity with the corresponding information should be created, and it should be returned inside an AuthenticationTicket.
Finally we need to override ApplyResponseChallengeAsync(). In this method we need to check to see if the response has a status code of 401 (Unauthorized). If that is the case, we use Helper.LookupChallenge() to see if there is a AuthenticationResponseChallenge in the environment, and if it requests the current authentication handler to cause the user to log in.
If that is the case, we create a redirect URL to the identity provider, including all required config values needed (retrieved from the options object), and redirect the user there. This URL generally includes a return URL, which obviously should correspond to the callback path checked in InvokeAsync().
That is actually all there is to building a new piece of authentication middleware. At least if it is a Passive one. And one that relies on the CookieAuthenticationMiddleware (or something similar) to do the cookie stuff.
Note: You might want to create a nice IAppBuilder extension method to handle the registration as well… But that isn’t really necessary if you don’t want to. But it looks better to write app.UseCustomAuthentication(myOptions) than app.Use<CustomAuthenticationMiddleware>(myOptions)…
If you want to build something like the CookieAuthenticationMiddleware that handles more things, like sign out etc, it becomes more complicated. As well as if you need to implement single sign out, like the WsFederation authentication middleware does. Luckily, most of these things have already been implemented by Microsoft in some form in other authentication handlers that can be used to get inspiration. And as it is all open source now, we can just go to CodePlex and see how they have solved those more complicated scenarios.
That’s it for this time! I hope it has given a bit more of an insight into how Katana authentication middlewares work, and what you need to do if you have a custom identity provider that you need to integrate with.