I'm trying to generate JWT token in .NET. At first, I tried to use "System.IdentityModel.Tokens.Jwt" but it was causing an issue during the validation of the token, so I switched to "jose-jwt". Even though I can create and validate a token with this piece of code:
private byte[] GetBytes(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
public string Login(LoginInformation credential)
{
var payload = new Dictionary<string, object>()
{
{ "sub", "mr.x#contoso.com" },
{ "exp", 1300819380 }
};
var secretKey = GetBytes("myawesomekey");
string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
string json = JWT.Decode(token, secretKey);
return json;
}
I have an issue when I try to test the generated token with the site "https://jwt.io/". Indeed, I copy/paste the generated token, I enter "myawesomekey" as the key but it keeps telling me "invalid signature".
I could just ignore that (as the decoding in my C# code works), but I'm quite curious and I'd like to know how come the decoding via the site fails. The only idea I have is that, in the C# code, I have to pass the key as a byte array, so maybe it's not valid to just pass "myawesomekey" to the site.
You're getting the bytes incorrectly for the key:
var payload = new Dictionary<string, object>()
{
{ "sub", "mr.x#contoso.com" },
{ "exp", 1300819380 }
};
var secretKey = Encoding.UTF8.GetBytes("myawesomekey");
string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
return token;
Works fine. This is probably also the cause of your problem with System.IdentityModel.Tokens.Jwt.
Related
I am trying to integrate an existing application with Google's OAuth2 system to make more secure calls to Google Services such as GMail and move away from the standard SMTP client sending techniques in C#. I have set up a service account and obtained a P12 key and everything is in place and the calls are going to the OAuth server but I keep getting the following JSON response back when the call to 'GetResponse' is made:
{"error":"invalid_grant","error_description":"Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim."}
Below is the C# code I am using to make this call and I also get the same results via CURL. Every time I fiddle with "iap" and "exp" values either using local or UTC times I get different messages back. It seem that the code I have now is what I should be using but I cannot determine why I'm still getting these messages. I have made sure, according to Google Documentation, that the system clock is in sync with the Google NTP of time.google.com, but still no change. I am doing something wrong with the construction of the JSON Web Token (JWT) when making the web request call?
try
{
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
List<string> jwtSegments = new List<string>();
var jwtHeader = new {
alg = "RS256",
typ = "JWT"
};
int nowTimeInEpochSeconds = (int)Math.Floor((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds);
var jwtClaimSet = new {
iss = "***********#********.iam.gserviceaccount.com",
scope = "https://www.googleapis.com/auth/gmail.send",
aud = "https://oauth2.googleapis.com/token",
exp = nowTimeInEpochSeconds + 3600,
iat = nowTimeInEpochSeconds
};
string serializedHeader = JsonConvert.SerializeObject(jwtHeader, Formatting.None);
string serializedClaimSet = JsonConvert.SerializeObject(jwtClaimSet, Formatting.None);
byte[] jwtHeaderBytes = Encoding.UTF8.GetBytes(serializedHeader);
byte[] jwtClaimSetBytes = Encoding.UTF8.GetBytes(serializedClaimSet);
string base64EncodedHeader = Base64UrlEncode(jwtHeaderBytes);
string base64EncodedClaimSet = Base64UrlEncode(jwtClaimSetBytes);
jwtSegments.Add(base64EncodedHeader);
jwtSegments.Add(base64EncodedClaimSet);
string jwtRequestToSign = string.Join(".", jwtSegments);
byte[] jwtRequestBytesToSign = Encoding.UTF8.GetBytes(jwtRequestToSign);
X509Certificate2 cert = new X509Certificate2(#"C:\**************.p12", "notasecret");
AsymmetricAlgorithm rsaSignature = cert.PrivateKey;
byte[] signature = rsaCryptoServiceProvider.SignData(jwtRequestBytesToSign, "SHA256");
string jwt = jwtRequestToSign + "." + Base64UrlEncode(signature);
WebRequest webRequestForaccessToken = WebRequest.Create("https://oauth2.googleapis.com/token") as HttpWebRequest;
string accessTokenRequestParameters = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + jwt;
byte[] accessTokenRequestFromJwt = Encoding.UTF8.GetBytes(accessTokenRequestParameters);
string token = string.Empty;
if (webRequestForaccessToken != null)
{
webRequestForaccessToken.Method = "POST";
webRequestForaccessToken.ContentType = "application/x-www-form-urlencoded";
webRequestForaccessToken.ContentLength = accessTokenRequestFromJwt.Length;
using (Stream stream = webRequestForaccessToken.GetRequestStream())
{
stream.Write(accessTokenRequestFromJwt, 0, accessTokenRequestFromJwt.Length);
}
using (HttpWebResponse httpWebResponseForaccessToken = webRequestForaccessToken.GetResponse() as HttpWebResponse)
{
Stream streamForaccessToken = httpWebResponseForaccessToken?.GetResponseStream();
if (streamForaccessToken != null)
{
using (StreamReader streamReaderForForaccessToken = new StreamReader(streamForaccessToken))
{
string jsonUserResponseString = streamReaderForForaccessToken.ReadToEnd();
JObject groupsIoApiUserObject = JObject.Parse(jsonUserResponseString);
token = groupsIoApiUserObject["access_token"].ToString();
}
}
}
}
}
catch (WebException ex)
{
try
{
string resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
Console.WriteLine(resp);
}
catch (Exception parseException)
{
string m = parseException.Message;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
two reasons for that either your method of calculation is not good, or the time of your server is not correct, can check with something like:
int nowTimeInEpochSeconds = DateTimeOffset.UtcNow.AddMinutes(59).ToUnixTimeSeconds()
So as it turns out there were two things that needed correction. The first was that according the answer provided by #GAOUL, I was not calculating the epoch time correctly (though I had to modify the suggested line to remove the "AddMinutes" method to get the correct value for the "iat" field). Then, the "exp" value became the "iat" value plus 3600 (1 hour).
So my original time calculation went from this:
int nowTimeInEpochSeconds = (int)Math.Floor((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds);
to this:
int nowTimeInEpochSeconds = (int)Math.Floor((double)DateTimeOffset.UtcNow.ToUnixTimeSeconds());
The second issue I discovered after reviewing my own code was that I was not correctly signing the signature with the private key (was not signing with private key at all).
So I replaced this branch of code:
X509Certificate2 cert = new X509Certificate2(#"C:\**************.p12", "notasecret");
AsymmetricAlgorithm rsaSignature = cert.PrivateKey;
byte[] signature = rsaCryptoServiceProvider.SignData(jwtRequestBytesToSign, "SHA256");
with this branch instead:
X509Certificate2 certificate = new X509Certificate2(#"**************.p12", "notasecret");
RSA rsa = certificate.GetRSAPrivateKey();
byte[] signature = rsa.SignData(jwtRequestBytesToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Once I applied these changes, I got a valid response back from the Google OAuth server and it contained the access/refresh tokens along with the other fields.
Also, thanks to #dazwilkin for referencing JWT.io. That was also a big help!
I've made a call of Client.MakeRequest("/api/v3/account"); to check if I'm authorized to make such a call, but I keep getting an error status 'unauthorized' everytime I make the call.
public static void MakeRequest(string endpoint)
{
var client = new RestClient("https://api.binance.com");
long timestamp = GetTimestamp();
RestRequest request = new RestRequest(endpoint, Method.GET);
request.AddHeader("X-MBX-APIKEY", API_KEY);
request.AddQueryParameter("recvWindow", "5000");
request.AddQueryParameter("timestamp", timestamp.ToString());
request.AddQueryParameter("signature", CreateSignature(request.Parameters, API_SECRET));
RestResponse response = (RestResponse)client.Get(request);
System.Diagnostics.Debug.WriteLine(response.Content);
}
public static string CreateSignature(List<Parameter> parameters, string secret)
{
var signature = "";
if (parameters.Count > 0)
{
foreach (var item in parameters)
{
if (item.Name != "X-MBX-APIKEY")
signature += $"{item.Name}={item.Value}&";
}
signature = signature.Substring(0, signature.Length - 1);
}
byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
byte[] queryStringBytes = Encoding.UTF8.GetBytes(signature);
HMACSHA256 hmacsha256 = new HMACSHA256(keyBytes);
byte[] bytes = hmacsha256.ComputeHash(queryStringBytes);
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
private static long GetTimestamp()
{
return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds();
}
I'm sure the signature is correct as I've followed the specifications from: https://binance-docs.github.io/apidocs/spot/en/#public-api-definitions
Other than that i've been debugging for a while and still can't quite figure out the mistake i'm making.
The endpoint is returning an unauthorized error so you're definitely hitting the endpoint. I see in the documentation that the API key can be restricted to specific IP addresses so check if the API key is correct and has no restrictions. If it does, make sure your IP configured to use the API key.
I have a RSA Private key with me and I have to generate a JWT token using RS256 algorithm.
I started with the below code which was working for "HmacSha256" algorithm
but when i change it to RS256 it throws errors like " IDX10634: Unable to create the SignatureProvider.Algorithm: 'System.String',SecurityKey:'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey'"
After that I modified the code with RSACryptoServiceProvider() class. But i didnt get a solution.
Please anyone can help with a sample code using RSACryptoServiceProvider class with a private key.
public static string CreateToken()//Dictionary<string, object> payload
{
string key = GetConfiguration["privateKey"].Tostring();
var securityKey =
new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(
Encoding.UTF8.GetBytes(key));
var credentials =
new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, "RS256");
var header = new JwtHeader(credentials);
JwtPayload payload = new JwtPayload();
payload.AddClaim(
new System.Security.Claims.Claim(
"context", "{'user': { 'name': 'username', 'email': 'email' }}",
JsonClaimValueTypes.Json));
payload.AddClaim(new System.Security.Claims.Claim("iss", #"app-key"));
payload.AddClaim(new System.Security.Claims.Claim("aud", "meet.jitsi.com"));
payload.AddClaim(new System.Security.Claims.Claim("sub", "meet.jitsi.com"));
payload.AddClaim(new System.Security.Claims.Claim("room", "TestRoom"));
var secToken = new JwtSecurityToken(header, payload);
var handler = new JwtSecurityTokenHandler();
var tokenString = handler.WriteToken(secToken);
return tokenString;
}
I'm using "BouncyCastle.NetCore" and "jose-jwt" libraries to sign and encrypt a web token. I'm able to sign with my private key by below code. but the requirements is to also perform OpenID JWT Encryption In order to encrypt the JWT payload, we need to do that using provided public key string (base64 decoded with X509 key spec).
the encode needed to use RSA algorithm and JWE header should include header name “alg" with the value: RSA_OAEP_256.
Below code is sign only with private key but not sure how to complete the encode??
class Program
{
string publicKeyString = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB";
public static async Task Main(string[] args)
{
var payload = new System.Collections.Generic.Dictionary<string, object>()
{
{ "sub", "Testmr.x#contoso.com" },
{ "iss", "https://www.YourBrand.com" },
{ "exp", 1300819380 },
{ "iat", 1446111752 },
{ "preferred_username", "JohnDoe2" },
{ "phone_number", "+2-10-344-3765333" }
};
var token = CreateToken(payload);
Console.WriteLine($"token={token}");
}
public static string CreateToken(object payload)
{
string jwt = string.Empty;
var fileStream = System.IO.File.OpenText("C:\\temp\\my_private_key.pem");
var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(fileStream, new MyPasswordFinder());
var keyPair = (Org.BouncyCastle.Crypto.AsymmetricKeyParameter)pemReader.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)keyPair);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
jwt = JWT.Encode(payload, rsa, JwsAlgorithm.RS256);//,options: new JwtOptions { EncodePayload = true }
}
return jwt;
}
}
The only difference EncodePayload seems to make is how payload ends up being written out to JWT body. It is true by default and does not affect payload encryption (see source code):
return jwtOptions.EncodePayload
? Compact.Serialize(headerBytes, payloadBytes, signature)
: Compact.Serialize(headerBytes, Encoding.UTF8.GetString(payloadBytes), signature);
What you probably want is to specify JweAlgorithm and correct JweEncryption (note, it's a different overload to the same function:
jwt = JWT.Encode(payload, rsa, JweAlgorithm.RSA_OAEP_256, JweEncryption.A256GCM);
I'm trying to send Push Notifications to APN with tokenization. I tried to use a few libraries like jose-jwt and Microsot Jwt class for creating JWT token, but I can't wrap my head around it.
I get stuck on creating JWT and signing it with private key.
For communicating with certificates, I used PushSharp and it worked just fine. Can anyone help me with a working similar example but with tokens?
edit: following Apple's documentation here: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1
Sample code: the closest I came to something would look like this, but I don't know how to create CngKey properly
var payload = new Dictionary<string, object>()
{
{ "iss", issuer },
{ "iat", DateTime.UtcNow }
};
var headers = new Dictionary<string, object>()
{
{ "kid", keyIdentifier}
};
CngKey key = CngKey.Create(CngAlgorithm.ECDsaP256); //how to create this CngKey
string token = Jose.JWT.Encode(payload, key, JwsAlgorithm.ES256, headers);
Thanks for your answers, had to contact many supports to get this done. Here is what the final result looked like.
/// <summary>
/// Method returns ECDSA signed JWT token format, from json header, json payload and privateKey (pure string extracted from *.p8 file - PKCS#8 format)
/// </summary>
/// <param name="privateKey">ECDSA256 key</param>
/// <param name="header">JSON header, i.e. "{\"alg\":\"ES256\" ,\"kid\":\"1234567899"\"}"</param>
/// <param name="payload">JSON payload, i.e. {\"iss\":\"MMMMMMMMMM"\",\"iat\":"122222222229"}"</param>
/// <returns>base64url encoded JWT token</returns>
public static string SignES256(string privateKey, string header, string payload)
{
CngKey key = CngKey.Import(
Convert.FromBase64String(privateKey),
CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng dsa = new ECDsaCng(key))
{
dsa.HashAlgorithm = CngAlgorithm.Sha256;
var unsignedJwtData =
Url.Base64urlEncode(Encoding.UTF8.GetBytes(header)) + "." + Url.Base64urlEncode(Encoding.UTF8.GetBytes(payload));
var signature =
dsa.SignData(Encoding.UTF8.GetBytes(unsignedJwtData));
return unsignedJwtData + "." + Url.Base64urlEncode(signature);
}
}
Here is what I use:
var privateKeyContent = System.IO.File.ReadAllText(AUTHKEYFILE);
var privateKey = privateKeyContent.Split('\n')[1];
var secretKeyFile = Convert.FromBase64String(privateKey);
var secretKey = CngKey.Import(secretKeyFile,CngKeyBlobFormat.Pkcs8PrivateBlob);
if you want to run your app in MacOS or linux, you can't use CngKey
in this case you can use
ECDsa algorithm for encoding the JWT
private ECDsa encoder(string privateKey)
{
var result = ECDsa.Create();
result.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out _);
return result;
}
Here is what i use;
public static string getAppleJWT(string privateKeyPath, string teamId, string keyId)
{
var private_key = getPrivateKey(privateKeyPath);
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.Now;
var payload = new Dictionary<string, object>()
{
{ "iss", teamId },
{ "iat", (int)issueTime.Subtract(utc0).TotalSeconds }
};
var headers = new Dictionary<string, object>()
{
{ "alg", "ES256"},
{ "kid", keyId}
};
return Jose.JWT.Encode(payload, private_key, JwsAlgorithm.ES256, headers);
}
public static CngKey getPrivateKey(string privateKeyPath)
{
var privateKeyLines = System.IO.File.ReadAllLines(privateKeyPath).ToList();
privateKeyLines.RemoveAt(privateKeyLines.Count - 1);
privateKeyLines.RemoveAt(0);
var privateKey = string.Join("", privateKeyLines);
var secretKeyFile = Convert.FromBase64String(privateKey);
var secretKey = CngKey.Import(secretKeyFile, CngKeyBlobFormat.Pkcs8PrivateBlob);
return secretKey;
}
Pulling all the pieces together and extending some of the prior answers
This utilizes Microsoft.IdentityModel.Tokens which removes Windows dependencies.
Tested this under .NET 6
How to create the token:
public static string CreateSecurityToken(string tokenId, string teamId, string tokenKey, DateTime utcGenerateDateTime)
{
var key = ECDsa.Create();
key?.ImportPkcs8PrivateKey(Convert.FromBase64String(tokenKey), out _);
var securityKey = new ECDsaSecurityKey(key) { KeyId = tokenId };
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.EcdsaSha256);
var descriptor = new SecurityTokenDescriptor
{
IssuedAt = utcGenerateDateTime,
Issuer = teamId,
SigningCredentials = credentials
};
var handler = new JwtSecurityTokenHandler();
var encodedToken = handler.CreateEncodedJwt(descriptor);
return encodedToken;
}
How to validate the token:
public static SecurityToken ValidateSecurityToken(string token, string tokenId, string tokenKey)
{
if (token == null)
return null;
var key = ECDsa.Create();
key?.ImportPkcs8PrivateKey(Convert.FromBase64String(tokenKey), out _);
var securityKey = new ECDsaSecurityKey(key) { KeyId = tokenId };
try
{
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = securityKey,
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
}, out var validatedToken);
return validatedToken;
}
catch
{
return null;
}
}