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. 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 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 (see the Using Owin External Login without ASP.NET Identity post for details).

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 >>

14 comments

  1. 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.

  2. 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

  3. Hi sir,
    As you say “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 ”
    Have you completed this task? Is the AuthenticationMode.Active useful?

  4. Thanks for your incredible series on Owin Authentication and Identity. This helped me so much to understand step by step what is happening, as the documentation for the template implementation of Asp .Net Identity is very poor.

    I do have a question. In the section “Converting to local identity” you state that in the ExternalLoginCallback function, the call to AuthenticationManager.GetExternalLoginInfoAsync() looks up the local identity and sets an AuthenticationResponseGrant of and an AuthenticationResponseRevoke on the AuthenticationManager.

    However, i noticed that those AuthenticationResponses are not yet set after this call. They are set after the call ‘SignInManager.ExternalSignInAsync’.
    Could it be that AuthenticationManager.GetExternalLoginInfoAsync() just gets the login info from the external cookie, and the SignInManager.ExternalSignInAsync is responsible for the management of those responses on the AuthenticationManager?

    1. 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.

      What I describe is what happens in the ExternalLoginCallback method. It retrievs the external identity through AuthenticationManager.GetExternalLoginInfoAsync(). The SignInManager.ExternalSignInAsync then lookups up the local identity from ASP.NET Identity and calls `AuthenticationManager.SignIn()` to set the `AuthenticationResponseGrant`.

  5. Hi Anders Abel:
    I would to share my problem with you about my application which authenticates requests wth social providers (google, linkedin, microsoft).
    The problem is a little strange because It works well with local IIS express, so when I debug my application with visual studio, instead when my application is deployed into a webserver It doesn’t work anymore.
    I notice that the cookie Aspnet.ExternalCookie is not written, so after authentication (google for example) ExternalLoginCallback is called correctly, but AuthenticationManager.GetExternalLoginInfoAsync() returns null.
    The main problem is .AspNet.ExternalCookie cookie is not written before, but how come It occurs only in production and not in debug mode ?

  6. Thanks for this series. It’s extremely useful for people like me who are new to Owin authentication. The breakpoint tip is especially useful as it gives insight into the abstracted authentication flow

  7. This looks great but I have yet to try it. My problem is that I generated an asp.net web forms template with authentication from VS 2015. Since Dropbox was missing, I also installed the https://github.com/TerribleDev/OwinOAuthProviders. Now Dropbox is logging in and reaching up to Allow correctly. But then instead of proceeding to my ReturnURI, it ‘s going to ask for login. On debugging, I find that it is going to RegisterExternalLogin page and failing at:

    Context.GetOwinContext().Authentication.GetExternalLoginInfo();

    Which is returning NULL. A search on google does not find any solution that works. How can I debug or fix this? I will appreciate any hints.

    Thanks.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.