OWIN Cookie Authentication with variable cookie paths
By default OWIN Cookie Authentication let’s you specify a single configurable cookie path that does not change for the lifetime of the application, for example
app.UseCookieAuthentication(new CookieAuthenticationOptions { CookiePath = "/Client1", CookieSecure = CookieSecureOption.SameAsRequest, CookieHttpOnly = true });
This is going to only allow cookie authentication to occur when the request is for any path under /Client1. But what if you wanted this same cookie and cookie authentication provider to work for other variable paths, what if we wanted it to execute for multiple configured paths like: /Client1, /Client2/Secured, /Client3/Private ? Or what if we wanted wanted this Cookie Authentication Provider to execute dynamically based on the request object?
ICookieManager to the rescue
The CookieAuthenticationOptions has a property: CookieManager which you can set to any instance of ICookieManager. ICookieManager contains these methods: GetRequestCookie, AppendResponseCookie, DeleteCookie but all we really need to worry about GetRequestCookie. It turns out that the CookieAuthenticationHandler will detect if this method returns null and if so it will just not continue trying to authenticate the request. To make things easy we’ll just inherit from the default OWIN ICookieManager which is ChunkingCookieManager, although it’s methods are not marked as virtual we can still override them by explicitly implementing the ICookieManager.GetRequestCookie method (Pro Tip! You can always override a non virtual method if you explicitly implement an interfaces method).
//explicit implementation string ICookieManager.GetRequestCookie(IOwinContext context, string key) { //TODO: Given what is in the context, we can check pretty much anything, if we don't want //this request to continue to be authenticated, just return null. Example: var toMatch = new[] {"/Client1", "/Client2/Secured", "/Client3/Private"}; if (!toMatch.Any(m => context.Request.Uri.AbsolutePath.StartsWith(m, StringComparison.OrdinalIgnoreCase))) { return null; } //if we don't want to ignore it then continue as normal: return base.GetRequestCookie(context, key); }
The last step is to not worry about the CookiePath since the custom ICookieManager.GetRequestCookie is going to deal with whether the middleware receives a cookie value or not so in that case, the CookiePath for the CookieAuthenticationOptions will remain the default of “/”
We’ve been doing this in Umbraco CMS core for quite some time, I meant to blog about this then but just didn’t find the time. In Umbraco we have a few custom request paths that we need authenticated with our custom back office cookie, for reference the BackOfficeCookieManager source is found here: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs