Understanding the Owin External Authentication Pipeline
Join the DZone community and get the full member experience.
Join For FreeOwin 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.
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.cs
file) 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 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 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.
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
.
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.
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.
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.
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, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments