Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure KeyVault - Sign JWT Token

I began using Azure Keyvault to store private keys for my application.

I have a use case where I need to sign a JWT token with an RSA private key.

When I had the private key in my application memory, it was easy, I would just do that

var token = new JwtSecurityToken(
                issuer,
                ...,
                claims,
                ...,
                ...,
                signingCredentials_PrivateKey);

Now that I began to use Azure Keyvault, I want to see if it's possible to sign JWT tokens via the KeyVaultClient.SignAsync method.

Something along the lines of

KeyVaultClient client = ...;
var token = new JwtSecurityToken(
                issuer,
                ...,
                claims,
                ...,
                ...);
var tokenString = client.SignAsync(myKeyIdentifier, token);
like image 774
user10962730 Avatar asked Jul 08 '19 06:07

user10962730


2 Answers

First, a JWT token consists of three parts: Header, Payload and Signature. All of them are Base64UrlEncoded.

You can get the signature as following:

HMAC-SHA256(
 base64urlEncoding(header) + '.' + base64urlEncoding(payload),
 secret
)

So, you need to generate the header and payload, combine them by dot, compute the hash, and then you can get the signature.

Here is a sample for your reference:

var byteData = Encoding.Unicode.GetBytes(base64urlEncoding(header) + "." + base64urlEncoding(payload));
var hasher = new SHA256CryptoServiceProvider();
var digest = hasher.ComputeHash(byteData);
var signature = await keyClient.SignAsync(keyIdentifier, "RS256", digest);
var token = base64urlEncoding(header) + "." + base64urlEncoding(payload) + "." + base64urlEncoding(signature)

The official SDK documentation for SignAsync

Wiki for JWT

like image 114
Jack Jia Avatar answered Oct 29 '22 08:10

Jack Jia


I ended up using Jack Jia's answer

var token = new JwtSecurityToken(
                issuer,
                appId,
                claims,
                signDate,
                expiryDate);

var header = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(new Dictionary<string, string>()
{
    { JwtHeaderParameterNames.Alg, "RS256" },
    { JwtHeaderParameterNames.Kid, "https://myvault.vault.azure.net/keys/mykey/keyid" },
    { JwtHeaderParameterNames.Typ, "JWT" }
}));
var byteData = Encoding.UTF8.GetBytes(header + "." + token.EncodedPayload);
var hasher = new SHA256CryptoServiceProvider();
var digest = hasher.ComputeHash(byteData);
var signature = await _keyVault.SignAsync("https://myvault.vault.azure.net/keys/mykey/keyid", "RS256", digest);

return $"{header}.{token.EncodedPayload}.{Base64UrlEncoder.Encode(signature.Result)}";

I found another solution, which I didn't like as much but it "integrates" better with the JWT libraries.

var token = new JwtSecurityToken(
    issuer,
    appId,
    claims,
    signDate,
    expiryDate,
    new SigningCredentials(new KeyVaultSecurityKey("https://myvault.vault.azure.net/keys/mykey/keyid", new KeyVaultSecurityKey.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)), "RS256")
    {
        CryptoProviderFactory = new CryptoProviderFactory() { CustomCryptoProvider = new KeyVaultCryptoProvider() }
    });

var handler = new JwtSecurityTokenHandler();
return handler.WriteToken(token);

Turns out that there is a library Microsoft.IdentityModel.KeyVaultExtensions with extensions to SecurityToken and ICryptoProvider which support KeyVault.

My problems with it are

  1. I can't reuse an existing instance of KeyVaultClient with this solution.
  2. It's blocking (Behind the scenes, it calls .GetAwaiter().GetResult() on KeyVaultClient.SignAsync
like image 24
user10962730 Avatar answered Oct 29 '22 10:10

user10962730