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.
The ASP.NET Core data-protection system is used for exactly this purpose. It encrypts and decrypts sensitive data such as the authentication cookie. By encrypting the authentication cookie before it's returned in the response, the application knows that the cookie has not been tampered with, and can trust its values.
AuthenticationScheme passed to AddAuthentication sets the default authentication scheme for the app. AuthenticationScheme is useful when there are multiple instances of cookie authentication and the app needs to authorize with a specific scheme. Setting the AuthenticationScheme to CookieAuthenticationDefaults.
Let's implement the Cookie Authentication in ASP.NET Core step by step. Open the Visual Studio and click on Create a new Project. Select ASP.NET Core Empty project and click on next. Give a name to your Project, select the location for the project creation, and click on Next.
A cookie is a small bit of text that accompanies requests and pages as they go between the Web server and browser. The cookie contains information the Web application can read whenever the user visits the site.
It's worth noting that you don't need to gain access to the keys to decrypt the authentication cookie. You simply need to use the right IDataProtector
created with the right purpose parameter, and subpurpose parameters.
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 thatOptions.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.
If you want to share cookies between applications then you might decide to persist the data protection keys to a directory. This can be done by adding the following to the ConfigureServices
method of the startup.cs file:
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
See below a helper method for .NET Core 2 to get claims from a cookie:
private IEnumerable<Claim> GetClaimFromCookie(HttpContext httpContext, string cookieName, string cookieSchema)
{
// Get the encrypted cookie value
var opt = httpContext.RequestServices.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>();
var cookie = opt.CurrentValue.CookieManager.GetRequestCookie(httpContext, cookieName);
// Decrypt if found
if (!string.IsNullOrEmpty(cookie))
{
var dataProtector = opt.CurrentValue.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", cookieSchema, "v2");
var ticketDataFormat = new TicketDataFormat(dataProtector);
var ticket = ticketDataFormat.Unprotect(cookie);
return ticket.Principal.Claims;
}
return null;
}
As was pointed by @Cirem, the dodgy way of creating a protector is exactly how Microsoft does it (see their code here). Therefore, it may change in future versions.
While inside ASP.NET Core app you can just use CookieAuthenticationOptions.TicketDataFormat.Unprotect(cookieValue)
.
Here, a simple static (!) method I wrote:
public static AuthenticationTicket DecryptAuthCookie(HttpContext httpContext)
{
// ONE - grab the CookieAuthenticationOptions instance
var opt = httpContext.RequestServices
.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>()
.Get(CookieAuthenticationDefaults.AuthenticationScheme); //or use .Get("Cookies")
// TWO - Get the encrypted cookie value
var cookie = opt.CookieManager.GetRequestCookie(httpContext, opt.Cookie.Name);
// THREE - decrypt it
return opt.TicketDataFormat.Unprotect(cookie);
}
Works fine under .NET 5 and .NET 6.
I'm adding this answer for reference, because this question pops up on every search engine if you search for how to manually decrypt ASP.NET auth cookie.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With