Owin Authentication series
- What’s this Owin Stuff About?
- ASP.NET Identity and Owin Overview
- Understanding the Owin External Authentication Pipeline
- Writing an Owin Authentication Middleware
- Using Owin External Login without ASP.NET Identity
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.
- The main
DummyAuthenticationMiddleware
class. - The internal
DummyAuthenticationHandler
class doing the actual work. - A
DummyAuthenticationOptions
class for handling settings. - An extension method in
DummyAuthenticationExtensions
for easy setup of the middleware by the client application. - 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.
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.
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.
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
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.
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.
It’s probably RAMMFAR that is missing. Without the http module providing support for the OWIN pipeline is not invoked on all requests. Ensure that you have this info in your web.config:
Thanks for your reply. Unfortunately it did not fix the problem. For some reason, when running under localhost, Request.Path is never the Options.CallbackPath so the following condition is never true.
public override async Task 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)
I just took your sample, and changed the property of your sample client to run under local IIS, clicked the button to create the Virtual directory and ran and get error.
When i change it back to run under IIS Express and ran, it works.
Maybe be you can try the same and see if you run into the same issue?
Thanks again for your help.
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??
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).
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.
This whole authentication pipeline is typical microsoftish convoluted nonsense. And undocumented as well. Engineering nightmare.
Great article! I was so glad when I finally found this. I was struggling to a sort out how authentication worked by reading the AspNetKatana source, and I couldn’t find a decent resource to help me write my own Middleware. Your article cleared everything up, and I have a solution working. Thanks for writing this.
I read the middleware code, and did not find anything related to the Url of external authentication provider. I wonder how the authentication manager know where is the external login page.
Do I miss something?
The dummy authentication middleware focus on how the Owin pipeline works. It never redirects to an external provider. In a real scenario it would be done in the
ApplyResponseChallengeAsync
method. In the example I’ve short-circuited it by redirecting directly to the callback. In a real world scenario that redirect would go to the external authentication provider which then redirects or posts a response to the callback path.For a .net 4.6 web app, Is there any way to achieve what UseWhen method does in asp.net core ? Conditionally execute one extra middleware and join back to main pipeline?
The need is this -> I am writing a new request logging middleware inheriting owinMiddleware. I want this to be first executed only for few APIs in my existing application & thus need to make it execute conditionally & I would like to write this code in startup instead of adding some code in the middleware itself.
Bloody good article. Do you know if it would be possible to extend “into” this middleware to create a method that will grab a new access token with refresh token?
I’ve written a class for doing so, but it’s totally separate. It works fine but the issue is it’s not possible to use any of the CSRF / security based stuff in the AuthenticationHandler class.