# Cis2 - Exchanging code with id\_token and refresh token

**URL:** <https://developer.community.nhs.uk/t/cis2-exchanging-code-with-id-token-and-refresh-token/1018>\
**Category:** Care Identity Service (CIS)\
**Tags:** authentication, integration\
**Created:** [25 June 2024 12:51 UTC](https://developer.community.nhs.uk/t/cis2-exchanging-code-with-id-token-and-refresh-token/1018 "2024-06-25T12:51:23Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![fawad.ahmad](https://avatars.discourse-cdn.com/v4/letter/f/ba9def/32.png) [@fawad.ahmad](https://developer.community.nhs.uk/u/fawad.ahmad)\
**Post date:** [25 June 2024 12:51 UTC](https://developer.community.nhs.uk/t/cis2-exchanging-code-with-id-token-and-refresh-token/1018/1 "2024-06-25T12:51:23Z")

</div>

Hi,

I am trying to complete flow for Cis2 authentication. I have NHS Care Identity button, when user clicks on it, I redirects them to integration environment’s authorisation URL. I use one of the authentication methods and I am redirected to my redirect URL with code and scope query strings.

I am now sending the code that I received to integration token endpoint so that I can receive Id\_token and refresh tokens. I am using private jwt.

I am doing following steps:

1. Generating Jwt token using my private key file to receive client assertion. I am passing my app\_key as iss and sub, as shown in my code below.

2. Sending the assertion that I received from step 1 along with access code, client\_id, redirect\_uri etc. Note in this step I am sending client\_id as 00000.apps.supplier. Replacing 00000 with my own number.

I receive following error: “{"error\_description":"JWT is not valid","error":"invalid\_client"}”.

**My controller (UserRestrictedAuthController), it uses UserRestrictedjwtHandler to create jwt token which is used for client assertion.**

public class UserRestrictedAuthController : ControllerBase  
{  
private UserRestrictedJwtHandler userRestrictedJwtHandler;  
string audience = “[https://am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/access\_token](https://am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/access_token)”;  
string clientId = “Mynumber.apps.supplier”;  
string apiKey = “myApiKey”;

```
string privateKeyFile = @"C:\Users\path\Resources\test-13.pem";
string kid = "test-13";

public async Task Get(string accessCode)
{
    if (!System.IO.File.Exists(privateKeyFile))
    {
        throw new FileNotFoundException($"The private key file was not found: {privateKeyFile}");
    }

    userRestrictedJwtHandler = new UserRestrictedJwtHandler(privateKeyFile, audience, apiKey, kid);
    var clientAssertion = userRestrictedJwtHandler.GenerateJwt();

    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri("https://am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443");

        var requestData = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type", "authorization_code"),
            new KeyValuePair<string, string>("code", accessCode),
            new KeyValuePair<string, string>("redirect_uri", "https://localhost:44358/authorise.aspx"),
            new KeyValuePair<string, string>("client_id", clientId),
            new KeyValuePair<string, string>("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
            new KeyValuePair<string, string>("client_assertion", clientAssertion)
        });

        HttpResponseMessage response = await client.PostAsync("/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/access_token", requestData);

        if (response.IsSuccessStatusCode)
        {
            string responseData = await response.Content.ReadAsStringAsync();
            Console.WriteLine("Response Data: " + responseData);
        }
        else
        {
            string errorResponse = await response.Content.ReadAsStringAsync();
            Console.WriteLine("Error: " + response.StatusCode + ", Response: " + errorResponse);
        }
    }
}

```

}

**Jwt handler code is given below.**

public class UserRestrictedJwtHandler  
{  
private readonly SigningCredentials \_signingCredentials;  
private readonly string \_audience;  
private readonly string \_clientId;  
private readonly string \_kid;

```
public UserRestrictedJwtHandler(string certPath, string audience, string clientId, string kid, bool isPem = true)
{
    _audience = audience;
    _clientId = clientId;
    _kid = kid;
    _signingCredentials = LoadCertificateFromPem(certPath, kid);
}

private SigningCredentials LoadCertificateFromPem(string pemPath, string kid)
{
    var privateKey = File.ReadAllText(pemPath);
    privateKey = privateKey.Replace("-----BEGIN PRIVATE KEY-----", "")
                           .Replace("-----END PRIVATE KEY-----", "")
                           .Replace("-----BEGIN RSA PRIVATE KEY-----", "")
                           .Replace("-----END RSA PRIVATE KEY-----", "")
                           .Trim();

    var keyBytes = Convert.FromBase64String(privateKey);
    var rsa = RSA.Create();
    rsa.ImportRSAPrivateKey(keyBytes, out _);

    var rsaSecurityKey = new RsaSecurityKey(rsa)
    {
        KeyId = kid
    };

    return new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha512)
    {
        CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false }
    };
}

public string GenerateJwt()
{
    var now = DateTime.UtcNow;

    var claims = new List<Claim>
{
    new Claim(JwtRegisteredClaimNames.Sub, _clientId),
    new Claim(JwtRegisteredClaimNames.Iss, _clientId),
    new Claim(JwtRegisteredClaimNames.Aud, _audience),
    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
    new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(now.AddMinutes(5)).ToUnixTimeSeconds().ToString()),
    new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(now).ToUnixTimeSeconds().ToString()),
    new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(now).ToUnixTimeSeconds().ToString())
};

    var token = new JwtSecurityToken(
        _clientId,
        _audience,
        claims,
        now,
        now.AddMinutes(5),
        _signingCredentials
    );

    var tokenHandler = new JwtSecurityTokenHandler();
    var jwt = tokenHandler.WriteToken(token);

    // Debug: Print the JWT to verify its structure
    Console.WriteLine("Generated JWT: " + jwt);

    return jwt;
}

```

}

---

<div class="post-metadata">

**Author:** ![hameed.mamaniat](https://avatars.discourse-cdn.com/v4/letter/h/919ad9/32.png) [@hameed.mamaniat](https://developer.community.nhs.uk/u/hameed.mamaniat)\
**Post date:** [9 July 2024 10:35 UTC](https://developer.community.nhs.uk/t/cis2-exchanging-code-with-id-token-and-refresh-token/1018/2 "2024-07-09T10:35:08Z")

</div>

My approach

do all the POC work using postman or some rest client

utilise [Online JWT tool](https://dinochiesa.github.io/jwt/) and generate the jwt accordingly

use [https://www.unixtimestamp.com/](https://www.unixtimestamp.com/) to generate the timestamp (5 mins in future)

retrieve code, and then use for the access token request

only when I have this part working will i write the c# code

ps: in your code, I couldnt see where you are setting the KID, or the algorithm?

---

<div class="post-metadata">

**Author:** ![john.lister3](https://avatars.discourse-cdn.com/v4/letter/j/a587f6/32.png) [@john.lister3](https://developer.community.nhs.uk/u/john.lister3)\
**Post date:** [17 September 2024 12:10 UTC](https://developer.community.nhs.uk/t/cis2-exchanging-code-with-id-token-and-refresh-token/1018/3 "2024-09-17T12:10:29Z")

</div>

Have you published your public keys as part of the client configuration and exposed them over the internet - if CIS2 Auth cannot access them, it cannot validate your signature and will fail. Any questions regarding your client config, please reach out to the onboarding teams.
