In this article we will see how we can create and sign a JWT token with the RS256 algorithm. This function is complementary to the validate function I posted some time ago.
Here is the Sign(...)
function that can create a RS256 signed JWT token. It makes use of the BouncyCastle library. It is available as a NuGet package with version 1.8.1.
using System; using System.Collections.Generic; using System.Text; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using Newtonsoft.Json; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.Pkcs; public string Sign(string payload, string privateKey) { List segments = new List(); var header = new { alg = "RS256", typ = "JWT" }; DateTime issued = DateTime.Now; DateTime expire = DateTime.Now.AddHours(10); byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None)); byte[] payloadBytes = Encoding.UTF8.GetBytes(payload); segments.Add(Base64UrlEncode(headerBytes)); segments.Add(Base64UrlEncode(payloadBytes)); string stringToSign = string.Join(".", segments.ToArray()); byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign); byte[] keyBytes = Convert.FromBase64String(privateKey); var privKeyObj = Asn1Object.FromByteArray(keyBytes); var privStruct = RsaPrivateKeyStructure.GetInstance((Asn1Sequence)privKeyObj); ISigner sig = SignerUtilities.GetSigner("SHA256withRSA"); sig.Init(true, new RsaKeyParameters(true, privStruct.Modulus, privStruct.PrivateExponent)); sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length); byte[] signature = sig.GenerateSignature(); segments.Add(Base64UrlEncode(signature)); return string.Join(".", segments.ToArray()); }
Here are some helper functions used in the above snippet.
// from JWT spec private static string Base64UrlEncode(byte[] input) { var output = Convert.ToBase64String(input); output = output.Split('=')[0]; // Remove any trailing '='s output = output.Replace('+', '-'); // 62nd char of encoding output = output.Replace('/', '_'); // 63rd char of encoding return output; } private static byte[] Base64UrlDecode(string input) { var output = input; output = output.Replace('-', '+'); // 62nd char of encoding output = output.Replace('_', '/'); // 63rd char of encoding switch (output.Length % 4) // Pad with trailing '='s { case 0: break; // No pad chars in this case case 1: output += "==="; break; // Three pad chars case 2: output += "=="; break; // Two pad chars case 3: output += "="; break; // One pad char default: throw new System.Exception("Illegal base64url string!"); } var converted = Convert.FromBase64String(output); // Standard base64 decoder return converted; }
The helper functions are the same ones found in the validate function.
This function is based on the code snippet found in this SO question.
Update 1: You can check this post here, where I have created a C# library that manages Jwt tokens.
Update 2: If you are having trouble making your keys work, have a look in my Check your RSA private and public keys post and make sure to check the Additional Resources section as well
Where is the Base64Encoding() definition?
Hi Murali, you can find the helper functions in this post.
Hi, can you give a simple example of how you generate the private key?
Hi Fabio, I used openssl to generate the private and public keys
Private key:
Public key:
Check also this post: OpenSSL Command Cheatsheet
Base64UrlEncode function is not there? Could you please explain whether it is built in function and I am missing to include any reference or it is custom function and You have missed to put in your code.
Hi Raju, thanks for noticing! I updated the code accordingly.
How can i verify using public key? with RSA384 algorithm? Any help is appreciated.
Hi Rushal,
You can use the code here with RS384. Just replace 256 to 384 and it should work. Also check here to see how you can verify using the public key.
Hi Tasos,
I’m encountering below error on this line:
Hi Romeo,
Though an older question, I’d gotten the same error, I don’t know why, I think that some deprecated code.
I changed this:
For this:
And it worked fine for me.
Hi Eduardo,
Thanks for your reply.
I tried your change and I am getting the following error when calling
Which version of BouncyCastle are you using? I checked my code with 1.8.2 and 1.8.5 and it is working without issues.
Hi _tasos,
Before all, congratulation for your post, it help me a lot !!!
I’m using 1.8.4 and getting error invalid cast, I’ll try 1.8.5 version.
Thank you, I am glad you found it useful!
Now, regarding the issue, I am guessing that the private key might be different, thus the need to load it with a different way. I am wondering if you have the openssl command that generates you private key so that I can test against my code.
byte[] keyBytes = Convert.FromBase64String(privateKey);
I’m encountering error on this line. My private key is in pem format but it says unable to convert using base 64. How did you guys resolve this?
Hi _tasos,
How to define payload with multiple values like:
payload = {
“iss”: issuer,
“sub”: client_id,
“aud”: aud
}
Hi Raheel,
The payload can be anything. If you need it to be a JSON object, you’ll need to serialize it. Have a look here for examples.
Hi,
I’m getting a “unknown tag 13 encountered”, online it seem the private key needs to be in DER format, I only have a private key —Begin…End— I tried removing those lines along with “/n” but that then give me “Extra data found at the end…”
It seems to be failing at var privKeyObj = Asn1Object.FromByteArray(keyBytes);
Hey Alex,
Sadly, I am not familiar with the DER format. The line that fails indicates that is a compatibility issue. You may be able to convert it to PEM format. Check out this SO question, maybe you be able to convert it.
Hi,
do you have sample code to decode the JWT created using above code.
Thanks
Hi Rehman,
You can checkout this post
This was incredibly helpful to me as I have been struggling to get a proper signed JWT using the built in .net crypto functions. My company is using an older version of .net and for reasons unknown none of the online info on how to assymetrically sign JWT’s were working. I used the above code and generated the keys using the link above and it all worked first time. Thanks so much!
Hello,
Have you encountered such a problem?
Unable to cast object of type ‘Org.BouncyCastle.Asn1.DerSequence’ to type ‘Org.BouncyCastle.Asn1.DerInteger’
If yes, how did you get over it?