68

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:

chunked encrypted cookie generated by ASP.NET

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.

3
  • 3
    could you update your answer with correct code? Commented Jan 26, 2019 at 19:07
  • 1
    The accepted answer has been given by @RonC, not me. His code is correct. Commented Jan 27, 2019 at 6:01
  • In .NET 8 it still throws payload exception. Question about it is in stackoverflow.com/questions/79173849/… Commented Nov 9, 2024 at 22:50

5 Answers 5

60

Decrypting the Authentication Cookie without needing the keys

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 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.


Decrypting the Authentication Cookie when persisting keys to a directory

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

Sign up to request clarification or add additional context in comments.

26 Comments

Really nice post about something I had been questioning but hadn't needed to deep dive.
Looks like in Core 2.0 the CookieAuthenticationMiddleware class has been replaced, but the string value of "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware" is still used. github.com/aspnet/Security/blob/…
I am still having issues with this working with .NET core 2.0 and a chunked cookie. Any ideas? I'm concatenating the cookie values together and using the cookie name in the DataProtectionProvider. Thanks.
In case you specified an application name when adding the data protection service services.AddDataProtection().PersistKeysToFileSystem(new System.IO.DirectoryInfo(@"C:\temp-keys\")).SetApplicationName("MyApplicationName"); be sure to specify the same name in the DataProtectionProvider.Create call: var dataProtection = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"), cfg => cfg.SetApplicationName("MyApplicationName"));
@RonC thanks for reply. I could be reading this incorrectly, but this page may indicate it is possible? learn.microsoft.com/en-us/aspnet/core/security/…
|
33

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 and further.

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.

Update from 2024:

Sorry, I came back with some notes.

While the above code works perfectly fine, keep in mind, that decrypting is always a CPU-intensive operation (as with anything crypto-related).

Basically you are decrypting the data twice - 1st time the ASP.NET Core does it for you, then you do it again, manually. If you have a high-load app that serves thousands or requests every minute, this can have a big impact on performance.

Instead of manually decrypting a cookie, why not extract the data from what you already have (i.e. the HttpContext)

For example:

//let's get the decrypted ticket properties from context-features
var authResult = httpContext.Features.Get<IAuthenticateResultFeature>()?.AuthenticateResult;
//Good. Now let's read some data from it

//for example let's read the auth cookie issue date
var issueDate = authResult.Properties.IssuedUtc;

P.S. Jeep in mind that IAuthenticateResultFeature can be null if the requests comes from anonymous user. Also keep in mind that AuthenticateResult can also be null if you're overwriting the HttpContext.User in your code (for example you have a custom priciple class with some custom logic). I spent hours debugging this. In that case - move the code up the pipe in the middleware, right after UseAuthentication() call.

9 Comments

For ASP.NET Core Identity, had to use .Get("Identity.Application") instead of .Get(CookieAuthenticationDefaults.AuthenticationScheme);
This worked up to step two for me. After step three I had NULL, which I didn't expect. I'm open to ideas.
@DonRolling did you call services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) and app.UseAuuthentication in your Startup.cs?
(await HttpContext.AuthenticateAsync()).Ticket returns the same information with a single line. Works in all .net versions from core 2.0 to 7.
@idilov you are 100% right! However, AuthenticateAsync re-decrypts the cookie (just as my code) and reconstructs the principle, and this can affect performance (just as my code). So I updated the answer with another more elegant solution.
|
12

Another variation for ASP.NET Core 2.2:

var cookieManager = new ChunkingCookieManager();
var cookie = cookieManager.GetRequestCookie(HttpContext, ".AspNetCore.Identity.Application");

var dataProtector = dataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "Identity.Application", "v2");

//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookie);
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(cookie);

2 Comments

Where does dataProtectionProvider come from?
@Pancake can be inject with DI in constructor, IDataProtectionProvider dataProtectionProvider . You also need to add it to services: learn.microsoft.com/en-us/aspnet/core/security/data-protection/…
12

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.

2 Comments

It's apparently due to the cookie schema. What is the cookie schema anyway?
@liang, it's likely due to using a newer version of .NET Core. MS changed the code referred in my link in v2.2 and completely redesigned it in v3.
3

I just got this working in Classic ASP.net (4.6.1). Note the following required installs:

  • Microsoft.Owin.Security.Interop (will come with a bunch of dependencies - note, I used verison 3.0.1 due to an exception, but that might not be necessary).
  • Microsfot.AspNetCore.DataProtection (will come with a bunch of dependencies)
  • Standard web stuff for the 4.6.1 framework

The following constants are defined by the framework:

  • PROVIDER_NAME = "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware"
  • SCHEME_NAME = "Identity.Application"
  • COOKIE_NAME = ".AspNetCore.Identity.Application" (can be customized)

The following constants are configuration-specific, but must be the same between applications.

  • APP_NAME = "Auth.Test.App"
  • SHARED_KEY_DIR = "C:\\app-keyring"

The Process:

This article was helpful in getting this set up on both sides, but particularly in properly configuring the .Net Core side. Thus, we shall leave that as an exercise for the reader.

Once you have these set up, on the 4.6.1 Decryption Side, the following code will yield the ClaimsIdentity set in (for example) .Net Core 3.0:

using Microsoft.AspNetCore.DataProtection;
using Microsoft.Owin.Security.Interop;
using System.IO;
using System.Security.Claims;
using System.Web;

public static ClaimsIdentity GetClaimsIdentity(HttpContext context)
{
    //Get the encrypted cookie value
    var cookie = context.Request.Cookies[Constants.COOKIE_NAME];
    if (cookie == null) {
        return null;
    }
    var cookieValue = cookie.Value;

    //Get a data protector to use with either approach
    var keysDir = new DirectoryInfo(Constants.SHARED_KEY_DIR);
    if (!keysDir.Exists) { keysDir.Create(); }

    var provider = DataProtectionProvider.Create(keysDir,
        options => options.SetApplicationName(Constants.APP_NAME));
    var dataProtector = provider.CreateProtector(Constants.PROVIDER_NAME, Constants.SCHEME_NAME, "v2");

    //Get the decrypted cookie as a Authentication Ticket
    var shim = new DataProtectorShim(dataProtector);
    var ticketDataFormat = new AspNetTicketDataFormat(shim);
    var ticket = ticketDataFormat.Unprotect(cookieValue);

    return ticket.Identity;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.