Key management
Detailed developer guidance about key management when integrating with CIS2 Authentication.
Overview
Asymmetric key cryptography is a method for verifying the sender of a message or request. It works like this:
- The sender generates a pair of keys - a private key and a public key - using a cryptographic algorithm. Each key is an alphanumeric code. They keep the private key secret and make the public key available to potential receivers.
- The sender 'signs' each message or request with their private key. This uses a signing algorithm and produces a 'signature' - another alphanumeric code - which the sender includes with the message.
- The receiver uses the public key to verify the signature.
CIS2 Authentication uses asymmetric keys in a number of ways:
- If you use private key JWT for client authentication, you use a signed JWT when sending us a token request.
- We sign the ID token we return in response to your token request.
- If you request it, we sign the response to your user info request.
- If you use back-channel logout, we sign the logout token that we send to your logout endpoint.
Signed JWTs
A JSON Web Token (JWT) is a standard URL-safe way of sending information in JSON format.
A signed JWT is a JWT that has been signed by the sender using their private key. It consists of three Base64URL encoded elements separated by a . character:
<header>.<payload>.<signature>
The first element is the JOSE header, an example of which is given below:
Example JOSE header
{
"typ": "JWT",
"kid": "b/O6OvVv1+y+WgrH5Ui9WTioLt0=",
"alg": "RS256"
}
This specifies that the token has been signed with an RSA Signature utilising the SHA-256 hashing algorithm and the key identified by the string b/O6OvVv1+y+WgrH5Ui9WTioLt0=.
The receiver of the JWT needs to obtain the public key corresponding to the kid provided. This data could be exchanged manually at the time of registration but this would not readily allow for signing keys to be rotated. To address this issue, signers publish their keys in a JSON Web Key Set (JWKS) at a known location. See the JSON Web Key Specification for further details about key sets.
Our JWKS endpoints
The location of our JWKS is specified in the jwks_uri claim in the configuration document for the relevant environment (see Discovery). For example, the JWKS for our Integration environment is available at this JWKS URI.
You can retrieve our JWKS by sending a HTTP GET request to the JWKS URI, as in the example below:
Example JWKS request
GET /openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/connect/jwk_uri HTTP/1.1
Host: am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443
We'll return the JWKS as a JSON document, as in the shortened example below:
Example returned JSON document
{
"keys":[
{
"kty":"RSA",
"kid":"b/O6OvVv1+y+WgrH5Ui9WTioLt0=",
"use":"sig",
"x5t":"hMMNLLPWyUG0_LOdC8aM4_hl2bc",
"x5c":[
"MIIDaDCCAlCgAwIBAgIDcB/YMA0GCSqG ... 7Y48BmkUqw6E9"
],
"n":"o1uXz14_oHyRkBM1I97f45nd6wvHfWGNf51qQe0_ ... WwArxi7KgQ",
"e":"AQAB",
"alg":"RS384"
},
{
"kty":"RSA",
"kid":"b/O6OvVv1+y+WgrH5Ui9WTioLt0=",
"use":"sig",
"x5t":"hMMNLLPWyUG0_LOdC8aM4_hl2bc",
"x5c":[
"MIIDaDCCAlCgAwIBAgIDcB/YMA0GCSqG ... Y48BmkUqw6E9"
],
"n":"o1uXz14_oHyRkBM1I97f45nd6wvHfWGNf51qQe0_ ... WwArxi7KgQ",
"e":"AQAB",
"alg":"RS256"
}
...
]
}
The key used to sign the JWT can be retrieved by locating the entry with the kid and alg values obtained from the JOSE header. In the example below a single key with the kid of b/O6OvVv1+y+WgrH5Ui9WTioLt0= and alg of RS256 has been returned. It is an RSA key with the modulus given by n and the public exponent by e (both of these values have been shortened in the example above).
Your JWKS endpoint
If you're using private key JWT to authenticate yourself when making a token request, you must provide a JWKS endpoint during registration that complies with the JSON Web Key Specification. You must include the kid of the signing key in the header of each signed JWT to indicate which key is to be used to validate the signature. This JWKS endpoint must be publicly accessible on the internet.
If you want to use a firewall to control access to your JWKS endpoint, you'll need to allow access to the endpoint from the following IP addresses:
| Environment | IP Range(s) |
|---|---|
| Live |
52.142.148.70/31 |
| INT | 51.104.255.212/31 |
| DEP | 51.132.152.140/31 |
Our IP addresses can change over time
If we change our IP addresses, we'll let you know in advance, but it's your responsibility to change your firewall configuration.
Key rotation
Keys should be 'rotated' periodically - adding new keys and removing old ones - to protect against the risk of a private key being compromised.
The signer can begin using a new key at its discretion and signals the change to the verifier using the kid value. The verifier knows to go back to the JWKS URI to re-retrieve the keys when it sees an unfamiliar kid value. The JWKS document at the JWKS URI will retain recently decommissioned signing keys for a reasonable period of time to facilitate a smooth transition.
Rotating our keys
You must react to a previously unpublished kid by retrieving the key from our JWKS Endpoint. We recommend that you implement a caching mechanism such that keys are read once and cached rather than be read from the JWKS endpoint for every use.
Rotating your keys
If you are using private key JWT for client authentication with a JWKS endpoint, you should rotate your keys periodically.
To do this:
- Add the new key to your JWKS first.
- Then, start using the new private key to sign your JWTs.
- Wait for a suitable period of time before removing the old key from your JWKS.
When we encounter a kid/alg combination that we don't have cached, we will immediately retrieve the JWKS from your endpoint to cache the new key and verify the provided JWT.
Should this key be missing from the JWKS at this time, then a default delay of 60s (configurable) is enforced before another attempt will be made to fetch the JWKS again when another unknown kid/alg combination is encountered.
Diagnosing issues with private key JWT authentication
Analysing a JWT
You can use jwt.io to analyse a client assertion. This service works offline, so no data is exfiltrated.
Copy the base64 string from the client_assertion, then you should be able to see the decoded token values on the right hand side, such as signing algorithm, key ID, etc.
As a minimum, the assertion should contain:
- aud - audience, this should match the token endpoint as per the OpenID well known endpoint
- exp - expiry of the token, should be valid for 5 mins, not more than 30 mins
- iss - issuer, client ID of the relying party application making the request
- jti - jwt token identifier - a unique identifier for the JWT token
- sub - same as iss
The following are optional:
- iat - 'issued at' timestamp
Header items:
- kid - key identifier
- alg - algorithm used for signing
Common issues
If the response indicates that the client authentication is invalid, then this might mean the JWT validation has failed. This can be for few reasons, such as:
- invalid private key used
- invalid aud (for example, missing port number)
- invalid exp (for example, not more than 30 mins from time of auth)
- no public key found that matches the kid/alg combination
- bad JWKS endpoint - see below
- exp and iat must be numeric values, not strings i.e. not enclosed in speech marks
Possible reasons
Networking issues - e.g. timeout, connection closed
A networking problem between CIS2 Authentication and your JWKS endpoint. You should make your public key JWKS endpoint accessible on the public internet via a domain name and path. Access to this endpoint can be restricted to the IP addresses listed above, making sure that the full CIDR ranges are allowed (during releases, it will generally swap between the two IP addresses, or 4 in the case of the production environment).
CIS2 Authentication only supports JWKS files over HTTPS on the standard port (443).
Missing port number
The aud claim in the JWT is missing the port, e.g. 443.
The aud claim should match the token endpoint as per the configuration document for the relevant environment.
Keys at JWKS endpoint not in a keys array
The JWKS endpoint is not structured correctly - it should be an array of JWK objects in JSON:
JWKS endpoint formatting
Incorrect:
{
"kty": "RSA",
"n": "s3jdcy-blahblah-long-string",
"e": "AQAB",
"alg": "RS512",
"kid": "some-key-id-123",
"use": "sig"
}
Correct:
{
"keys": [
{
"kty": "RSA",
"n": "s3jdcy-blahblah-long-string",
"e": "AQAB",
"alg": "RS512",
"kid": "some-key-id-123",
"use": "sig"
}
]
}
Invalid authentication mechanism
Invalid authentication method for accessing this endpoint.
This can occur if any of the parameters used for generating the assertion is invalid, for example invalid client assertion type, or a client assertion type which also contains a client secret parameter. Usually occurs when you transition from client secret to private key JWT client authentication.
Invalid algorithm
The JWT assertion signing algorithm must be the same one that is set up in the client configuration.
Invalid realm
Mixing OIDC realm authentication, with the aud as the token endpoint of the Healthcare realm, and vice versa.
Whitespace
There shouldn't be any unnecessary spaces in the values of any of the JWT parameters.
Invalid or too long JWT validity
If the exp of the JWT is greater than 30 minutes, it will not be accepted - 5 minutes expiry time for JWT is recommended.
According to RFC 7523,
The JWT MUST contain an "exp" (expiration time) claim that
limits the time window during which the JWT can be used. The
authorization server MUST reject any JWT with an expiration time
that has passed, subject to allowable clock skew between
systems. Note that the authorization server may reject JWTs
with an "exp" claim value that is unreasonably far in the
future.
Use of a key that does not exist at their JWK endpoint
The key used to sign the assertion is not in the Relying Party JWKS endpoint when looked up by kid/alg.
JWT attribute name spelling mistakes
Simple typos in the attribute/claim names can stop the token from being validated.
Helpful websites
These websites can help diagnose issues:
- JWT.IO - a JWT verifier and debugger
- JSON Web Token Verifier - similar to JWT.IO, but accepts a JWKS URL for signature validation
- JWT Decoder - provides more comprehensive information on errors, including attribute type issues
These websites are not affiliated with the NHS or to be assumed as trusted, and you should use them with care.
Last edited: 21 January 2026 3:41 pm