Let's consider a common-known ASP.NET Core scenario. Firstly we add the middleware:
public void Configure(IApplicationBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "MyCookie",
CookieName = "MyCookie",
LoginPath = new PathString("/Home/Login/"),
AccessDeniedPath = new PathString("/Home/AccessDenied/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
//...
}
Then serialize a principal:
await HttpContext.Authentication.SignInAsync("MyCookie", principal);
After these two calls an encrypted cookie will be stored at the client side. You can see the cookie (in my case it was chunked) in any browser devtools:
It's not a problem (and not a question) to work with cookies from application code.
My question is: how to decrypt the cookie outside the application? I guess a private key is needed for that, how to get it?
I checked the docs and found only common words:
This will create an encrypted cookie and add it to the current response. The AuthenticationScheme specified during configuration must also be used when calling SignInAsync.
Under the covers the encryption used is ASP.NET's Data Protection system. If you are hosting on multiple machines, load balancing or using a web farm then you will need to configure data protection to use the same key ring and application identifier.
So, is it possible to decrypt the authentication cookie, and if so how?
UPDATE #1: Based on Ron C great answer and comments, I've ended up with code:
public class Startup
{
//constructor is omitted...
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection().PersistKeysToFileSystem(
new DirectoryInfo(@"C:\temp-keys\"));
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "MyCookie",
CookieName = "MyCookie",
LoginPath = new PathString("/Home/Index/"),
AccessDeniedPath = new PathString("/Home/AccessDenied/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
public class HomeController : Controller
{
public async Task<IActionResult> Index()
{
await HttpContext.Authentication.SignInAsync("MyCookie", new ClaimsPrincipal());
return View();
}
public IActionResult DecryptCookie()
{
var provider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));
string cookieValue = HttpContext.Request.Cookies["MyCookie"];
var dataProtector = provider.CreateProtector(
typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(false, true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
return Content(plainText);
}
}
Unfortunately this code always produces exception on Unprotect
method call:
CryptographicException in Microsoft.AspNetCore.DataProtection.dll: Additional information: The payload was invalid.
I tested different variations of this code on several machines without positive result. Probably I made a mistake, but where?
UPDATE #2: My mistake was the DataProtectionProvider
hasn't been set in UseCookieAuthentication
. Thanks to @RonC again.
Based on the CookieAuthenticationMiddleware
source code https://github.com/aspnet/Security/blob/rel/1.1.1/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationMiddleware.cs#L4 it looks like the purpose you need to pass is typeof(CookieAuthenticationMiddleware)
. And since they are passing additional parameters to the IDataProtector
you will need to match them. So this line of code should get you an IDataProtector
that can be used to decrypt the authentication cookie:
var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, Options.AuthenticationScheme, "v2");
Note that provider
here is an IDataProtectionProvider
obtained from the DI container and Options.AuthenticationScheme
is just "MyCookie" in this case since that's what it was set to in the Configure
method of the startup.cs file.
Here is an example action method for decrypting your authentication cookie two different ways:
public IActionResult DecryptCookie() {
//Get the encrypted cookie value
string cookieValue = HttpContext.Request.Cookies["MyCookie"];
//Get a data protector to use with either approach
var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");
//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
//Get the decrypted cookie as a Authentication Ticket
TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);
return View();
}
This method uses an IDataProtectionProvider
called provider
that is constructor injected.
services.AddDataProtection().PersistKeysToFileSystem(
new DirectoryInfo(@"C:\temp-keys\"));
BE CAREFUL though because the keys are not encrypted so it's up to you to protect them!!! Only persist the keys to a directory if you absolutely must, (or if you are just trying to understand how the system works). You will also need to specify a cookie DataProtectionProvider
that uses those keys. This can be done with the help of the UseCookieAuthentication
configuration in the Configure
method of the startup.cs class like so:
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\")),
AuthenticationScheme = "MyCookie",
CookieName = "MyCookie",
LoginPath = new PathString("/Home/Login"),
AccessDeniedPath = new PathString("/Home/AccessDenied"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
With that configuration done. You can now decrypt the authentication cookie with the following code:
public IActionResult DecryptCookie() {
ViewData["Message"] = "This is the decrypt page";
var user = HttpContext.User; //User will be set to the ClaimsPrincipal
//Get the encrypted cookie value
string cookieValue = HttpContext.Request.Cookies["MyCookie"];
var provider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));
//Get a data protector to use with either approach
var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");
//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
//Get teh decrypted cookies as a Authentication Ticket
TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);
return View();
}
You can learn more about this latter scenario here: https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/compatibility/cookie-sharing