Skip to main content

7. Key management

Key Usage

Asymmetric keys are used in a number of ways by OpenID Connect Provider:

  • The Relying Party MAY use a JWT signed with a private key to authenticate to the NHS CIS2 Authentication OpenID Provider when forming a Token Request as described in the Token Request, Client Authentication section.
  • The NHS CIS2 Authentication OpenID Provider will respond to a Token Request with a ID Token in the form of of a signed JWT as described in the Token Request, ID Token section.
  • The NHS CIS2 Authentication OpenID provider may respond to a UserInfo Request with a signed JWT as described in the UserInfo Response section.

JSON Web Key Sets

A signed JWT is comprised of three Base64URL encoded elements separated by a . character. 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=”. 

A Relying Party or OpenID Provider party attempting to verify a signature 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 at a published location. See the JSON Web Key Specification for further details about key sets.

The location of the NHS CIS2 Authentication OpenID Provider is provided by the jwks_uri Claim in the OpenID Provider Configuration Document (see the Discovery section) e.g. https://am.nhsdev.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/oidc/connect/jwk_uri.

Relying Parties wishing to use a JWT signed with a private key to authenticate to the NHS CIS2 Authentication OpenID Provider when forming a Token Request MUST provide a JWKS endpoint during the registration process that complies with the JSON Web Key Specification and MUST include the kid of the signing key in the header of each JWT to indicate which key is to be used to validate the signature. This jwks_uri endpoint MUST be publicly accessible on the internet

A party’s JWK Set can be retrieved by sending a HTTP GET request to the jwks_uri location as in the example below:

HTTP GET request to the jwks_uri location

GET /openam/oauth2/realms/root/realms/oidc/connect/jwk_uri HTTP/1.1
  Host: am.nhsdev.auth-ptl.cis2.spineservices.nhs.uk:443

This will result in a JSON document being returned 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 "b/O6OvVv1+y+WgrH5Ui9WTioLt0=" and alg "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).


Key Rollover

Keys are rolled over by periodically adding new keys to the JWK Set. 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 location to re-retrieve the keys when it sees an unfamiliar kid value. The JWK Set document at the jwks_uri will retain recently decommissioned signing keys for a reasonable period of time to facilitate a smooth transition.

NHS CIS2 Authentication Key Rollover

Relying Parties wishing to validate JWTs signed by the NHS CIS2 Authentication OpenID Provider MUST react to a previously unpublished kid by locating the key from the JWKS Endpoint. It is RECOMMENDED that the Relying Party implements a caching mechanism such that keys are read once and cached rather than be read from the JWKS Endpoint for every use.

Relying Party Key Rollover

Relying Parties using the private_key_jwt method for client authentication with a JWKS endpoint can begin using a new key to sign the client authentication JWT by adding the new signing key public key to the JWKS endpoint and using the private key to sign immediately. When the NHS CIS2 Authentication OpenID Provider encounters a kid/alg combination that it doesn't have in its cache, then it will immediately retrieve the JWKS from the endpoint to cache the new key and confirm the veracity of the provided JWT. Once the new key is being used, the old one can be removed from the JWKS.

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. 


Relying Party JWKS Endpoint

Relying Parties wishing to use a Private Key JWT for client authentication MUST provide a JSON Web Key Set Endpoint containing their private key. This MUST be a public internet facing endpoint and it SHOULD be accessible by a Public DNS Domain. Relying Parties MUST declare their signing algorithms and endpoint URL as part of the registration process.

Relying Parties using a firewall to control access to their endpoint will need to allow access to the endpoint from the following addresses:

Environment IP Range(s)
Live

52.142.148.70/31
51.143.231.182/31

DEV 51.143.229.100/31
INT 51.104.255.212/31
DEP 51.132.152.140/31
REF-1 20.49.138.64/31

Diagnosing issues with Private Key JWT authentication

Analysing a JWT

Clients can use jwt.io website to analyse a client assertion. JWT.io 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 to identify 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 a couple of 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 found
  • bad JWK endpoint - see below

Possible reasons

Networking issues - e.g. timeout, connection closed

A networking problem, between CIS2 Authentication and the Relying Party JWKS endpoint. Relying Party applications should make their 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)

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 OpenID well known endpoint.

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, eg invalid client assertion type, or a client assertion type which also contains a client secret parameter. Usually occurs when relying parties transition from client_secret_post to private_key_jwt

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. 

Last edited: 18 June 2024 2:26 pm