Skip to main content

Sign in journey

Implement the OpenID Connect Authorization Code Flow to sign users in with CIS2 Authentication.

Overview

CIS2 Authentication conforms to the open standard OpenID Connect (OIDC). OIDC defines three types of authentication flow to cater for different client types: the Authorization Code Flow, the Implicit Flow and the Hybrid Flow. The Authorization Code Flow is the most commonly used flow and is designed for use with web applications. It is the only flow we currently support.

The following sequence diagram shows how your application interacts with CIS2 Authentication to sign in a user:

Sequence diagram showing how an application interacts with CIS2 Authentication

In words:

  1. The user (a healthcare worker) launches the healthcare application.
  2. Because the user isn't signed in, the healthcare application redirects the user's browser to CIS2 Authentication's authentication endpoint.
  3. The user signs in to their CIS account (using their chosen authenticator).
  4. CIS2 Authentication redirects control back to the healthcare application, with an authorisation code.
  5. The healthcare application calls CIS2 Authentication's token endpoint, with the authorisation code, and receives an ID token and an access token in return.
  6. Optionally, the healthcare application calls CIS2 Authentication's user info endpoint, with the access token, and receives the user information in return.
Using an authentication component

Using an authentication component

This page explains how to hand-code your integration with CIS2 Authentication.

However, because CIS2 conforms to the open standard OIDC, there are a number of off-the-shelf authentication components that could make your integration easier.

For details, see Choosing an authentication component.


Authentication request

Request

To sign a user in, send their browser to our authorisation endpoint using an HTTP GET request (you must use TLS i.e. HTTPS).

To find the URL for our authorisation endpoint, discover it dynamically via the configuration document for the relevant environment.

You may generate the request either:

  • indirectly via an HTTP 302 redirect response, for example in response to a user attempting to access a protected resource, or
  • directly, for example as a result of a login button being selected

An example of each is given below:

Example indirect request

HTTP/1.1 302 Found
  Location: am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/authorize?
    response_type=code
    &scope=openid%20profile
    &client_id=999999999999.apps.national
    &state=af0ifjsldkj
    &redirect_uri=https%3A%2F%2Fwww.nationalsupplier.nhs.net%2Fcallback

Example direct request

GET am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/authorize?
    response_type=code
    &scope=openid%20profile
    &client_id=999999999999.apps.national
    &state=af0ifjsldkj
    &redirect_uri=https%3A%2F%2Fwww.nationalsupplier.nhs.net%2Fcallback

Authentication successful response

If the user is successfully authenticated, we'll return an authorisation code to your application. This is achieved by returning an HTTP 302 redirect request to the user's browser requesting that the response is redirected to the redirection URI you specified in the authentication request.

The response will contain a code parameter and state parameter. You must ignore unrecognized response parameters.

The code parameter holds the authorisation code which is an opaque string value. You can present it to our token endpoint to obtain ID, access and refresh tokens (see below). The code is valid for 120 seconds and can only be used once.

The state parameter contains the state value provided in the original authentication request.

You must implement CSRF protection for its redirection URI. This is typically accomplished by requiring any request sent to the redirection URI endpoint to include a value that binds the request to the user-agent's authenticated state (e.g. a hash of the session cookie used to authenticate the user-agent). You should use the state parameter to achieve this as described in the OAuth 2.0 Authorization Framework. For more details, see Security considerations.

Example successful response

HTTP/1.1 302 Found
  Location: https://www.nationalsupplier.nhs.net/callback?
    code=4g4jJrMMV3DF8uO_kj-Ql3hmzUE
    &state=af0ifjsldkj

Authentication error response

If the authentication request fails, we'll return an error response. As for a successful response this is achieved by returning an HTTP 302 redirect request to the user's browser requesting that the response is redirected to the redirection URI you specified in the request.

The response will contain error, error_description and state parameters. 

Most error codes are due to an invalid request format, for example a response_type other than code. However the following error may occur as the result of a valid request:

Error Meaning
interaction_required    An authentication request was made with the prompt=none parameter but the user is not currently authenticated.

If the client identifier or redirection URI specified is invalid an error will be returned directly to the browser. Similarly HTTP errors unrelated to OpenID Connect will be returned to the browser using the appropriate HTTP status code.

Failure of the user to authenticate successfully, for example failure to enter valid credentials, will result in a response not being returned to your application at all.

Example error response

HTTP/1.1 302 Found
  Location: https://www.nationalsupplier.nhs.net/callback?
    error=unsupported_response_type
    &error_description=Response%20type%20is%20not%20supported
    &state=af0ifjsldkj

Post-authentication URLs and bookmarks

