Walkthrough: Decrypting and Reading Claims from a JWT in C#

In this walkthrough, we'll demonstrate how to decrypt and read claims from a JWT (JSON Web Token) received in a query string parameter using C#. We'll use the Microsoft.IdentityModel library to accomplish this task. Additionally, we'll show you how to verify the JWT's signature by fetching the public key from a specified endpoint.

Prerequisites

Before we begin, ensure you have the following prerequisites in place:

  • A web application capable of receiving JWTs via query string parameters.
  • Microsoft.IdentityModel library installed. You can add it to your project using NuGet Package Manager.

Steps

Step 1: Receive and Extract the JWT

First, you need to receive the JWT from the query string parameter and extract it. Here's an example of how you can do this in C#:

// Assuming you've received the user JWT, which is named by the conneQt platform 'user'
string jwtPayload = HttpContext.Current.Request.QueryString["user"];

The value read into jwtPayload is a signed JWT that has been encrypted using the encryptionSecret value sent to the Toolbar during widget registration.

Step 2: Read the public keys to verify the JWT is signed by conneQt

To verify the JWT's signature, you need to fetch the public keys from conneQt's public-keys endpoint and use the response to validate the token.

Internally, conneQt supports key rotation, which means the JWT you receive may have been signed by an older key if generated just before conneQt switches to a new one. To accommodate this, conneQt's public-keys endpoint provides an array of signing keys, including historical ones for a specified duration.

The first step is to define a class to represent the response from conneQt's public-keys endpoint:

public class JsonWebKeysCollection
{
    public ICollection<JsonElement> Keys { get; set; } = Array.Empty<JsonElement>();

    public SecurityKey[] ToSecurityKeys()
    {
        return Keys
            .Select(x => new JsonWebKey(x.ToString()))
            .Cast<SecurityKey>()
            .ToArray();
    }
}

Now make a HTTP request to the public-keys endpoint to download the public keys and read them as an array of SecurityKey instances.

// Download the public keys from the conneQt public-keys endpoint
Uri endpoint = new Uri("https://int.conneqt.health/api/v1/public-keys");
using HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync(endpoint, cancellationToken);
response.EnsureSuccessStatusCode();

// deserialize the response into a JsonWebKeysCollection model
JsonSerializerOptions deserializationOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
JsonWebKeysCollection? keyCollection = (JsonWebKeysCollection?)await response.Content.ReadFromJsonAsync(typeof(JsonWebKeysCollection), deserializationOptions, cancellationToken);
keyCollection ??= new JsonWebKeysCollection { Keys = new List<JsonElement>(0) };

// use the ToSecurityKeys method on the model to parse the JSON content into an array of SecurityKey values
SecurityKey[] signingKeys = keyCollection.ToSecurityKeys();
Note

The endpoint URL will be different across environments (e.g. integration, production).

Step 3: Load the encryption key used to encrypt the JWT

During widget registration the request must provide conneQt with an encryptionSecret value, conneQt uses this value to encrypt the JWT before including it in a URL.

Read the encryption secret into a SecurityKey that will be provided to the Microsoft.IdentityModel library for decrypting the JWT.

// this matches our sample request in our API documentation, please don't use this value in production
string encryptionSecret = "HAEISWDHFDHSWAUIDHF8239DH0C0HIAB";
byte[] encryptionSecretBytes = Encoding.UTF8.GetBytes(encryptionSecret);
SecurityKey decryptionKey = new SymmetricSecurityKey(encryptionSecretBytes);

Step 4: Validate and Read the JWT

The Microsoft.IdentityModel library reads and validates the JWT in one call via the JwtSecurityTokenHandler class, and maps a valid JWT to a ClaimsPrincipal we can read the list of claims from.

Create a JwtSecurityTokenHandler and clear the default mappings that come with the library which morph the claim names from the JWT.

 JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
 tokenHandler.InboundClaimTypeMap.Clear();

Next use the JwtSecurityTokenHandler to decrypt the JWT, verify the signature, and the read the claims list. conneQt sets the intended audience property of the JWT to the name of the widget as it is was sent to the registration endpoint.

// conneQt will set the audience value of the JWT to match the registered name of our widget
string registeredWidgetName = "My Widget";

// conneQt will set the issuer property of the JWT to a string value
// ideally in your code the expected value would be read from configuration
string expectedJwtIssuer = "https://toolbar.quicksilva.io";

ClaimsPrincipal? principal = tokenHandler.ValidateToken(jwtPayload, new TokenValidationParameters
{
    TokenDecryptionKey = decryptionKey,
    IssuerSigningKeys = signingKeys,
    ValidAudience = registeredWidgetName,
    ValidIssuer = expectedJwtIssuer,
    ValidateAudience = true,
    ValidateIssuer = true,
    ValidateLifetime = true,
}, out _);

Successfully reading the JWT will produce a ClaimsPrincipal from which the list of claims that were included in the JWT can be read.

IEnumerable<Claim> claims = principal.Claims;

That's it! You've successfully decrypted and read claims from a JWT received via a query string parameter in your C# application using the Microsoft.IdentityModel library. Don't forget to handle errors and exceptions gracefully in a production environment and ensure the security of your encryption keys.

See Also