I create token from .net by this C# code (with System.IdentityModel.Tokens.Jwt):
var keybytes = Convert.FromBase64String("MYCUSTOMCODELONGMOD4NEEDBEZE");
var signingCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(keybytes),
SecurityAlgorithms.HmacSha256Signature,
SecurityAlgorithms.Sha256Digest);
var nbf = DateTime.UtcNow.AddDays(-100);
var exp = DateTime.UtcNow.AddDays(100);
var payload = new JwtPayload(null, "", new List<Claim>(), nbf, exp);
var user = new Dictionary<string, object>();
user.Add("userId", "1");
payload.Add("user", user);
payload.Add("success", true);
var jwtToken = new JwtSecurityToken(new JwtHeader(signingCredentials), payload);
var jwtTokenHandler = new JwtSecurityTokenHandler();
var resultToken = jwtTokenHandler.WriteToken(jwtToken);
I send the resultToken to nodejs and verify it (with jsonwebtoken library) with below code:
var jwt = require('jsonwebtoken');
var result = jwt.verify(
resultToken,
new Buffer('MYCUSTOMCODELONGMOD4NEEDBEZE').toString('base64'),
{ algorithms: ['HS256'] },
function(err, decoded) {
if (err) {
console.log('decode token failed with error: '+ JSON.stringify(err));
}
}
);
I got the error: invalid signature. The resultToken content:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTQ4MTMxMTUsIm5iZiI6MTQ3NzUzMzExNSwidXNlciI6eyJ1c2VySWQiOiIxIn0sInN1Y2Nlc3MiOnRydWV9.4bjYyIUFMouz-ctFyxXkJ_QcJJQofCEFffUuazWFjGw
I have debug it on jwt.io with above signature (MYCUSTOMCODELONGMOD4NEEDBEZE) and secret base64 encoded checked, it's ok.
I have tried a signature without base64 encoded by chaging keybytes in C# code:
var keybytes = Encoding.UTF8.GetBytes("MYCUSTOMCODELONGMOD4NEEDBEZE");
And it verified successfully in nodejs. So i think the issue comes from my nodejs code when verify a base64 encoded signature. Did i miss some options when verify token or somethings?
I have no idea what you did but this snippet is working for me with the token you provided above.
var jwt = require('jwt-simple')
var secret = new Buffer('MYCUSTOMCODELONGMOD4NEEDBEZE').toString('base64')
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTQ4MTMxMTUsIm5iZiI6MTQ3NzUzMzExNSwidXNlciI6eyJ1c2VySWQiOiIxIn0sInN1Y2Nlc3MiOnRydWV9.4bjYyIUFMouz-ctFyxXkJ_QcJJQofCEFffUuazWFjGw'
var decoded = jwt.decode(token, secret)
console.log(decoded)
Output:
❯ node jwt.js
{ exp: 1494813115,
nbf: 1477533115,
user: { userId: '1' },
success: true }
Using jsonwebtoken library
// var jwt = require('jwt-simple')
var jwt = require('jsonwebtoken');
var secret = Buffer.from('MYCUSTOMCODELONGMOD4NEEDBEZE', 'base64')
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTQ4MTMxMTUsIm5iZiI6MTQ3NzUzMzExNSwidXNlciI6eyJ1c2VySWQiOiIxIn0sInN1Y2Nlc3MiOnRydWV9.4bjYyIUFMouz-ctFyxXkJ_QcJJQofCEFffUuazWFjGw'
jwt.verify(token, secret, { algorithms: ['HS256'] }, function(err, decoded) {
if (err) {
console.log(err)
} else {
console.log(decoded)
}
})
Again still working fine.
The only difference i can see is the secret.
Related
I have an issue that I'm now completely at a loss over.
I have an authentication server that creates and sends JWT's to a unity application (android/VR).
The auth server creates a token as such
private string GenerateJwtToken(User user)
{
var symmetricKey = Convert.FromBase64String(_tokenSettings.Secret);
var expires = DateTime.Now.AddMinutes(Convert.ToInt32(15));
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.PrimarySid, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
new Claim("company", user.Company),
new Claim(ClaimTypes.Role, "api_user"),
new Claim(ClaimTypes.Expiration, $"{expires.ToShortDateString() + " " + expires.ToShortTimeString()}"),
}),
Expires = expires,
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
return token;
}
I have no issues decoding this and getting out the claims in a console application. This is where the confusion comes in. When I try and perform the same task in Unity and build the application to the VR headset, I'm met with issues, namely this
"IDX10729: Unable to decode the header 'header' as Base64Url encoded string. jwtEncodedString: 'Token here'."
The unity auth service looks like this
public async Task<string> GetAccessToken(string username, string password)
{
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage())
{
//TODO: Replace this hardcoded URL value here.
request.RequestUri = new Uri("{{URL Emmited}}");
request.Method = HttpMethod.Post;
var body = new AuthRequest()
{
Username = username,
Password = password
};
var content = new StringContent(JsonConvert.SerializeObject(body), encoding: Encoding.UTF8, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
var tokenContent = JsonConvert.DeserializeObject<Response>(responseContent);
var token = tokenContent.Token;
return token;
}
}
}
and retrieving the claims, I've tried two different ways
public string GetUserName(string token)
{
try
{
var securityToken = new JwtSecurityToken(token);
return securityToken?.Claims.First(claim => claim.Type == "given_name").Value;
}
catch (Exception exception)
{
//log exception here
return string.Empty;
}
}
and
public string GetUserName(string token)
{
try
{
var handler = new JwtSecurityTokenHandler();
var securityToken = handler.ReadJwtToken(token);
return securityToken?.Claims.First(claim => claim.Type == "given_name").Value;
}
catch (Exception exception)
{
//log exception here
return string.Empty;
}
}
ReadJwtToken internally just creates a new JwtSecurityToken object so I assume there's something in that I'm missing.
Both of these throw the same "Unable to decode the header 'header' as Base64Url encoded string" error, however, if I do handler.CanReadToken(token); this returns true, which baffles me even more.
Anyone have any ideas as to why this is happening and any clues on how to fix it.
some further information; the token I retrieve can be decoded in JWT.io and in a console app, I'm using the dotnet standard 2.0 assemblies for System.IdentityModel, System.IdentityModel.Tokens.JWT, System.IdentityModel.Tokens, System.IdentityModel.Logging and Newtonsoft.Json
I'm trying to sign a JWT using HS256. I'm using System.IdentityModel.Tokens.Jwt . When decoding the token using jwt.io I get invalid signature and I've noticed that my headers read:
{
"alg": "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"typ": "JWT"
}
rather than {"alg":"HS256","typ":"JWT"} as I expected.
Is this what's causing the invalid signature? Also any ideas on a fix? Please note that I need to include custom claims as well.
var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(clientsecret));
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
var header = new JwtHeader(credentials);
You can create your JSON Web Token (JWT) as follows using System.IdentityModel.Tokens.Jwt, which should set all fields correctly (secret is the key you use to sign your JWT):
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("sub", "customer") }),
Issuer = "Who issued the token",
Claims = new Dictionary<string, object>
{
["email"] = Email,
},
IssuedAt = now,
NotBefore = now,
Expires = now + TimeSpan.FromDays(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var serializedToken = tokenHandler.WriteToken(token);
serializedToken finally contains the serialized JWT.
Please note that the SecurityTokenDescriptorclass is from the Microsoft.IdentityModel.Tokens namespace of the same NuGet package, not from System.IdentityModel.Tokens namespace.
SecurityAlgorithms.HmacSha256Signature
change
SecurityAlgorithms.HmacSha256
I'm having trouble getting my .NET Core client to generate OAuth access tokens for a salesforce endpoint that requires OAuth of type 'JWT Bearer Flow'.
It seems there are limited .NET Framework examples that show a .NET client doing this, however none that show a .NET Core client doing it
e.g.
https://salesforce.stackexchange.com/questions/53662/oauth-jwt-token-bearer-flow-returns-invalid-client-credentials
So in my .NET Core 3.1 app i've generated a self signed certificate, added the private key to the above example's code when loading in the certificate, however a System.InvalidCastExceptionexception exception occurs on this line:
var rsa = certificate.GetRSAPrivateKey() as RSACryptoServiceProvider;
Exception:
System.InvalidCastException: 'Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'.'
It appears that this private key is used in the JWT Bearer Flow as part of the signature, and perhaps RSACryptoServiceProvider is not used in .NET core as it was in .NET Framework.
My question is this - is there actually a way in .NET Core to generate access tokens for the OAuth JWT Bearer Flow?
Full code that I'm using:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var token = GetAccessToken();
}
static dynamic GetAccessToken()
{
// get the certificate
var certificate = new X509Certificate2(#"C:\temp\cert.pfx");
// create a header
var header = new { alg = "RS256" };
// create a claimset
var expiryDate = GetExpiryDate();
var claimset = new
{
iss = "xxxxxx",
prn = "xxxxxx",
aud = "https://test.salesforce.com",
exp = expiryDate
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = ToBase64UrlString(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = ToBase64UrlString(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
// signature
var rsa = (RSACryptoServiceProvider) certificate.GetRSAPrivateKey();
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = ToBase64UrlString(signatureBytes);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = "https://login.salesforce.com/services/oauth2/token";
var content = new NameValueCollection();
content["assertion"] = jwt;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));
var result = JsonConvert.DeserializeObject<dynamic>(response);
return result;
}
static int GetExpiryDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var currentUtcTime = DateTime.UtcNow;
var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;
return exp;
}
static string ToBase64UrlString(byte[] input)
{
return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
Well - it turns out posting to stackoverflow gets the brain cogs turning.
The answer ended up being doing a deep dive to find a similar issue here and using the solution from x509certificate2 sign for jwt in .net core 2.1
I ended up replacing the following code:
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = ToBase64UrlString(signatureBytes);
With this code which makes use of the System.IdentityModel.Tokens.Jwt nuget package:
var signingCredentials = new X509SigningCredentials(certificate, "RS256");
var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);
Full code after solution:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var token = GetAccessToken();
}
static dynamic GetAccessToken()
{
// get the certificate
var certificate = new X509Certificate2(#"C:\temp\cert.pfx");
// create a header
var header = new { alg = "RS256" };
// create a claimset
var expiryDate = GetExpiryDate();
var claimset = new
{
iss = "xxxxx",
prn = "xxxxx",
aud = "https://test.salesforce.com",
exp = expiryDate
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = ToBase64UrlString(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = ToBase64UrlString(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
var signingCredentials = new X509SigningCredentials(certificate, "RS256");
var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signature;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = "https://test.salesforce.com/services/oauth2/token";
var content = new NameValueCollection();
content["assertion"] = jwt;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));
var result = JsonConvert.DeserializeObject<dynamic>(response);
return result;
}
static int GetExpiryDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var currentUtcTime = DateTime.UtcNow;
var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;
return exp;
}
static string ToBase64UrlString(byte[] input)
{
return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
I am replying to this question just because such a similar answer would have helped me a lot when I landed on this page the first time.
First of all you don't have to generate the JWT from the C# client.
To generate a JWT token you can use this website: https://jwt.io/
There is a very well done video showing how to generate a JWT token:
https://www.youtube.com/watch?v=cViU2-xVscA&t=1680s
Once generated, use it from your C# client to call the get access_token endpoint
https://developer.salesforce.com/docs/atlas.en-us.api_iot.meta/api_iot/qs_auth_access_token.htm
(Watch the video on YT)
If all is correct you will get the access_token
To run the API calls, all you need is the access_token and not the JWT.
Once you have it add it to the HTTP calls like this
public static void AddBearerToken(this HttpRequestMessage request, string accessToken)
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
}
From time to time the access_token will expire. To check its validity you can call the token introspect api
https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oidc_token_introspection_endpoint.htm&type=5
You need to pass two additional parameters: client_id and client_secret
The client_id is the Consumer Key. You get it from the Connected App in Salesforce
The client_server is the Consumer Secret. You get it from Connected App in Salesforce
If the introspect token API returns a response with
{ active: false, ... }
it means that the access_token is expired and you need to issue a new one.
To issue a new access_token simply call the "/services/oauth2/token" again using the same JWT.
I have stuck in apple push notification. Following the document in https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1 I created the header and payload with the private key to generate token, but after call api: https://api.development.push.apple.com:443/3/device/, It told bad device token. Check in jwt.io it said invalid token.
Anyone know this problem or idea.
Thank you !
Here is the code .net core:
var header = JsonConvert.SerializeObject(new { alg = "ES256", kid = keyId });
var payload = JsonConvert.SerializeObject(new { iss = teamId, iat = ToEpoch(DateTime.UtcNow) });
var key = CngKey.Import(Convert.FromBase64String(p8privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng dsa = new ECDsaCng(key))
{
dsa.HashAlgorithm = CngAlgorithm.Sha256;
var headerBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(header));
var payloadBasae64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtData = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(header)) + "." + System.Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtDataBytes = Encoding.UTF8.GetBytes(unsignedJwtData);
var signature =
dsa.SignData(unsignedJwtDataBytes);
return unsignedJwtData + "." + System.Convert.ToBase64String(signature);
}
}
I'm trying to generate a JWT to use as an Authorization header for a HTTP request.
This is happening in C#, using the System.IdentityModel.Tokens.Jwt package from Nuget. My code for it is as follows:
byte[] certByteArray = Convert.FromBase64String("CertificatePrivateKeyPlaceholder");
var certificate = new X509Certificate2(certByteArray, "MyPassword");
var tokenHandler = new JwtSecurityTokenHandler();
var descriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("Issuer", "MyIssuer"),
new Claim("Audience", "MyAudience")
}),
EncryptingCredentials = new X509EncryptingCredentials(certificate),
SigningCredentials = new X509SigningCredentials(certificate, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest),
};
var token = tokenHandler.CreateToken();
string stringToken = "bearer " + tokenHandler.WriteToken(token)
At the end stringToken looks like a JWT, but it is of the format aaaa.bbbb. and it appears that the signature portion (after the second . is missing)
Any pointers as to what I might be doing wrong?
You need to pass the descriptor as a parameter to CreateToken:
var token = tokenHandler.CreateToken(descriptor);