Simant
Simant

Reputation: 4242

How to generate a JSON Web Key (JWK) in C#

I need to generate JSON Web Key (jwk) using C# as shown. I have gone through a few articles but there were implementations in other languages such as Java and there was a third-party library (Nimbus JOSE+JWT library and so). Could you please help me to generate a JSON Web Key in C#?

{
    "e": "AQAB",
    "n": "nZD7QWmIwj-3N_RZ1qJjX6CdibU87y2l02yMay4KunambalP9g0fU9yZLwLX9WYJINcXZDUf6QeZ-SSbblET-h8Q4OvfSQ7iuu0WqcvBGy8M0qoZ7I-NiChw8dyybMJHgpiP_AyxpCQnp3bQ6829kb3fopbb4cAkOilwVRBYPhRLboXma0cwcllJHPLvMp1oGa7Ad8osmmJhXhM9qdFFASg_OCQdPnYVzp8gOFeOGwlXfSFEgt5vgeU25E-ycUOREcnP7BnMUk7wpwYqlE537LWGOV5z_1Dqcqc9LmN-z4HmNV7b23QZW4_mzKIOY4IqjmnUGgLU9ycFj5YGDCts7Q",
    "alg": "RS256",
    "kid": "8f796169-0ac4-48a3-a202-fa4f3d814fcd",
    "kty": "RSA",
    "use": "sig"
}

Upvotes: 9

Views: 18577

Answers (3)

Alexander Farber
Alexander Farber

Reputation: 22958

First create two RSA keys:

RSA rsa1 = RSA.Create(2048);
RSA rsa2 = RSA.Create(2048);

RsaSecurityKey publicKey1 = new(rsa1.ExportParameters(false))
{
    KeyId = "keyId1"
};
RsaSecurityKey publicKey2 = new(rsa2.ExportParameters(false))
{
    KeyId = "keyId2"
};

RsaSecurityKey publicAndPrivateKey1 = new(rsa1.ExportParameters(true))
{
    KeyId = "keyId1"
};
RsaSecurityKey publicAndPrivateKey2 = new(rsa2.ExportParameters(true))
{
    KeyId = "keyId2"
};

Use them to create two JWK's:

JsonWebKey jwk1 = JsonWebKeyConverter.ConvertFromRSASecurityKey(publicKey1);
JsonWebKey jwk2 = JsonWebKeyConverter.ConvertFromRSASecurityKey(publicKey2);

Add them to a JWKS and print it using JsonExtensions.SerializeToJson() method (which works better than Newtonsoft JSON here, because the output can be passed back to the JsonWebKey and JsonWebKeySet constructors):

IList<JsonWebKey> jwksList = new List<JsonWebKey>
{
    jwk1,
    jwk2,
};

Dictionary<string, IList<JsonWebKey>> jwksDict = new() 
{ 
    { "keys", jwksList }
};

string jwksStr = SerializeToJson(jwksDict);
Console.WriteLine(jwksStr); // put this at https://example.com/.well-known/jwks.json

JsonWebKeySet jwks = new(jwksStr);

Then issue two JWT's:

JsonWebTokenHandler tokenHandler = new();

SecurityTokenDescriptor descriptor1 = new()
{
    Issuer = "example.com",
    Audience = "cats",
    SigningCredentials = new SigningCredentials(publicAndPrivateKey1, SecurityAlgorithms.RsaSsaPssSha256),
};

SecurityTokenDescriptor descriptor2 = new()
{
    Issuer = "example.com",
    Audience = "dogs",
    SigningCredentials = new SigningCredentials(publicAndPrivateKey2, SecurityAlgorithms.RsaSsaPssSha256),
};

string jwt1 = tokenHandler.CreateToken(descriptor1);
string jwt2 = tokenHandler.CreateToken(descriptor2);

Finally, validate the two tokens if someone gives them back to you:

TokenValidationParameters parameters = new() 
{ 
    ValidateIssuer = true,
    ValidIssuer = "example.com",
    ValidateAudience = true,
    ValidAudiences = new[] { "cats", "mice" }, // "dogs" are not allowed
    IssuerSigningKeys = jwks.GetSigningKeys(),
};

TokenValidationResult result1 = tokenHandler.ValidateToken(jwt1, parameters);
TokenValidationResult result2 = tokenHandler.ValidateToken(jwt2, parameters);

Console.WriteLine("jwt1 is valid: " + result1.IsValid);
Console.WriteLine("jwt2 is valid: " + result2.IsValid);

Screenshot of my console app:

screenshot

Here the Nuget packages used in the .csproj file:

<ItemGroup>
    <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.25.0" />
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.0" />
</ItemGroup>

and the using statements:

using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
using static System.IdentityModel.Tokens.Jwt.JsonExtensions;

Upvotes: 11

Neil Laslett
Neil Laslett

Reputation: 2149

No need to run this through a X509 cert. You can easily serialize the JSON to a Microsoft.IdentityModel.Tokens.JsonWebKey object using the .Create(string json) constructor.

You can either pass in your JSON (after removing line breaks and escaping quotes),

string str_jwk = "{\"e\":\"AQAB\",\"n\":\"nZD7QWmIwj-3N_RZ1qJjX6CdibU87y2l02yMay4KunambalP9g0fU9yZLwLX9WYJINcXZDUf6QeZ-SSbblET-h8Q4OvfSQ7iuu0WqcvBGy8M0qoZ7I-NiChw8dyybMJHgpiP_AyxpCQnp3bQ6829kb3fopbb4cAkOilwVRBYPhRLboXma0cwcllJHPLvMp1oGa7Ad8osmmJhXhM9qdFFASg_OCQdPnYVzp8gOFeOGwlXfSFEgt5vgeU25E-ycUOREcnP7BnMUk7wpwYqlE537LWGOV5z_1Dqcqc9LmN-z4HmNV7b23QZW4_mzKIOY4IqjmnUGgLU9ycFj5YGDCts7Q\",\"alg\":\"RS256\",\"kid\":\"8f796169-0ac4-48a3-a202-fa4f3d814fcd\",\"kty\":\"RSA\",\"use\":\"sig\"}";

Or use Notepad++ to Base64 encode (Plugins -> MIME Tools -> Base64 Encode with padding), then decode with:

string encoded_value = "ew0KICAgICJlIjogIkFRQUIiLA0KICAgICJuIjogIm5aRDdRV21Jd2otM05fUloxcUpqWDZDZGliVTg3eTJsMDJ5TWF5NEt1bmFtYmFsUDlnMGZVOXlaTHdMWDlXWUpJTmNYWkRVZjZRZVotU1NiYmxFVC1oOFE0T3ZmU1E3aXV1MFdxY3ZCR3k4TTBxb1o3SS1OaUNodzhkeXliTUpIZ3BpUF9BeXhwQ1FucDNiUTY4MjlrYjNmb3BiYjRjQWtPaWx3VlJCWVBoUkxib1htYTBjd2NsbEpIUEx2TXAxb0dhN0FkOG9zbW1KaFhoTTlxZEZGQVNnX09DUWRQbllWenA4Z09GZU9Hd2xYZlNGRWd0NXZnZVUyNUUteWNVT1JFY25QN0JuTVVrN3dwd1lxbEU1MzdMV0dPVjV6XzFEcWNxYzlMbU4tejRIbU5WN2IyM1FaVzRfbXpLSU9ZNElxam1uVUdnTFU5eWNGajVZR0RDdHM3USIsDQogICAgImFsZyI6ICJSUzI1NiIsDQogICAgImtpZCI6ICI4Zjc5NjE2OS0wYWM0LTQ4YTMtYTIwMi1mYTRmM2Q4MTRmY2QiLA0KICAgICJrdHkiOiAiUlNBIiwNCiAgICAidXNlIjogInNpZyINCn0=";
string str_jwk = Encoding.UTF8.GetString(Convert.FromBase64String(encoded_value))

Then:

var jwk = JsonWebKey.Create(str_jwk);

That will give you your JWK. My question to you is, how do you then use it to sign a JWT? That's what I'm pulling my hair out over. (Under .NET Framework 4.8. I know there are some much better helper classes available in .NET 5/6.)

Upvotes: 2

BBoy
BBoy

Reputation: 1113

Christos's comment above points in the correct direction, however the class you actually want is JsonWebKeyConverter. in the Microsoft.IdentityModel.Tokens nuget package.

Assuming you have your public key file available somewhere then you would want the following code:

var cert = new X509Certificate2(@".\CertPublicKey.cer");
var key = new X509SecurityKey(cert);
var jwk = JsonWebKeyConverter.ConvertFromX509SecurityKey(key, true);

Upvotes: 8

Related Questions