Understanding the Owin External Authentication Pipeline

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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. The ExternalLogin action returns a custom ChallengeResult (also found in the AccountController.cs file) that when executed calls context.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 the AuthenticationManager 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 the AuthenticationManager. 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 the AuthenticationResponseGrant 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 the AccountController.ExternalLoginCallback() action.

Converting to Local Identity

The identity received from Google is an external identity. ASP.NET Identity is built around the concept of a local 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 type ApplicationCookie. 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 an AuthenticationResponseRevoke 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.

This post is part of the Owin Authentication series.<< ASP.NET Identity and Owin OverviewWriting an Owin Authentication Middleware >>

  • Payoj Baral on 2014-07-11

    As far as I am concerned your articles on Owin are the best out there. Most others that I read tend to omit something or make some assumptions about how much the reader knows. This really helped. Thank you.

  • Robert on 2014-07-14

    Great post! Thank you for that.
    I’m trying to understand how this works in a WebForms-template. A VS2013 Webforms project does not contain the AccountController.cs and I can’t find anything that handles the callback from e.g. Google.

    Do you know how the call-back is handled (or should be handled) in a new VS2012/2013 Webforms ASP.NET application? The call to Google works, and I can get it to do a return call, but to what URL? The default seems to be /signin-google, but that doen’t exist either.

    I also asked this question on StackOverflow: http://stackoverflow.com/questions/24722738/externallogincallback-missing-in-vs2013-webforms-application-template

    Thanks,
    Robert

  • Sky Sigal on 2014-07-30

    Thanks for the effort put in to document clearly what is going on. Excellent job.

  • 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

The complete code for all posts is available on GitHub.

Popular Posts

Archives

Series

Powered by WordPress with the Passion for Coding theme.