Anyone work with the IRS A2A?

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, :slight_smile: 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 -

  1. Get client ID and User Id
  2. Consent to Test environment
  3. 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 :frowning:

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 :slight_smile:

				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