.NET Zone is brought to you in partnership with:

I'm a software developer working as a senior consultant at Kentor in Stockholm, Sweden. My core competence is as a technical specialist within development and system architecture. In my heart I am, and probably will remain, a programmer. I still think programming is tremendously fun, more than 20 years after I first tried it. That's why my blog is named Passion for Coding.  Anders is a DZone MVB and is not an employee of DZone and has posted 81 posts at DZone. You can read more from them at their website. View Full User Profile

Understanding the Owin External Authentication Pipeline

06.17.2014
| 2809 views |
  • submit to reddit

Owin makes it easy to inject new middleware into the processing pipeline. This can be leveraged to inject breakpoints in the pipeline, to inspect the state of the Owin context during authentication.

When creating a new MVC 5.1 project a Startup.Auth.cs file is added to the project that configures the Owin pipeline with authentication middleware. By two middleware for authentication are enabled through calls to app.UseCookieAuthentication() and app.UseExternalSignInCookie. There are also commented out sections for Microsoft, Twitter, Facebook and Google authentication. This post will use Google Authentication as an example and also add some “dummy” middleware that makes it possible to set breakpoints and inspect the authentication pipeline.

Inserting Breakpoint Middleware

The middleware is executed in the order they are listed in the file, so by inserting a simple middleware between the existing, it is possible to inspect how each middleware interact with the authentication pipeline.

The injected middleware is just a few lines of code, but it allows two breakpoints to be set: on the opening and closing braces, which enables inspection before and after the call to the next middleware.

app.Use(async (context, next) =>
{
  await next.Invoke();
});

I’ve added some debugging middleware and removed the unused commented out middleware initialization. This is the resulting startup function that will be used for the remainder of this post:

public void ConfigureAuth(IAppBuilder app)
{
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
 
    app.Use(async (Context, next) =>
        {
            await next.Invoke();
        });
 
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider
        {
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                validateInterval: TimeSpan.FromMinutes(30),
                regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
        }
    });
 
    app.Use(async (Context, next) =>
    {
        await next.Invoke();
    });
 
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
 
    app.Use(async (Context, next) =>
    {
        await next.Invoke();
    });
 
    app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
    {
        ClientId = "ABC.apps.googleusercontent.com",
        ClientSecret = "XYZ"
    });
 
    app.Use(async (Context, next) =>
    {
        await next.Invoke();
    });
}

Adding breakpoints allows for inspecting each step in the pipeline. This is what it looks like when I’ve just pushed the “Google” button on the login page of the application.
2014-06-17 15_54_12-IdentityTest (Debugging) - Microsoft Visual Studio
The break is right after the ExternalLogin action on the MVC AccountController has been invoked. We’re after the call to next.Invoke(), but before returning from this last “middleware” in the chain. TheExternalLogin action returns a custom ChallengeResult (also found in the AccountController.csfile) that when executed callscontext.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);. It is that method that sets the AuthenticationResponseChallenge shown on the picture above.

This is an important principle of the Owin authentication.

Components do not call each other directly. They put a message in theAuthenticationManager of the Owin context, which is inspected by other middleware in the pipeline.

Redirecting to Google

The status code of the response is now 401 and the AuthenticationResponseChallenge is set on theAuthenticationManager. Remember that each Owin middleware can inspect each request twice; before and after invoking the next middleware in the chain.

When the google authentication middleware inspects the outgoing response and finds that the status code is 401, it checks for an AuthenticationResponseChallenge with type “Google”. In this case it will find it and alter the response accordingly. When hitting a breakpoint at line 33, this is how the response looks like.
2014-06-17 16_21_09-IdentityTest (Debugging) - Microsoft Visual Studio
The Google authentication middleware has changed the outgoing response to become a redirect to the Google oauth service.

Getting the Return Value from Google

Google performs the authentication and redirects the user back with the authentication info in the query string. Stepping the code, the breakpoints on lines 42 and 44 are never hit. The Google authentication middleware found that it could process the request itself and never called the next middleware in the pipeline. Instead the breakpoint on line 33 shows that it changed the response to a 302 redirect and set theAuthenticationResponseGrant on the AuthenticationManager.
2014-06-17 16_37_17-IdentityTest (Debugging) - Microsoft Visual Studio
The grant has an AuthenticationType of ExternalCookie, which is what the next middleware in the pipeline is looking for.

Setting the External Cookie

The External cookie authentication middleware will set a cookie with the received identity. The cookie is encrypted and works very much the same way as the old forms auth cookie, except that it is not automatically read and used by the application.
2014-06-17 16_42_31-IdentityTest (Debugging) - Microsoft Visual Studio
The external cookie is used to remember the identity received from Google during the redirect back to theAccountController.ExternalLoginCallback() action.

Converting to Local Identity

The identity received from Google is an external identity. ASP.NET Identity is built around the concept of alocal identity that can have zero or more external logins. What the MVC controller does when it receives the external identity (through a call to AuthenticationManager.GetExternalLoginInfoAsync()) is to look up the local identity in ASP.NET Identity and issue a AuthenticationResponseGrant of typeApplicationCookie. This is the resulting response when inspected at line 44.
2014-06-17 16_53_06-IdentityTest (Debugging) - Microsoft Visual Studio
There is now both an AuthenticationResponseGrant of type ApplicationCookie but also anAuthenticationResponseRevoke of type ExternalCookie to get rid of the temporary external cookie.

The Final Application Cookie

Finally, by putting a breakpoint at line 9, it is possible to inspect the cookies set.
2014-06-17 16_57_14-IdentityTest (Debugging) - Microsoft Visual Studio
The external cookie is removed and the application cookie is set. The application cookie middleware will now find the application cookie on each request and unencrypt it, unserialize the contained claims identity and set on the request.

Comments

Through the use of messages on the AuthenticationManager the authentication pipeline is extremely decoupled. There are (barely) no hard dependencies between the different middleware or from the MVC controller on the actual middleware that performs the authentication. As far as I can tell, the AuthenticationManager and the messages held by it are not part of the Owin specification, they are part of Microsoft’s Katana implementation of Owin. So if you are using another implementation, don’t expect anything in this post to be true for that implementation.

The design with a separate external cookie makes it possible to add a translation layer and not use the external identity as it is. I think that it is also possible to use the external identity directly – using the ExternalCookie middleware is not hard coded into the authentication middleware. The middleware looks up the cookie middleware to use and that should be possible to set directly to be the application cookie (if you have tried, please leave a comment to let me know!).

Flexible architecture always come with some sort of penalty. In this case the external cookie setup requires an extra redirect, adding to the number of redirects happening when hitting the login button. In total it is three redirects without any real user feedback, which can be annoying on connections with high latency.

To summarize I think that the design is good and easy to configure with different middleware in different combinations. To write an own middleware (which is the subject for my next post) there are some more moving parts to keep track of.

Published at DZone with permission of Anders Abel, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)