After we redirect the user's browser to your redirection URL, you'll need to display a web page of some sort. This might be the home page of your application, or another page within your application. If we returned an error response, it might be an error page.

Depending on how your code works, your redirection URL might be displayed in the user's browser, along with the one-off authorisation code. If the user bookmarks the page, it won't work for them because in a future session the URL won't be valid.

Therefore, you should include a mechanism to ensure the URL reflect the page being displayed, and is therefore bookmarkable. For example, you could:

  • redirect the user's browser to the relevant page
  • return the page directly from the redirection URL but rewrite the URL using JavaScript within the web page

Token request

Request

Once you've received an authorisation code, you can request the associated ID, access and refresh tokens. Do this by sending an HTTP POST request over TLS directly to our token endpoint.

To find the URL for our token endpoint, discover it dynamically via the configuration document for the relevant environment.

You must send the following parameters, using the "application/x-www-form-urlencoded" format with a character encoding of UTF-8 in the HTTP request entity-body:

Name Description
grant_type This must be authorization_code.
code The authorisation code you received in response to the authentication request.
redirect_uri The redirection URI you supplied in the original authentication request.
client_id Your application's client identifier.
client_assertion_type If you are using 'private key JWT' or 'client secret JWT' client authentication (see below), this must be urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
client_assertion If you are using 'private key JWT' or 'client secret JWT' client authentication (see below), this is your authentication JWT.
client_secret If you are using 'client secret' client authentication (see below), this is the client secret that you received when you registered to access an environment.

Example request

POST https://am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/access_token HTTP/1.1
  Host: am.nhsdev.auth-ptl.cis2.spineservices.nhs.uk:443
  Content-Type: application/x-www-form-urlencoded
   
  grant_type=authorization_code
  &code=4g4jJrMMV3DF8uO_kj-Ql3hmzUE
  &redirect_uri=https%3A%2F%2Fwww.nationalsupplier.nhs.net%2Fcallback
  &client_id=999999999999.apps.national
  &client_secret=50a6122b-6907-4a13-948e-f31d4c4714d5

Client authentication

When calling our token endpoint, your application needs to prove its identity so we can be sure your request is genuine.

We provide three methods for this, as follows:

Method Description
Private key JWT Recommended. Authentication using a JWT signed with a private key owned by you. You must send the JWT in the client_assertion parameter with the client_assertion_type parameter set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer. You must expose your public keys as a JWKS endpoint.
Client secret JWT Authentication using a JWT created with a Hash-based Message Authentication Code (HMAC) calculated from a client secret used as a shared key. You must send the JWT in the client_assertion parameter with the client_assertion_type parameter set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
Client secret Authentication using your client secret in the client_secret parameter in the request body. 

You'll need to decide which method to use, and tell us about it when you register to access an environment.

Recommendation

We recommend you use private key JWT authentication, due to its increased security and ability to seamlessly roll over to a new keypair without requiring a configuration change.

You may start with client secret authentication, but should move to private key JWT authentication before you go live. 

Authentication JWT

For both the private key JWT and client secret JWT mechanisms, you must construct a JWT that must contain the following required claim values and may contain the following optional claim values:

Claim Status Description
iss required Issuer. This must be your client ID.
sub required Subject. This must be your client ID
aud required Audience. A value that identifies us as an intended audience. This value must exactly match the value of our token endpoint URI as given in the relevant configuration document (see Discovery for more details).
jti required JWT ID. A unique identifier for the token, which can be used to prevent reuse of the token. These tokens must only be used once.
exp required Expiration time on or after which the JWT must not be accepted for processing. This is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC. We suggest an expiry after 5 minutes.
iat optional Time at which the JWT was issued.

The JWT may contain other claims. Any claims used that are not understood will be ignored.

Private key JWT

For private key JWT authentication, you must sign the JWT using a private key owned by you.

The example below shows the use of a JWT signed with a private key to authenticate (in this example the JWT value has been abbreviated).

Example JWT with private key authentication

POST https://am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/access_token HTTP/1.1
  Host: am.nhsdev.auth-ptl.cis2.spineservices.nhs.uk:443
  Content-Type: application/x-www-form-urlencoded
 
  grant_type=authorization_code&
  &code=4g4jJrMMV3DF8uO_kj-Ql3hmzUE
  &redirect_uri=https%3A%2F%2Fwww.nationalsupplier.nhs.net%2Fcallback
  &client_id=999999999999.apps.nationa
  &client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&
  &client_assertion=eyJhbGciOiJFUzI1NiIsImtpZCI6IjE2In0.a ... b.c ... d

