Combining ASP.NET MVC 5 and OWIN together can be fun, but you can very easily run into issues from the differences in philosophy & implementation between the two technologies. One such difference is related to how the two manipulate cookies, where by the cookie manager in OWIN and the cookie manager in ASP.NET will overwrite each other's cookies, causing issues with authentication using the [Authorize]
attribute and the various OWIN authentication errors. In this post, we go over the long process of finding the right solution in 2017 (tl;dr: upgrade OWIN and use SystemWebChunkingCookieManager
).
Symptoms
The problem can manifest in a few different ways, some more subtle than others, but by far the most blatent is the infinite login redirect loop. I experienced this when integrating Azure AD via OpenID Connect in a side project. This combination normally makes the login flow look like this:
- User hits page that requires authentication (such as one marked with the
[Authorize]
attribute) - Application generates a login request URL, which is sent to User
- User is redirected to Microsoft's login page
- User logs in with their credentials
- MS constructs a URL pointing back to the original application with the authentication result
- User is redirected back to the application
- Application consumes the authentication result, storing the login info in a cookie (assuming you are using cookie-based authentication)
- User goes back to the original URL and is now fully authenticated, sending the auth cookie to the application for all browser and API requests
The infinite redirect loop makes this workflow a lot sadder, and looks like this:
- Steps 1-7 of the above
- OWIN and MVC delete each other's cookies before the page is loaded
- The route is hit, the
[Authorize]
attribute detects a non-authenticated user, and the user is redirected to the MS login URL - Since the user is already logged in on the MS side, it redirects immediately back to the application, starting the loop again
Community Discussions
One suggestion was 'upgrading' to ASP.NET core, as the problem was fixed since everything is OWIN-based now. For most existing apps, however, ASP.NET Core is still lacking in features and converting existing apps would be challenging to impossible.
Another suggestion (1, 2, 3) is to install the Kentor Cookie Saver. This adds a new middleware to the OWIN pipeline to save authentication cookies and prevent them from getting obliterated by the clashes between the cookie managers. This works fine for a subset of the issues, but is essentially unsupported and is more of a band-aid for the symptom than a true fix.
Solution
It turns out in early 2017 a workaround was added: SystemWebCookieManager and SystemWebChunkingCookieManager. These were added in the 3.1.0
release of Microsoft.Owin.Host.SystemWeb
. You can pass either of these to app.UseCookieAuthentication()
to make OWIN write cookies to correct location. This makes OWIN and ASP.NET stop clobbering each others' cookies, and stops the infinite redirect loop. You can see a full example of its usage below:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebChunkingCookieManager()
});
var authenticationOptions = new OpenIdConnectAuthenticationNotifications();
app.UseOpenIdConnectAuthentication(authenticationOptions);
}
This squashed the bug for me and allowed me to get back to having fun programming.