Storing password in a signed and encrypted JWT

JWT

The code below demonstrates how we can avoid caching password or some other sensitive data in memory, and instead pass it back to the client in an encrypted format. It generates a JWT that is both signed and encrypted with an RSA key pair. The key itself is bound to a X509Certificate in a user certificates store. For some reason this certificate has to be set as exportable, so it won't work with a smart card e.g.

Get the RSA key


public static Lazy<X509Certificate2> JwtCertificate = new(() =>
{
	string certString = ConfigurationManager.AppSettings["jwt.certificate"];
    
    X509Store xstore = new(StoreName.My, StoreLocation.CurrentUser);
    
    
    xstore.Open(OpenFlags.MaxAllowed);
    X509Certificate2 xcer = xstore.Certificates.Find(X509FindType.FindByThumbprint, certString, false)
    	.OfType<X509Certificate2>()
        .FirstOrDefault();
        
    return xcer;
}

Generate the JWT


public static string GenerateToken(string userId, string password, DateTime exp)
{
	RsaSecurityKey encryptionKey = new(JwtCertificate.Value
    	.GetRSAPrivateKey());
        
    RsaSecurityKey signingKey = new(JwtCertificate.Value
        .GetRSAPrivateKey());

    EncryptingCredentials encryptionCredentials = new(encryptionKey,
         SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512);

    SigningCredentials signingCredentials = new
         (signingKey, SecurityAlgorithms.RsaSha256Signature);

    JwtSecurityTokenHandler tokenHandler = new();

    SecurityTokenDescriptor tokenDescriptor = new ()
    {
         Subject = new ClaimsIdentity(new Claim[]
         {
         	new Claim(ClaimTypes.Name, userId),
         }),
         Claims = new Dictionary<string, object>()
         {
            [JwtRegisteredClaimNames.Sub] = userId,
            ["pwd"] = password
         },
         Expires = exp,
         SigningCredentials = signingCredentials,
         EncryptingCredentials = encryptionCredentials
    };

    SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);

    string result = tokenHandler.WriteToken(token);

    return result;
}

Validate the JWT


public static (string userId, string password) ParseToken(string jwt)
{
	JwtSecurityTokenHandler tokenHandler = new();
    
    RsaSecurityKey encryptionKey = new(JwtCertificate.Value
    	.GetRSAPrivateKey().ExportParameters(true));

    RsaSecurityKey signingKey = new(JwtCertificate.Value
         .GetRSAPrivateKey());

    TokenValidationParameters validations = new ()
    {
    	ValidateIssuerSigningKey = true,
        IssuerSigningKey = signingKey,
        TokenDecryptionKey = encryptionKey,
        ValidateIssuer = false,
        ValidateAudience = false,
    };
            
            
    ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwt, validations, out SecurityToken securityToken);

    JwtSecurityToken jwtSecurityToken = securityToken as JwtSecurityToken;

    string userId = jwtSecurityToken.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
    string pwd = jwtSecurityToken.Claims.FirstOrDefault(c => c.Type == "pwd")?.Value;

    return (userId, pwd);

}

Also reference my previous explanation of configuriring JWT with ASP .NET core, so you can encrypt sensitive data there as well: https://alexrait.blogspot.com/2021/08/configuring-jwt-authentication-with.html

Post a Comment

Previous Post Next Post