The JWT consists of three Base64URL encoded elements separated by a . character. The first element is the token header. Decoding the value from the example above gives the following JSON document object:

Example token header (decoded from Base64URL)

{
  "alg":"ES256",
  "kid":"16"
}

This specifies that the token has been signed with ECDSA utilising a P-256 curve and SHA-256 hash algorithm using the key identifier 16.

Decoding the second element would give the JSON object containing the claims about your application, such as the client identifier and expiration date.

The third element is the signature. We advertise our supported signing algorithms via our configuration documents as described in Discovery - for client authentication, these are listed in the token_endpoint_auth_signing_alg_values_supported field.

For details on how to use a JSON Web Key Set Endpoint to publish the details of the key associated with kid, see Key management.

Client secret JWT

For client secret JWT authentication, the JWT is signed using an HMAC SHA algorithm, such as HMAC SHA-256. The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of client_secret as the shared key.

Token successful response

If the request validation is successful, we'll return an HTTP 200 OK response including ID, access and refresh tokens as in the example below:

Example HTTP 200 OK response

HTTP/1.1 200 OK
  Content-Type: application/json
  Cache-Control: no-store
  Pragma: no-cache
 
  {
   "access_token": "XeFKw71aKiVMTAml8MNbk_ZLiqk",
   "refresh_token":"GujHL12JL2wk86xNdf1McD3nty4",
   "scope":"openid profile"
   "id_token":"eyJ0eXAiOiJKV1QiLCJraWQiOiJiL082T3ZWdjEreStXZ3JINVVpOVdUaW9MdDA9IiwiYWxnIjoiUlMyNTYifQ.ewogICJhdF9oYXNoIjogIjZUcEZsS3BRd1ZJT3RVTTZqcUJtWWciLAogICJzdWIiOiAiOTk5OTk5OTk5OTk5IiwKICAiYXVkaXRUcmFja2luZ0lkIjogImIwYzhiNWJhLWE0OWItNDhmYi1hZGJmLTVjYmU1NmUzM2YwZS0zMTY4MjgiLAogICJpc3MiOiAiaHR0cHM6Ly9hbS5uaHNkZXYucHRsLm5oc2QtZXNhLm5ldDo0NDMvb3BlbmFtL29hdXRoMi9yZWFsbXMvcm9vdC9yZWFsbXMvb2lkYyIsCiAgInRva2VuTmFtZSI6ICJpZF90b2tlbiIsCiAgImF1ZCI6ICI5OTk5OTk5OTk5OTkuYXBwcy5uYXRpb25hbCIsCiAgImNfaGFzaCI6ICIxdjZ1WUdjUU55OW5mU05KUnRPRjB3IiwKICAiYWNyIjogIkFBTDFfVVNFUlBBU1MiLAogICJvcmcuZm9yZ2Vyb2NrLm9wZW5pZGNvbm5lY3Qub3BzIjogInQyRnJaVEREXzU2RUJGZThidDB0QVpuLWQ5ZyIsCiAgInNfaGFzaCI6ICJ4M1hudDFmdDVqRE5DcUVSTzlFQ1pnIiwKICAiYXpwIjogIjk5OTk5OTk5OTk5OS5hcHBzLm5hdGlvbmFsIiwKICAiYXV0aF90aW1lIjogMTU4OTgxNjQ4OCwKICAicmVhbG0iOiAiL29pZGMiLAogICJleHAiOiAxNTg5ODIwODQ0LAogICJ0b2tlblR5cGUiOiAiSldUVG9rZW4iLAogICJpYXQiOiAxNTg5ODE3MjQ0Cn0=.iMllAqigJ2o1Iep2o65P8xESjEtNCid4j4bUNfxDcYkuXEkCjXXJScpyL80CEK3oOYCDZXy6vRCcYRn2gkglJz4_QFnP2l8SnIKsUUgL99uWTPqC7Rjtk6l0mrehSRCWqp3lpPLSzyThx484cGjgptkd_UvV5mi77VjvmBs3yVBdvW_l0iQXbrvvIsCjoCikTvK90OAhnPwF5aq36xKttXrgysdFiwkwJzVdBv_OrUYwuO9MJTvScbiDJpZj7P_DZ1e8xrhuGDHt9SpLLCLy_Ewq5ZAlb7cA7QdxTu9C3GtBQm3O-KgUgfouHHzMvcJ-mtbWNJMF-ZKGdbC4Pi7A",
    "token_type":"Bearer",
    "expires_in":3600
  }

The response body will include the following parameters:

Name

Description

access_token The access token which you can use to access our UserInfo endpoint. This is an opaque value.
token_type Always set to Bearer.
refresh_token A refresh token which can be used to obtain a new access token - although we don't recommend this as explained further down.
expires_in The lifetime in seconds of the access token. Always set to 3600.
id_token The Base64URL encoded ID token. This is described further below.

ID token

The ID token is a security token that contains claims about the user's authentication. It is represented as a JWT as described in the JSON Web Token Specification and is signed using JWS as described in the JSON Web Signature Specification.

The ID token consists of three Base64URL encoded elements separated by a . character. The first element is the JOSE (JSON Object Signing and Encryption) header describing the signing algorithm used, the second element contains the claims and the third the signature.

You can decode JWTs for inspection using an online website such as https://jwt.io/.

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=. For details on how to obtain the public key associated with kid, see Key management.

Decoding the second element gives the JSON object containing the claims about the user's authentication. For example:

Example claims about the user's authentication

{
  "at_hash": "6TpFlKpQwVIOtUM6jqBmYg",
  "sub": "999999999999",
  "auditTrackingId": "b0c8b5ba-a49b-48fb-adbf-5cbe56e33f0e-316828",
  "iss": "https://am.nhsdev.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/oidc",
  "tokenName": "id_token",
  "aud": "999999999999.apps.national",
  "c_hash": "1v6uYGcQNy9nfSNJRtOF0w",
  "acr": "AAL1_USERPASS",
  "org.forgerock.openidconnect.ops": "t2FrZTDD_56EBFe8bt0tAZn-d9g",
  "s_hash": "x3Xnt1ft5jDNCqERO9ECZg",
  "azp": "999999999999.apps.national",
  "auth_time": 1589816488,
  "realm": "/oidc",
  "exp": 1589820844,
  "tokenType": "JWTToken",
  "iat": 1589817244, 
  "sid": "330bc4641e7e0c8c5acb3e98b096cf08edeb6ba6fc1fcc254233099cb12ac119",
  "selected_roleid": "656006137102",
  "id_assurance_level": "3",
  "authentication_assurance_level":"3"
}

The third element is the signature for the JSON object. For details on how this signature is created and how to validate it, see the JSON Web Signature Specification.

The ID token contains the following claims of interest.

Claim Description
iss Our Issuer identifier as specified in the relevant configuration document (see Discovery for more details).
sub A unique identifier for the user. See Scopes and claims for more details.
aud Your client identifier, as assigned to you when you registered to access an environment.
exp The time on or after which the ID token must not be accepted for processing. This is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC. Set to expire after 1 hour.
iat The time at which the JWT was issued. This is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC.
auth_time The time at which the user authentication occurred. This is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC. If the user was already authenticated and wasn't required to re-authenticate (due to the setting of the prompt parameter), this is the time they originally authenticated.
nonce Used to associate a client session with an ID token, and to mitigate replay attacks. If you provided this in your authentication request, it is passed through unmodified.
acr The Authentication Context Class Reference for the authentication event. For details see Authenticator guidance for developers.
amr The Authentication Methods Reference for the authentication event.  For details see Authenticator guidance for developers.
at_hash A case-sensitive hash of the associated access token. For details see the 'Access token validation' below.
sid

An optional session identifier for use with back-channel logout, for example 330bc4641e7e0c8c5acb3e98b096cf08edeb6ba6fc1fcc254233099cb12ac119.

This will initially only be present for smartcard authentications, and is solely for use with back-channel logout - it should not be assumed to be cross-referenced to any other session identifiers.

selected_roleid An optional value indicating the 12 digit unique identifier for the National RBAC Role Profile (roleProfileCode) that the user (optionally) selected. This value will correspond to one of the nhsid_rbac_roles.person_roleid values from the UserInfo claims, so further information about the role profile can be determined from the corresponding claim. How the role selection is performed varies depending on the authentication method and for a smartcard the version of the Identity Agent installed. See Role selection for more details. Role selection isn't limited to smartcards and all authentication methods are capable of requiring the user to choose a role.
id_assurance_level The level of assurance performed on the user's identity. This is a string value which can take one of the following values: 0, 1, 2 or 3. These values correspond to Identity Assurance Levels as defined in the NIST Digital Identity Guidelines for Enrollment and Identity Proofing Requirements. An assurance level of IAL3 gives a very high level of assurance of the user's identity including checks such as physical presence for identity proofing and verification of identifying attributes by a trained and authorized individual. You should validate that this claim has a value appropriate for your use case. For access to national clinical systems the id_assurance_level must be 3.

authentication_assurance_level

The level of assurance of the authentication process. This value must be checked to ensure that an appropriate level of authentication has taken place.

Any claims contained in the ID token other than those listed in the table above must be ignored.

ID token validation

