8

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"
}
Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
Simant
  • 3,142
  • 4
  • 32
  • 61
  • Have you checked this https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.jsonwebkey.create?view=azure-dotnet#Microsoft_IdentityModel_Tokens_JsonWebKey_Create_System_String_ ? This is part of Azure SDK for .NET as I understand from the link I shared. – Christos Dec 30 '20 at 18:59

3 Answers3

7

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);
BBoy
  • 1,063
  • 1
  • 10
  • 21
  • Any quick way to serialize this into JSON too? I guess that could be done manually, but it would be nice to handle this will a standard library. – user2173353 Jan 13 '22 at 15:06
  • That is the best thing I found so far: https://github.com/StephenClearyExamples/RsaJwk/tree/master/RsaJwk – user2173353 Jan 13 '22 at 15:43
6

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;
Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
  • I implemented a custom *.well-known/openid-configuration/jwks* endpoint and return the object your code suggests. The JSON resembles what's displayed on other authorities (mine has more fields, though). Nevertheless, I still get this error when I try to authorize: *WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"*. Since I turned off all the validations in *AddJwtBearer()*, I guess that there's some glitch. How would you troubleshoot it? – Konrad Viltersten Jan 23 '23 at 09:14
2

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

Neil Laslett
  • 2,019
  • 22
  • 22
  • 1
    `var signingKey = new JsonWebKeySet(jsonString).GetSigningKeys().First();` then get the signing credentials to create your JwtSecurityToken; `new SigningCredentials(signingKey, _configuration.SigningAlgorithm.ToString()` – Lee Apr 12 '22 at 03:39