Decrypting OWIN Authentication Ticket

Home / Decrypting OWIN Authentication Ticket

Being able to decrypt the OWIN AuthenticationTicket can be very useful. In the cases where the cookie/tickets are shared across applications, this is especially true.

Interestingly, if you’re using OWIN for both cookie-based authentication and access tokens, the Ticket is stored in both mediums.

With that in mind, the easiest method to decrypt a ticket to access claims, etc is to simply stand up a protected Resource server with a single Api endpoint to display the contents of the ticket. Going this route, the decryption is automatically handled by OWIN with very little code. The endpoint can be accessed by a user’s browser (decrypting the cookie) or by a server passing in a Bearer token.


Here’s an example that simply displays all of the user’s claims from either an AccessToken or Cookie:

// Allow CORS for all origins.
[EnableCors(origins: "*", headers: "*", methods: "*")]
[HostAuthentication("Application")]
[Authorize]
public class UserController : ApiController
{
    public object Get()
    {
        var identity = this.User.Identity as ClaimsIdentity;            
        var roleClaims = identity.Claims.Where(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Select(x => x.Value).ToList();
        var nonRoleClaims = identity.Claims.Where(x => x.Type != ClaimsIdentity.DefaultRoleClaimType).Select(x => new { Type = x.Type, Value = x.Value }).ToList();
        return new { name = identity.Name, roles = roleClaims, claims = nonRoleClaims };
    }
}

In the case where we’re designing an SSO system, in which multiple applications are sharing the auth cookie and machineKeys, which OWIN uses for encryption/decryption, and also hooking into the Middleware, we can decrypt the Access Token returned from the authorization code flow directly. To accomplish this, we implement the IDataProtector interface and use the System.Web.Security.MachineKey.Unprotect method.

/// <summary>
/// Helper method to decrypt the OWIN ticket
/// </summary>
private class MachineKeyProtector : IDataProtector
{
    private readonly string[] _purpose =
    {
        typeof(OAuthAuthorizationServerMiddleware).Namespace,
        "Access_Token",
        "v1"
    };

    public byte[] Protect(byte[] userData)
    {
        throw new NotImplementedException();
    }

    public byte[] Unprotect(byte[] protectedData)
    {
        return System.Web.Security.MachineKey.Unprotect(protectedData, _purpose);
    }
}

To utilize this, we just create an instance and pass in the Token to get the decrypted Ticket:

var secureDataFormat = new TicketDataFormat(new MachineKeyProtector());
AuthenticationTicket ticket = secureDataFormat.Unprotect(accessToken);

The AuthenticationTicket itself contains the ClaimsIdentity and a Dictionary of Properties. This can be useful for a number of things from testing to hooking into the MiddleWare and creating our own local ticket/cookie without the need to go back against the protected Resource provider.

On a side note, I did notice that the version of OWIN across machines has to match. IE – I had one application running OWIN v3 and another running v2. They could neither decrypt the tickets between them nor utilize the shared domain cookie.

9 thoughts on “Decrypting OWIN Authentication Ticket”

  1. Thanks for this amazing post this is what I have been looking for. In my case, I have an identity framework hosted on Azure and I want to decode the generated token in other applications that may or not live in azure as well. do you have a sample code you can share? I’m kind of lost on where to implement your solution

    1. Sure – Here’s an example where I requested a token from the OWIN server using password grant flow. The response is decrypted using the MachineKeyProtector as described in the post. Note that if you’re using the newer .NET Core DataProtector, the decryption is slightly different since the DataProtectorShim class must be used.

      var httpClientHandler = new HttpClientHandler()
      {
      	AllowAutoRedirect = false,
      	AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
      };
      
      var httpClient = new HttpClient(httpClientHandler) { Timeout = 20000 };
      
      // Build up the body for the token request
      var tokenDict = new Dictionary<string, string>() {
      	{ "grant_type", "password" },
      	{ "username", "username" },
      	{ "password", "password" },
      	{ "client_id", "clientId" },
      	{ "client_secret", "clientSecret" }
      };
      
      // Request the token
      HttpResponseMessage tokenResponse = await httpClient.PostAsync("https://yourtokenpath.com"), new FormUrlEncodedContent(tokenDict));
      
      // Deserialize the token response
      dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
      string accessToken = (string)response.access_token;
      string expires = (string)response.expires_in;
      string refreshToken = null;
      if (response.refresh_token != null) refreshToken = (string)response.refresh_token;
      
      // We could just pull the info from the ticket
      var secureDataFormat = new TicketDataFormat(new MachineKeyProtector());
      AuthenticationTicket ticket = secureDataFormat.Unprotect(accessToken);
      
      // The ClaimsIdenity becomes available as a property on the AuthenticationTicket
      var claimsIdentity = ticket.Identity;
      var userName = claimsIdentity.Name;
      
      // We can check roles or do anything that would otherwise be possible with a ClaimsIdentity
      var isInRole = HasClaim(ClaimTypes.Role, "SomeRole");
      
  2. Hi,
    I am using the same code in asp.net and passing 256 character token but getting exception ‘ Error occurred during a cryptographic operation ‘

    Using exact code shared above, do I need to add anything else, like notifying machine key in web.config or something.

    1. Having the machineKey in the web.config is my first thought as to what would fix this. For the .NET (non-Core) projects where I used this, I always had a machineKey node in the web.config and things worked fine.

      <system.web>
        <machineKey compatibilityMode="Framework45" validationKey=".." decryptionKey=".." validation="SHA1" decryption="AES" />
      </system.web>
      

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.