You must validate the ID token as follows:

  1. The value of the iss claim must exactly match our Issuer identifier as specified in the relevant configuration document (see Discovery for more details).
  2. The aud claim must contain your client identifier.
  3. If the ID token is received via direct communication between your application and our token endpoint, then TLS server validation may be used to validate the issuer in place of checking the token signature (see TLS requirements). You must validate the signature of all other ID tokens according to  JSON Web Signature Specification using the algorithm specified in the JWT alg header parameter. You must use the keys provided by us as described in Key management section. 
  4. The current time must be before the time represented by the exp claim.
  5. If a nonce parameter was sent in the authentication request, a nonce claim will be present and its value must be checked to verify that it is the same value as the one that was sent in the authentication request. You should check the nonce value for replay attacks. 
  6. If the acr claim was requested, you should check that the asserted claim value is appropriate for your use case. For details, see the Authenticator guidance for developers.
  7. You should check the auth_time claim value and request re-authentication if you determine too much time has elapsed since the last user authentication. For further guidance, see Session management.

Access token validation

You may validate the integrity of the access token using the value of the at_hash claim. Its value is the Base64URL encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token, where the hash algorithm used is the hash algorithm used in the alg parameter of the ID token's JOSE header. The alg value is always RS256 so to obtain the value, hash the access_token value with SHA-256, then take the left-most 128 bits and Base64URL-encode them.

Access token use with national APIs

The access token returned from our token endpoint is only for use with our user info endpoint as described in the sections below.

If you want to use our national APIs, you must also obtain an access token from our API Platform as described in API Platform - security and authorisation.

For applications wanting to use both CIS2 Authentication and a user-restricted RESTful API, we recommended using separate authentication and authorisation.

Token error response

If your token request is invalid or unauthorized, we'll return an HTTP 400 response as in the following example:

Example HTTP 400 response

HTTP/1.1 400 Bad Request
  Content-Type: application/json
  Cache-Control: no-store
  Pragma: no-cache
 
{
  "error_description":"The provided access grant is invalid, expired, or revoked.",
  "error":"invalid_grant"
}

User info request

The ID token retrieved above only contains claims about the authentication event and the identity of the user. You can get other information about the user from our user info endpoint.

To find the URL for our user info endpoint, discover it dynamically via the configuration document for the relevant environment.

You can call our user info endpoint using either HTTP GET or HTTP POST. You must send the access token you obtained from the token endpoint as a 'bearer token'.

We recommend that you use HTTP GET and the access token be sent using the Authorization header as in the example below:

Example HTTP GET using Authorization header

GET https://am.nhsint.auth-ptl.cis2.spineservices.nhs.uk:443/openam/oauth2/realms/root/realms/NHSIdentity/realms/Healthcare/userinfo
  Authorization: Bearer XeFKw71aKiVMTAml8MNbk_ZLiqk

User info response

We'll return the user info claims as a JSON document in an HTTP 200 OK response as in the example below:

Example HTTP 200 OK response

HTTP/1.1 200 OK
  Content-Type: application/json
 
  {
   "sub": "999999999999",
   "name": "Smith Jane Ms",
   "given_name": "Jane",
   "family_name": "Smith"
  }

The user info claims will always include a sub claim. You must verify this check it exactly matches the sub claim in the ID token; if they do not match, you must not use the user info response values.

You should authenticate the OpenID provider either by checking the TLS certificate (see TLS requirements) or by validating the signature of the JWT if provided. For details on how to obtain a signed user info response see the next section.

The claims you receive depend on the scopes you requested (via the scope parameter when you made the authentication request).

For details of the scopes and claims for the user info endpoint, see Scopes and claims.

Signed user info response

During registration, you may request that the user info response is signed, in which case the user info response:

  • will be a signed JWT containing the claims, rather than a JSON document
  • will contain iss and aud claims

You should validate:

User info error response

When a request fails, we'll respond with the appropriate HTTP status code (typically, 400, 401, 403, or 405) and include an error code in the response.

The response will contain error and error_description fields.

Example error response

HTTP/1.1 401 Unauthorized
  Content-Type: application/json
 
{
  "error_description":"The access token provided is expired, revoked, malformed, or invalid for other reasons.",
  "error":"invalid_token"
}

Refresh token use

You should only require access to the user info endpoint at the time of the original user authentication. This should only happen once as part of the authentication journey and you can use the access token immediately and the discard it. You should have no other need to retrieve the user info after this point in time. 

Therefore the use of the refresh token to obtain a new access token is not recommended.

If you do want to use the refresh token, contact us to discuss your use case.

Last edited: 12 March 2026 3:59 pm