ASP.NET Core – How to digitally sign your JWT

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.

JWT

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”AlgorithmKey Size
HS256HMAC using SHA-25664 bytes
HS384HMAC using SHA-384128 bytes
HS512HMAC using SHA-512128 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.RandomNumberGeneratorto 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 JsonWebKeyhas 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.

Asymmetric Encryption

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.

RSA is widely used to create digital signatures. The owner of the private key is the only one capable of signing a message. While the public key allows any entity to verify the validity of the signature.

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 2048is 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 JsonWebKeyConverterused 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.

References

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/

Share this on...

Rate this Post:

Share:

Topics:

General

Tags: