My sample project
Summary
I have created a sample project to test the issuing of JWT tokens in an ASP.Net Core applications.
Refer to the Github repo above for the full sample.
In the JwtService.cs class, there is a method called GenerateSecurityToken, which generates the Jwt token.
The Login method of the AccountController calls the JwtService class to generate the token, saves the token in a cookie and also sets another cookie for the user id.
Note: The token is signed with a secret, which is also appended with a user specific salt. The user salt can be invalidated at any point, making the user's token invalid.
The token is also encrypted with an encryption key. This makes the body of the token illegible when inspected in JWT.io. I believe it is called a JWE.
This question discusses the signing and encrypting order.
Issue
I do not want the bearer or any third party to inspect the content of the token. The token's purpose in authentication.
My sample code intercepts the authentication pipeline, and uses the email from the cookie to pull user roles out of the database and creates role claims out of the roles.
A few of you may have figured out the information leak caused by issuing a separate cookie with the email.
Ideally, I want to ONLY issue the JWE token in the cookie.
I want to intercept the authentication pipeline, decrypt the token, use the email claim to get the user salt (from the db) and validate the token.
I have read the documentation, but cannot figure out a way to decrypt the token.
To be honest, I am not even sure of the order of operation (sign then encrypt or encrypt then sign), when the token is issued.
If anyone could even point me to the source code for JwtSecurityTokenHandler that might be a good start.
TIA
public string GenerateSecurityToken(string email, byte[] salt, string[] roles)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_secret).Concat(salt).ToArray();
byte[] ecKey = new byte[256 / 8];
Array.Copy(Encoding.ASCII.GetBytes(_ecKey), ecKey, 256 / 8);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _issuer,
Audience = _audience,
Subject = new ClaimsIdentity(
new List<Claim>
{
new Claim(ClaimTypes.Email, email)
}),
// .Concat(roles.Select(r => new Claim(ClaimTypes.Role, r))).ToArray()),
Expires = DateTime.UtcNow.AddMinutes(double.Parse(_expDate)),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
EncryptingCredentials = new EncryptingCredentials(
new SymmetricSecurityKey(
ecKey),
SecurityAlgorithms.Aes256KW,
SecurityAlgorithms.Aes256CbcHmacSha512)
};
var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
While you are very interested in hashing the JWT, I recommend you have four methods:
To generates the token (Sequence is important):
GenerateToken
HashToken
Then when you receive the bearer token with the request (Sequence is important):
DeHashToken
ValidateToken
As both processes, JWT generating and hashing are very straightforward:
To get the best results, forget about the hashing and focus on Generate Token and Validate Token.
After you get success with JWT, in a separate project, focus on Hashing and DeHashing any text (JWT or something else).
When you succeed with both, don't call each other from each other.
Build new Methods like
GenerateAndHash (It calls both GenerateToken and HashToken)
VerfyHashAndValidateToken (It calls both DeHashToken and ValidateToken)
To Generate and Validate JWT step by step see this (The title of the article could be confusing but it talks about JWT in .NET Core in a very simple way especially in part 2 I think)
https://www.codemag.com/Article/1805021/Security-in-Angular-Part-1
https://www.codemag.com/Article/1809031/Security-in-Angular-Part-2
https://www.codemag.com/Article/1811031/Security-in-Angular-Part-3
To Hash
public static string ToCustomHash(this string text)
{
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
var pbkdf2 = new Rfc2898DeriveBytes(text, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
var hashedToBase64 = Convert.ToBase64String(hashBytes);
return hashedToBase64;
}
To Verify The hash
public static bool VerifyHashWith(this string storedPassword, string loginPassword)
{
byte[] hashBytes = Convert.FromBase64String(storedPassword);
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
var pbkdf2 = new Rfc2898DeriveBytes(loginPassword, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
for (int i = 0; i < 20; i++)
{
if (hashBytes[i + 16] != hash[i]) { return false; }
}
return true;
}
Related
I am using the following code, which I borrowed originally from the jwt-dotnet github page
private static string CreateToken(UserPrincipal principal)
{
/*
* https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
* http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
*/
var key = ConfigurationManager.AppSettings["jwt-key"];
var claims = new Dictionary<string, string>()
{
{ClaimTypes.Name, "Rainbow Dash" },
{ClaimTypes.WindowsAccountName, "RDash"}
};
var algorithm = new HMACSHA256Algorithm();
var serializer = new JsonNetSerializer();
var urlEncoder = new JwtBase64UrlEncoder();
var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(claims, key);
return token;
}
The above code generates the following token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUmFpbmJvdyBEYXNoIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy93aW5kb3dzYWNjb3VudG5hbWUiOiJSRGFzaCJ9.5WZWDJ0pvTe6QLjVNUeTfZicX_wSsk1dtYvXUbpiOiw
So, I hopped over to jwt.io to test my token. I'm told I have an invalid signature.
How do I give it a valid 'signature'? I don't understand what my JWT is missing.
The tool over JWT.io can verify the digital signature of your token if you give it the secret signing key you used while creating a token:
And from looking at your code it's the value contained in your:
ConfigurationManager.AppSettings["jwt-key"];
Just input the value inside the "secret" text box and if the signature of the token matches the one calculated by JWT.io then you'll get a message saying that the signature is valid.
I am using the following code, which I borrowed originally from the jwt-dotnet github page
private static string CreateToken(UserPrincipal principal)
{
/*
* https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
* http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
*/
var key = ConfigurationManager.AppSettings["jwt-key"];
var claims = new Dictionary<string, string>()
{
{ClaimTypes.Name, "Rainbow Dash" },
{ClaimTypes.WindowsAccountName, "RDash"}
};
var algorithm = new HMACSHA256Algorithm();
var serializer = new JsonNetSerializer();
var urlEncoder = new JwtBase64UrlEncoder();
var encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(claims, key);
return token;
}
The above code generates the following token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiUmFpbmJvdyBEYXNoIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy93aW5kb3dzYWNjb3VudG5hbWUiOiJSRGFzaCJ9.5WZWDJ0pvTe6QLjVNUeTfZicX_wSsk1dtYvXUbpiOiw
So, I hopped over to jwt.io to test my token. I'm told I have an invalid signature.
How do I give it a valid 'signature'? I don't understand what my JWT is missing.
The tool over JWT.io can verify the digital signature of your token if you give it the secret signing key you used while creating a token:
And from looking at your code it's the value contained in your:
ConfigurationManager.AppSettings["jwt-key"];
Just input the value inside the "secret" text box and if the signature of the token matches the one calculated by JWT.io then you'll get a message saying that the signature is valid.
For token based authentication Microsoft.IdentityModel.Tokens provides a list of security algorithms that can be used to create SigningCredentials:
string secretKey = "MySuperSecretKey";
byte[] keybytes = Encoding.ASCII.GetBytes(secretKey);
SecurityKey securityKey = new SymmetricSecurityKey(keybytes);
SigningCredentials signingCredentials =
new SigningCredentials(securityKey,
SecurityAlgorithms.HmacSha256);
SigningCredentials signingCredentials =
new SigningCredentials(securityKey,
SecurityAlgorithms.HmacSha256Signature);
What is the difference between HmacSha256 and HmacSha256Signature? When would you use the signature one instead of the non-signature one?**
There are other "non signature" and "signature" algorithms as well. For example, RsaSha256 and RsaSha256Signature
HmacSha256 is a string constant evaluating to "HS256". HmacSha256Signature is also a string constant but evaluates to "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"
The latest definition of System.IdentityModel.Tokens.SecurityAlgorithms does not include HmacSha256 but instead allows you to separate the signature and digest algorithms for the SigningCredentials.
You should use HmacSha256Signature for future-proofing your application as HmacSha256 looks deprecated.
From the Microsoft docs...
The members that have a Signature suffix can be used to specify the
signatureAlgoritm parameter and the members that have a Digest suffix
can be used to specify the digestAlgorithm parameter.
How can I (can I?) use X509SecurityKey for Asp.Net Core JWT validation?
My current code is roughly:
X509SecurityKey signingKey = null;
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var v = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
var v1 = v.Find(X509FindType.FindBySubjectDistinguishedName, strCertName, true);
signingKey = new X509SecurityKey(v1[0]);
}
and later on for the signing credentials...
new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
This causes an exception:
SignatureAlgorithm: 'HS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.X509SecurityKey'
is not supported.
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
I tried a few algorithms, but it doesn't seem like it works with any of them?
I tried a few algorithms, but it doesn't seem like it works with any of them?
You're trying to use an asymmetric key (embedded in a X.509 certificate) with a HMAC algorithm (that we often abusively call "symmetric signature algorithm"): this cannot work.
Assuming your certificate is a RSA certificate, you should be able to use SecurityAlgorithms.RsaSha256.
var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256)
I am building login workflow using Google for user. Once user is authenticated, I call GetAuthResponse to get idToken.
https://developers.google.com/identity/sign-in/web/backend-auth
Now, I need to verify certificate against Google certificate. I am using JwtSecurityToken(C#) for the same.
I am referencing for verification - http://blogs.msdn.com/b/alejacma/archive/2008/06/25/how-to-sign-and-verify-the-signature-with-net-and-a-certificate-c.aspx
Issue is - I always gets false from VerifyHash. As, VerifyHash returns just false without any reason, I am not able to find way to verify whether idToken is
valid or not. My code is given below
String strID = ""; // idToken received from Google AuthResponse
JwtSecurityToken token = new JwtSecurityToken(strID);
byte[] text = GetHash(token.RawData);
SHA256Cng sha1 = new SHA256Cng();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
byte[] signature = Encoding.Unicode.GetBytes(token.RawSignature);
// Modulus and exponent value from https://www.googleapis.com/oauth2/v2/certs - second set of keys
String modulus = "uHzGq7cMlx21nydbz9VsW1PItetb9mqvnpLp_8E3Knyk-mjv9DlaPhKGHYlJfHYGzKa2190C5vfsLLb1MIeGfdAv7ftpFsanIWawl8Zo0g-l0m7T2yG_7XerqcVK91lFifeJtgxKI86cPdZkgRy6DaYxMuAwAlhvpi3_UhPvsIwi7M6mxE8nUNpUWodh_YjJNu3wOxKDwbBZuRV2itjY6Z7RjFgJt1CsKF-QjqSVvWjAl0LaCaeMS_8yae0ln5YNeS8rAb6xkmcOuYeyhYsiBzwLRvgpXzEVLjLr631Z99oUHTpP9vWJDpGhfkrClkbmdtZ-ZCwX-eFW6ndd54BJEQ==";
String exponent = "AQAB";
modulus = modulus.Replace('-', '+').Replace('_', '/'); // Else it gives Base64 error
StringBuilder sb = new StringBuilder();
sb.Append("<RSAKeyValue>");
sb.Append("<Modulus>");
sb.Append(modulus);
sb.Append("</Modulus>");
sb.Append("<Exponent>");
sb.Append(exponent);
sb.Append("</Exponent>");
sb.Append("</RSAKeyValue>");
RSACryptoServiceProvider RSAVerifier = new RSACryptoServiceProvider();
RSAVerifier.FromXmlString(sb.ToString());
// Verify the signature with the hash
return RSAVerifier.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature);
You might want to try as done in the Google+ Token Verification project - this fork includes a few minor updates that are still in review.
An alternative approach is to just verify the tokens using Google's token verification endpoints:
curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjkyNGE0NjA2NDgxM2I5YTA5ZmFjZGJiNzYwZGI5OTMwMWU0ZjBkZjAifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwNTcwOTc3MjI2ODMwNTc3MjMwIiwiYXpwIjoiMzY0MzgxNDQxMzEwLXRuOGw2ZnY2OWdnOGY3a3VjanJhYTFyZWpmaXRxbGpuLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXRfaGFzaCI6IlAzLU1HZTdocWZhUkZ5Si1qcWRidHciLCJhdWQiOiIzNjQzODE0NDEzMTAtdG44bDZmdjY5Z2c4ZjdrdWNqcmFhMXJlamZpdHFsam4uYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJjX2hhc2giOiJjd3hsdXBUSkc4N2FnbU1pb0tSYUV3IiwiaWF0IjoxNDM0NDcyODc2LCJleHAiOjE0MzQ0NzY0NzZ9.Gz_WljZOV9NphDdClakLstutEKk65PNpEof7mxM2j-AOfVwh-SS0L5uxIaknFOk4-nDGmip42vrPYgNvbQWKZY63XuCs94YQgVVmTNCTJnao1IavtrhYvpDqGuGKdEB3Wemg5sS81pEthdvHwyxfwLPYukIhT8-u4ESfbFacsRtR77QRIOk-iLJAVYWTROJ05Gpa-EkTunEBVmZyYetbMfSoYkbwFKxYOlHLY-ENz_XfHTGhYhb-GyGrrw0r4FyHb81IWJ6Jf-7w6y3RiUJik7kYRkvnFouXUFSm8GBwxsioi9AAkavUWUk27s15Kcv-_hkPXzVrW5SvR1zoTI_IMw