Reputation: 4242
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
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:
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
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
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