I just can’t seem to get the JWT right for authentication.
All examples of JWT use HMAC which is HS256 not RS256 which is what they require.
I’m using openssl to generate the RS256 signature but the IRS is reporting:
“ERSV306 - The given JWT for client authentication is invalid”
Not very helpful as to what about it is invalid.
I’ve looked though all the error codes and do not see one about the signature, the user ID or anything else to help eliminate parts of the JWT as causing the problem.
The JWT is supposed to be “signed with a private key” - which I assume, probably incorrectly, that the signature component is the signature of the header and payload (header.payload.signature) which is what is normally an HMAC(header.payload).
This is the kind of thing that Chatgpt works pretty well for.
But generally, if you have a private key, then the other end needs your public key to decrypt.
The issue I guess I’m having is since I can’t use HMAC, do I take the Hash of the encoded header + payload before singing it? I’ve tried both ways and get the same error.
Are they able to decode it okay but then does the error mean the information in the payload is not valid.
Or is the key I provided them not working?
None of the other error messages say things like “can’t decrypt” or “invalid client ID”.
The actual post contains 2 JWTs. One us called the ‘User’ and the other the ‘Client’.
The client is second, so does this mean the User is Okay?
Of course, being the IRS there is no way to contact them without waiting on hold for 4 hours and hoping you can get someone that can answer these questions.
So I was hoping someone here might have worked with this successfully.
1 Like
While you are waiting for a real answer,
This “debugger” on this site looks like it would be useful. https://jwt.io/
I’ve been using that unsuccessfully, but my understanding is the IRS is doing ‘something different’.
The info I’ve found is comments from developers saying things like “this is the worst experience of my career”. Yeah. I tend to agree.
1 Like
Hi @PaulMacFarlane,
I am facing the same issue. Check these before trying to fetch token -
- Get client ID and User Id
- Consent to Test environment
- While signing the token use the kid which was shared as a part of JWK set in the RSA signing param.
Saw in your message above, client jwt is 2nd, it’s not, client JWT is 1st and User Jwt is the 2nd. Please try that, it might work for you, for me it has not 
Let me know if it works!!
Regards,
Bikahs Shah
1 Like
Thanks Bikahs,
I did get this working last month.
@PaulMacFarlane Wow!! That’s amazing.
I am still stuck with it, could you please have a look, what am I missing?
Thanks in advance 
clientJwt = generateJWKs.GenerateJWTTokenV13(clientId, clientId, tokenEndpointTest);
userJwt = generateJWKs.GenerateJWTTokenV13(clientId, userId, tokenEndpointTest);
return await RequestAccessToken(clientJwt, userJwt, tokenEndpointTest);
internal string GenerateJWTTokenV13(string issuer, string subject, string audience)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
string key = @"MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuxJr5bETbAhvq
//Rest of the private key generated using : openssl pkcs12 -in ctoririsa2a_1.pfx -nocerts -out privatekey.pem -nodes
I4X28UBoyGcAN91ug5rFPQ==";
RSA rsa = RSA.Create(2048);
rsa.ImportPkcs8PrivateKey(
source: Convert.FromBase64String(key),
bytesRead: out int _);
var signingCredentials = new SigningCredentials(
key: new RsaSecurityKey(rsa)
{
KeyId = "same kid from JWKs"
},
algorithm: SecurityAlgorithms.RsaSha256);
var tokenDescriptor = new SecurityTokenDescriptor
{
Claims = new Dictionary<string, object>
{
{ "iss", issuer}, // Client ID
{ "sub", subject }, // Client ID
{ "aud", audience }, // Token endpoint (Authorization Server URL)
{ "iat", DateTime.UtcNow }, // Token issue time
{ "exp", DateTime.UtcNow.AddMinutes(15) }, // Token expiration time
{ "jti", Guid.NewGuid() } // Unique token ID
},
SigningCredentials = signingCredentials
};
var token = tokenHandler.CreateToken(tokenDescriptor);
JwtSecurityToken jwtSecurityToken = (JwtSecurityToken)token;
jwtSecurityToken.Header.Remove("typ");
jwtSecurityToken.Payload.Remove("nbf");
var tokenString = tokenHandler.WriteToken(token);
return tokenString;
}
catch (Exception ex)
{
return ex.Message;
}
}
static async Task<string> RequestAccessToken(string clientJwt, string userJwt, string tokenEndpoint)
{
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint);
// Prepare the request body
var content = new StringContent(
$"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" +
$"&assertion={Uri.EscapeDataString(clientJwt)}" +
$"&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" +
$"&client_assertion={Uri.EscapeDataString(userJwt)}");
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
request.Content = content;
// Send the request and process the response
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return await response.Content.ReadAsStringAsync();
}
}
Regards,
Bikash Shah
@PaulMacFarlane Hey Paul, what was the issue you were facing? Could you please explain the issue? was it the JWKs you pasted on the portal? or the Private key used to sign? Any help is appreciated, I have tried calling the IRIS contact but as you know 4 hours of wait and still not connecting to the right person. Thanks in advance.
Happy new year!!
Regards,
Bikash Shah
I am not a C++ developer, but if I’m understanding your code above, you are creating a new private key with each call?
The private key (and kid) should be recorded in the IRIS Developer/Software registration.
So when you call with the jwt, the IRIS system has to key to decode what you sent.
Hi @PaulMacFarlane,
The private key is not created newly, it’s a constant being used in the RSA object.
I’m using the same kid that was given to IRIS during registration. Also the private key is generated from the certificate using which the JWKs was created.
How did you generate the private key for signing? Which language are you using for integration?
Thanks,
Bikash Shah
This website is called ClarionHub because we all use Clarion for development.
1 Like
@PaulMacFarlane about the issue you faced, could you please help me understand what the issue was(since the error messages we are receiving are the same) and how did you resolve it.
For the JWKs I followed these steps -
- From the cert file which is a .pfx file, generated the JWKs and pasted onto the IRIS website.
- For the private key, I used the same certificate to generate the private key using
openssl pkcs12 -in ctoririsa2a_1.pfx -nocerts -out privatekey.pem -nodes
As you can see here the file privatekey.pem has the private key.
- This private key I am using in the code for signing.
Regards,
Bikash Shah
So, for EACH JWT you need to create 3 elements
“Header” which contains JSON structure with the KID and the ALG
“Payload” which contains the JSON structure with iss (client ID), sub (user ID), aud (access url), and the jti Unique ID, etc. (all defined in the specs)
Base64Encode each.
Create a string “Data” which is Base64Encode(Header) + “.” + Base64Encode(Payload)
Next, we need a signature of the “Data”.
I save the token to a file “Data.txt”
Get the encoded “signature” (capture the output of this command)
openssl dgst -sha256 -sign privatekeyfile Data.txt
Now you have the 3rd element.
JWT = Base64Encode(Header) + “.” + Base64Encode(Payload) + “.” + Base64Encode(signature)
Remember, you will create 2 of these JWTs. One for Client and one for User.
Test the results here: https://jwt.io/
and keep tweaking until it works.
1 Like
@PaulMacFarlane First of all thanks for your help on this
. I tried exactly the same as mentioned above.
try
{
var jwtHeader = new Dictionary<string, object>
{
{ "kid", "06072019" },
{ "alg", "RS256" }
};
var payload = new Dictionary<string, object>
{
{ "iss", issuer}, // Client ID
{ "sub", subject }, // Client ID
{ "aud", audience }, // Token endpoint (Authorization Server URL)
{ "iat", DateTime.UtcNow }, // Token issue time
{ "exp", DateTime.UtcNow.AddMinutes(15) }, // Token expiration time
{ "jti", Guid.NewGuid() } // Unique token ID
};
// Serialize the dictionary to a JSON string
string jsonHeader = JsonConvert.SerializeObject(jwtHeader);
// Convert the JSON string to a byte array
byte[] headerBytes = Encoding.UTF8.GetBytes(jsonHeader);
// Encode the byte array to a Base64 string
string base64EncodedHeader = Convert.ToBase64String(headerBytes);
// Serialize the dictionary to a JSON string
string jsonPayload = JsonConvert.SerializeObject(payload);
// Convert the JSON string to a byte array
byte[] payloadBytes = Encoding.UTF8.GetBytes(jsonPayload);
// Encode the byte array to a Base64 string
string base64EncodedPayload = Convert.ToBase64String(payloadBytes);
//Create a string “Data” which is base64EncodedHeader + “.” + base64EncodedPayload
var data = base64EncodedHeader + "." + base64EncodedPayload;
//Save data to a txt file called Data.txt
File.WriteAllText(@"C:\\Users\\Bikash.Shah\\Data.txt", data);
//Read the contents of the file Signature.bin from C:\\Users\\Bikash.Shah
var signature = File.ReadAllBytes(@"C:\\Users\\Bikash.Shah\\Signature.bin");
var jwt = data + "." + Convert.ToBase64String(signature);
//Save JWT to a txt file called JWT.txt
File.WriteAllText("JWT.txt", jwt);
return jwt;
Still getting same error.
For the signature I am using the command mentioned above
openssl dgst -sha256 -sign privatekey.pem Data.txt > signature.bin
Here privatekey.pem is the file where my private key is present.
The output tested on jwt.io says Invalid Signature.
How did you generate the private key file? I think there is some issue in generating the private key on my end. I have used the following -
openssl pkcs12 -in ctoririsa2a_1.pfx -nocerts -out privatekey.pem -nodes
Regards,
Bikash Shah
Found this article…
Used this site:
https://mkjwk.org/
@PaulMacFarlane above site is for generating JWKS without having a SSL certificate.
From the documentation, it says that we need to have an SSL certificate which shall not be self signed. and the digital certificate providers are one of the following
We used the Entrust one and generated the JWKs from the .pfx certificate we got from the certificate authority.
And then the same JWKs was pasted while registering the App.
For private key I have used this command
openssl pkcs12 -in ctoririsa2a_1.pfx -nocerts -out privatekey.pem -nodes
the o/p looks like this
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuxJr5bETbAhvq…
GcAN91ug5rFPQ==
-----END PRIVATE KEY-----
Is the right process? please let me know if I am doing something wrong here.
Thanks again!!
Regards,
Bikash Shah
Hi @PaulMacFarlane
Please let me know if you have any question. I am thinking the issue is with JWKs and the private key used for signing the data. If you could let me know how did you generate the JWKs and private key for signing from your certificate, that would be a great help.
Thanks again for your support.
Regards,
Bikash Shah
Bikash - Can you share your code? I get the same response. I don’t think I’m supplying the correct values for my two JWTs and/or they are in the wrong order. I did verify my JWTs using the jwt io site, but the value(s) must be wrong or wrong order. - thx