When we decide to use JWT in our API’s and Frontend SPA, we need to use an algorithm when issuing a token. There are several options for subscribing to the JWT. It must be symmetrical or asymmetric. Probabilistic or deterministic. See in this article how to sign your JWT and tips on using them.
When generating a JWT, it is necessary to inform an encryption algorithm. See example:
// jws generator / jwe | |
var tokenHandler = new JsonWebTokenHandler (); | |
// Creating asymmetric key | |
var key = new RsaSecurityKey ( RSA . Create ( 2048 )); | |
// creating the jwt | |
var jwt = new SecurityTokenDescriptor | |
{ | |
Issuer = ” www.mysite.com ” , | |
Audience = ” your-spa ” , | |
// Details hide | |
SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . RsaSsaPssSha256 ) // <- Probabilistic algorithm | |
}; | |
// subscribing to jwt | |
string jws = tokenHandler . CreateToken ( jwt ); | |
Console . WriteLine ( jws ); |
view rawclaimGeneration.cs hosted with ❤ by GitHub
In addition to RSA it is possible to use symmetric keys and ECDSA.
To know how to choose the algorithm, it is necessary to understand where each one fits.
Algorithm
An encryption algorithm can be considered as a formula. It is a set of mathematical procedures. And through the use of this algorithm, the information is transformed into a Ciphertext (ciphertext) and requires the use of a key to transform the data in its original form.
JWT with symmetric key
When using a symmetric algorithm, a single key is used. Both to encrypt data and to decrypt. In that case, the two parties involved in a communication must have the key. Whether for reading, or for writing a new one.
HMAC
Hash-Based Message Authentication Codes (HMACs) are a group of algorithms that use a symmetric key to sign messages and their security is linked to the hash function used, for example, SHA256.
When to use a symmetric key?
Only in scenarios where there will be ONE API. Because, the other APIs will not have the ability to validate the JWS, unless the private key is shared between the API’s.
If you work on a small team, this risk may be tolerable. In medium and large teams it is a potential security breach.
After all, any team in possession of the private key can generate Tokens and impersonate permissions and users. Getting privileged access to your API’s.
The problem in the end is ensuring that your key is properly stored. It is only shared by trusted entities.
Generating a symmetric HMAC key
When generating a symmetric key you must ask yourself: Will only one API generate the JWT or will more services have this right? (There is no problem if this strategy changes in the future).
Why is the question important? When generating a Key, the size of the Bytes matters. There are recommendations on the size of the key.
NIST has published a Recommendation for Applications Using Approved Hash Algorithms, Security Effect of the HMAC Key Section – 5.3.4 document where it makes recommendations regarding the Key and the Algorithm that will be used.
“alg” | Algorithm | Key Size |
---|---|---|
HS256 | HMAC using SHA-256 | 64 bytes |
HS384 | HMAC using SHA-384 | 128 bytes |
HS512 | HMAC using SHA-512 | 128 bytes |
Like NIST, Microsoft itself reinforces the key size, links at the end.
Knowing this, it is possible to generate a key automatically with the .Net components. That way the only API can store the key somewhere. And recover every time you sign a JWT. This ensures security, once the record is removed, a new, completely random key will be generated.
Doing this way there will be more bureaucracy, because sharing the key means sharing the JWK. Whether through a physical file or through the database.
static void Main ( string [] args ) | |
{ | |
var tokenHandler = new JsonWebTokenHandler (); | |
SecurityKey key = null ; | |
// HMAC Key | |
key = AutoGeneratedHmac ( 64 ); | |
// Hmac Sha256 | |
Jwt . SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . HmacSha256 ); | |
Console . WriteLine ( $ ” { tokenHandler . CreateToken ( Jwt )} { Environment . NewLine } ” ); | |
// HMAC Sha 384 | |
key = AutoGeneratedHmac ( 128 ); | |
Jwt . SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . HmacSha384 ); | |
Console . WriteLine ( $ ” { tokenHandler . CreateToken ( Jwt )} { Environment . NewLine } ” ); | |
// Hmac Sha 512 | |
Jwt . SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . HmacSha512 ); | |
Console . WriteLine ( $ ” { tokenHandler . CreateToken ( Jwt )} { Environment . NewLine } ” ); | |
} | |
private static RandomNumberGenerator Rng = RandomNumberGenerator . Create (); | |
private static DateTime Now = DateTime . Now ; | |
private static SecurityTokenDescriptor Jwt = new SecurityTokenDescriptor | |
{ | |
Issuer = ” www.mysite.com ” , | |
Audience = ” your-spa ” , | |
IssuedAt = Now , | |
NotBefore = Now , | |
Expires = Now . AddHours ( 1 ), | |
Subject = new ClaimsIdentity ( new List < Claim > | |
{ | |
new Claim ( JwtRegisteredClaimNames . Email , ” meuemail@gmail.com ” , ClaimValueTypes . Email ), | |
new Claim ( JwtRegisteredClaimNames . GivenName , ” Bruno Brito ” ), | |
new Claim ( JwtRegisteredClaimNames . Sub , Guid . NewGuid (). ToString ()) | |
}) | |
}; | |
private static SecurityKey AutoGeneratedHmac ( int bytes ) | |
{ | |
return new SymmetricSecurityKey ( GenerateHmacKey ( bytes )); | |
} | |
private static byte [] GenerateHmacKey ( int bytes ) | |
{ | |
byte [] data = new byte [ bytes ]; | |
Rng . GetBytes ( data ); | |
return data ; | |
} |
view rawhmacautokey.cs hosted with ❤ by GitHub
In the example what is worth mentioning is the use of the object System.Security.Cryptography.RandomNumberGenerator
to generate the key.
This implementation above has a problem. If the application restarts the keys will renew, invalidating the previously generated tokens.
To solve this problem the object JsonWebKey
has the objective of saving the encryption parameters. So if the application is restarted, just check if that object exists and recover. Otherwise, create a new one.
static void Main ( string [] args ) | |
{ | |
var tokenHandler = new JsonWebTokenHandler (); | |
var now = DateTime . Now ; | |
var key = AutoGeneratedHmac ( 64 ); | |
var jwt = new SecurityTokenDescriptor | |
{ | |
Issuer = ” www.mysite.com ” , | |
Audience = ” your-spa ” , | |
IssuedAt = now , | |
NotBefore = now , | |
Expires = now . AddHours ( 1 ), | |
Subject = new ClaimsIdentity ( new List < Claim > | |
{ | |
new Claim ( JwtRegisteredClaimNames . Email , ” meuemail@gmail.com ” , ClaimValueTypes . Email ), | |
new Claim ( JwtRegisteredClaimNames . GivenName , ” Bruno Brito ” ), | |
new Claim ( JwtRegisteredClaimNames . Sub , Guid . NewGuid (). ToString ()) | |
}), | |
SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . HmacSha256 ) | |
}; | |
Console . WriteLine ( $ ” { tokenHandler . CreateToken ( jwt )} { Environment . NewLine } ” ); | |
var jws = tokenHandler . CreateToken ( jwt ); | |
// Store HMAC as Filesystem, recover and test if it’s valid | |
var jwk = JsonWebKeyConverter . ConvertFromSymmetricSecurityKey ( key ); | |
jwk . KeyId = Guid . NewGuid (). ToString (); | |
File . WriteAllText ( ” current.key ” , JsonConvert . SerializeObject ( jwk )); | |
// Now recover and verify if still valid | |
var storedJwk = JsonConvert . DeserializeObject < JsonWebKey > ( File . ReadAllText ( ” current.key ” )); | |
var validationResult = tokenHandler . ValidateToken ( jws , new TokenValidationParameters | |
{ | |
ValidIssuer = ” www.mysite.com ” , | |
ValidAudience = ” your-spa ” , | |
IssuerSigningKey = storedJwk | |
}); | |
Console . WriteLine ( validationResult . IsValid ); | |
} | |
private static SymmetricSecurityKey AutoGeneratedHmac ( int bytes ) | |
{ | |
return new SymmetricSecurityKey ( GenerateHmacKey ( bytes )); | |
} | |
private static RandomNumberGenerator Rng = RandomNumberGenerator . Create (); | |
private static byte [] GenerateHmacKey ( int bytes ) | |
{ | |
byte [] data = new byte [ bytes ]; | |
Rng . GetBytes ( data ); | |
return data ; | |
} |
view rawjsonwebkey.cs hosted with ❤ by GitHub
Note that the code uses the component JsonWebKey, which we covered in another article: JWT components (JWS, JWE, JWA, JWK, JWKS) .
JWT with asymmetric key
An asymmetric algorithm involves two keys. A public key and another private key. While a (private) key is used to digitally sign the message, another (public) key can only be used to verify the authenticity of the signature.
The RFC 7518 defines the RSA and ECDSA algorithms to sign a JWT. There are several variations of RSA and ECDsa. The examples will use those most recommended by RFC 7518.
RSA
RSA is the acronym for Rivest – Shamir – Adleman. It is a cryptography created in 1977. And it revolutionized the methods used at the time. Because it introduced the concept of asymmetric keys.
Until then, encryption models use the same key to encrypt and decrypt the message.
Elliptic Curves – ECDsa
There are several technical explanations about ECC ( Elliptic Curve Cryptography ). Links below for curious. In practice, EC is better and more efficient than RSA. Experts and experts on the subject say that the ECC has revolutionized asymmetric algorithms.
When to use an asymmetric key?
The best answer would be, whenever possible. However, in environments that have a single API. That there is no URI to query the public key. It is not an OAuth 2.0 server. It won’t be a big differentiator.
But consider that if you have adopted the strategy of the previous example. And it started to save the key (JWK) in some place like Filesystem, database or Blob. Switching to asymmetric keys will provide an additional level of security.
If you are in an OAuth 2.0 environment. The use of asymmetric keys is mandatory, due to the nature of the OAuth 2.0 specification.
Generating RSASSA-PSS using SHA-256 and MGF1 with SHA-256
Consider the following code:
public static void Run () | |
{ | |
var tokenHandler = new JsonWebTokenHandler (); | |
var key = new RsaSecurityKey ( RSA . Create ( 2048 )) | |
{ | |
KeyId = Guid . NewGuid (). ToString () | |
}; | |
Jwt . SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . RsaSsaPssSha256 ); | |
Console . WriteLine ( $ ” { tokenHandler . CreateToken ( Jwt )} { Environment . NewLine } ” ); | |
} | |
private static DateTime Now = DateTime . Now ; | |
private static SecurityTokenDescriptor Jwt = new SecurityTokenDescriptor | |
{ | |
Issuer = ” www.mysite.com ” , | |
Audience = ” your-spa ” , | |
IssuedAt = Now , | |
NotBefore = Now , | |
Expires = Now . AddHours ( 1 ), | |
Subject = new ClaimsIdentity ( new List < Claim > | |
{ | |
new Claim ( JwtRegisteredClaimNames . Email , ” meuemail@gmail.com ” , ClaimValueTypes . Email ), | |
new Claim ( JwtRegisteredClaimNames . GivenName , ” Bruno Brito ” ), | |
new Claim ( JwtRegisteredClaimNames . Sub , Guid . NewGuid (). ToString ()) | |
}) | |
}; | |
private static TokenValidationParameters TokenValidationParams = new TokenValidationParameters | |
{ | |
ValidIssuer = ” www.mysite.com ” , | |
ValidAudience = ” your-spa ” , | |
}; |
view rawcreatersa.cs hosted with ❤ by GitHub
- The size
2048
is the minimum specified by RFC 7518 – section 3.5.
Using RSA through .NET is very simple. Using the same technique as the previous example. It is possible to save the data of the created key and thus reload the same key when the application restarts.
public static void Run () | |
{ | |
var tokenHandler = new JsonWebTokenHandler (); | |
var key = new RsaSecurityKey ( RSA . Create ( 2048 )) | |
{ | |
KeyId = Guid . NewGuid (). ToString () | |
}; | |
Jwt . SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . RsaSsaPssSha256 ); | |
var lastJws = tokenHandler . CreateToken ( Jwt ); | |
Console . WriteLine ( $ ” { lastJws } { Environment . NewLine } ” ); | |
// Store in filesystem | |
// Store HMAC as Filesystem, recover and test if it’s valid | |
var jwk = JsonWebKeyConverter . ConvertFromRSASecurityKey ( key ); | |
File . WriteAllText ( ” current-rsa.key ” , JsonConvert . SerializeObject ( jwk )); | |
var storedJwk = JsonConvert . DeserializeObject < JsonWebKey > ( File . ReadAllText ( ” current-rsa.key ” )); | |
TokenValidationParams . IssuerSigningKey = storedJwk ; | |
var validationResult = tokenHandler . ValidateToken ( lastJws , TokenValidationParams ); | |
Console . WriteLine ( validationResult . IsValid ); | |
} |
view rawstorersa.cs hosted with ❤ by GitHub
Simple, right?
Generating ECDSA using P-256 and SHA-256
This is the most recommended algorithm by the RFC to use when signing your JWT.
Like RSA, its use is simple.
private static DateTime Now = DateTime . Now ; | |
private static SecurityTokenDescriptor Jwt = new SecurityTokenDescriptor | |
{ | |
Issuer = ” www.mysite.com ” , | |
Audience = ” your-spa ” , | |
IssuedAt = Now , | |
NotBefore = Now , | |
Expires = Now . AddHours ( 1 ), | |
Subject = new ClaimsIdentity ( new List < Claim > | |
{ | |
new Claim ( JwtRegisteredClaimNames . Email , ” meuemail@gmail.com ” , ClaimValueTypes . Email ), | |
new Claim ( JwtRegisteredClaimNames . GivenName , ” Bruno Brito ” ), | |
new Claim ( JwtRegisteredClaimNames . Sub , Guid . NewGuid (). ToString ()) | |
}) | |
}; | |
private static TokenValidationParameters TokenValidationParams = new TokenValidationParameters | |
{ | |
ValidIssuer = ” www.mysite.com ” , | |
ValidAudience = ” your-spa ” , | |
}; | |
public static void Run () | |
{ | |
var tokenHandler = new JsonWebTokenHandler (); | |
var key = new ECDsaSecurityKey ( ECDsa . Create ( ECCurve . NamedCurves . nistP256 )) | |
{ | |
KeyId = Guid . NewGuid (). ToString () | |
}; | |
Jwt . SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . EcdsaSha256 ); | |
var lastJws = tokenHandler . CreateToken ( Jwt ); | |
Console . WriteLine ( $ ” { lastJws } { Environment . NewLine } ” ); | |
} |
view rawecdsaexample.cs hosted with ❤ by GitHub
The Nist P256 curve is specified in the RFC itself, so it is being used.
To save to disk, or elsewhere, there is a small detail. The class method JsonWebKeyConverter
used in the previous examples is not available fornetstandard2.1. Because of a bug in the library Microsoft.IdentityModel.JsonWebTokens
. Already reported on Github .
However, it is very simple to parse manually.
public static void Run () | |
{ | |
var tokenHandler = new JsonWebTokenHandler (); | |
var key = new ECDsaSecurityKey ( ECDsa . Create ( ECCurve . NamedCurves . nistP256 )) | |
{ | |
KeyId = Guid . NewGuid (). ToString () | |
}; | |
Jwt . SigningCredentials = new SigningCredentials ( key , SecurityAlgorithms . EcdsaSha256 ); | |
var lastJws = tokenHandler . CreateToken ( Jwt ); | |
Console . WriteLine ( $ ” { lastJws } { Environment . NewLine } ” ); | |
// Store in filesystem | |
// Store HMAC as Filesystem, recover and test if it’s valid | |
var parameters = key . ECDsa . ExportParameters ( true ); | |
var jwk = new JsonWebKey () | |
{ | |
Kty = JsonWebAlgorithmsKeyTypes . EllipticCurve , | |
Use = ” sig ” , | |
Kid = key . KeyId , | |
KeyId = key . KeyId , | |
X = Base64UrlEncoder . Encode ( parameters . Q . X ) | |
Y = Base64UrlEncoder . Encode ( parameters . Q . Y ), | |
D = Base64UrlEncoder . Encode ( parameters . D ), | |
Crv = JsonWebKeyECTypes . P256 , | |
Alg = ” ES256 “ | |
}; | |
File . WriteAllText ( ” current-ecdsa.key ” , JsonConvert . SerializeObject ( jwk )); | |
var storedJwk = JsonConvert . DeserializeObject < JsonWebKey > ( File . ReadAllText ( ” current-ecdsa.key ” )); | |
TokenValidationParams . IssuerSigningKey = storedJwk ; | |
var validationResult = tokenHandler . ValidateToken ( lastJws , TokenValidationParams ); | |
Console . WriteLine ( validationResult . IsValid ); | |
} |
view rawecdsastore.cs hosted with ❤ by GitHub
Jwks.Manager component
Instead of doing each of these algorithms on the arm, choose where to save. I recommend using the Jwks.Manager component that will not only generate the JWK, but also manage it and eventually expire after a predetermined time.
Download
The project code is available on my GitHub
Conclusion
Many things about JWT and OAuth 2.0 are complex in theory. However, this complexity is abstracted through the various components available.
I hope this article helps you to understand and also improve the security of your API’s.
Want to learn more? Check out this post: Calling Microsoft Graph from your Teams application
References
- HMAC256 Constructor – Key Size recommendation
- HMAC384 Constructor – Key Size recommendation
- HMAC512 Constructor – Key Size recommendation
- RFC 7518
- A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography
- Serious Cryptography: A Practical Introduction to Modern Encryption
About the Author:
First of all, I’m a father! #dev fullStack and MCSA Web Application.
Reference:
Brito, B. (2020). ASP.NET Core – How to digitally sign your JWT. Available at: https://www.brunobrito.net.br/jwt-assinaura-digital-rsa-ecdsa-hmac/
Take our Survey:
Can you take 4 minutes today and help us gain insights, with this short survey for the Microsoft 365 & Azure Community? We would love to hear from you and invite you to share your thoughts.