User-restricted RESTful APIs - CIS2 separate authentication and authorisation
Learn how to integrate your software with our user-restricted RESTful APIs - using our CIS2 separate authentication and authorisation pattern.
Overview
This page explains how to integrate your software with our user-restricted RESTful APIs.
In particular, it describes the CIS2 separate authentication and authorisation pattern, which uses our OAuth 2.0 authorisation server. It also describes the signed JWT authentication and authorisation pattern which is used for application authentication.
For a full list of available patterns, see Security and authorisation.
When to use this pattern
Use this pattern when:
- accessing a user-restricted RESTful API
- the end user is a healthcare worker
- you want to separate authentication from authorisation
- you need the healthcare worker's identity information
In particular, this pattern is appropriate when building GP software.
How this pattern works
In this pattern, authentication and authorisation are done separately. You might authenticate the healthcare worker when they sign in but only get authorisation to call the API if and when you need it. You do authentication directly with CIS2 Authentication and then separately do authorisation with our OAuth2.0 authorisation service. You need to register your application separately with the API Platform and with CIS2.
The healthcare worker authenticates with either an NHS smartcard or a modern alternative.
The following diagram illustrates the pattern:
The following sequence diagram shows how the various components interact:
In words:
- The healthcare worker launches the calling application.
- The calling application forwards the healthcare worker's browser to the CIS2 authentication endpoint.
- The healthcare worker authenticates with their preferred mechanism (this could be using their face, fingerprint, security key or a smartcard and PIN).
- CIS2 Authentication redirects the healthcare worker's browser back to the calling application, with an authorisation code.
- The calling application calls the CIS2 Authentication token endpoint, with the authorisation code, and receives an access token and an ID token in return.
- Time passes, until the healthcare worker needs to access a user-restricted API.
- The calling application calls our OAuth2.0 token endpoint (/oauth2/token), passing the ID token, and receives an access token (a token exchange).
- The calling application calls the user-restricted API, including the access token.
Using this pattern with CIS1
If you are already using CIS1 Authentication in your application, see Using CIS2 with CIS1 for API access.
Tutorials
Detailed integration instructions
The following diagram shows the high level integration phases with API platform and the steps below it explain in detail how to use this security pattern.
Environments and testing
As well as production, we have a number of test environments. Each environment is paired with a suitable CIS2 environment. In the steps below, make sure you use the appropriate URL base path:
Environment | Sign-in method | Availability | URL base path |
Paired CIS2 environment |
---|---|---|---|---|
Sandbox | Simulated sign-in - no smartcard needed | Hello World API only (all other sandbox APIs are open-access) | sandbox.api.service.nhs.uk/oauth2 | Not applicable |
Integration test | Simulated sign-in - no smartcard needed - using our mock authorisation service | All APIs | int.api.service.nhs.uk/oauth2-mock | Not applicable |
Integration test | Any of the CIS2 Authentication mechanisms | All APIs | int.api.service.nhs.uk/oauth2 | Integration (INT) |
Production | Any of the CIS2 Authentication mechanisms | All APIs | api.service.nhs.uk/oauth2 | Live |
For more information on testing, see Testing APIs.
Phase 1: Set up your application
This section explains the detailed steps to set up your application before you can initiate authorisation. The steps involved are:
- register your application with CIS2 Authentication
- register your application on the API platform
- generate a key pair
- register your public key
Step 1: Register your application with CIS2 Authentication
To use this pattern you need to register your application with CIS2 Authentication directly.
How you do this depends on which environment you're using. For details, see Onboard to CIS2 Authentication.
Step 2: Register your application on the API platform
To use this pattern, you need to register a new application on the API platform. This allows you to select and manage which APIs the application can access.
You need a separate application for your production environment. Best practice is to register one application for each of the test environments you want to access - integration test or (for Hello World API only) sandbox:
-
If you do not already have one, create a developer account.
-
Navigate to my developer account and sign in.
- Select 'Environment access' on my developer account.
- Select 'Add new application'.
-
Enter the details of your application including application owner and application name to create your new application.
-
Select 'View your new application' to check or edit your application details.
- Click the 'Edit' button to make a note of the API key. If you are editing the security details for production applications, follow the online instructions to set up mobile authentication.
- Click the 'Add APIs' button to add the API you want to use.
For the Hello World (Sandbox) example, you need to select the API "Hello World API - User Restricted (Sandbox)".
Step 3: Generate a key pair
You need to generate a private/public key pair, which is used to create a client_assertion later in the process, for each application you created in Step 2 to access testing or production environments. It must be a 4096-bit RSA key pair.
Note that if you generate your own JWKS file, you must use the RS512 algorithm to do this.
Decide on your Key Identifier (KID) - a unique name to identify the key pair in use. The KID will be used to refer to the key pair when constructing and posting the JWT.
We recommend:
- test-1 for testing
- prod-1 for production use
If you create multiple applications to test across multiple test environments, you need a different KID and key pair for each environment.
If you create subsequent key pairs for key rotation, number them sequentially, for example test-2, test-3 and so on.
Do not re-use a KID.
For development and integration test environments only, you might find it easiest to use an external key generator to create a private-public key pair, and a JWKS file. Do not use this for a production environment.
For production environments (or test environments), generating your own public-private key locally is much more trustworthy.
Generate a private/public key pair using an external key generator - for test environments only
There are several external key generators available on the internet, and while we cannot endorse any one in particular, we know people have had success with https://mkjwk.org/.
To use it, enter:
Key Size: | 4096 |
Key Use: | Signature |
Algorithm: | RS512 |
Key ID: | <your KID, for example test-1> |
Show X.509: | Yes |
This produces:
- "Public Key" - a JWK that you can use to create a JWKS file for uploading (see below)
- "Private Key (X.509 PEM Format)" - your private key in PEM format
- "Public Key (X.509 PEM Format)" - your public key in PEM format
Important - always keep your private key private. Do not send it to us!
To generate a JWKS file from the public key JWK, you need to wrap it with a keys array, for example:
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "YOUR_KID",
"alg": "RS512",
"n": "gY1TGIkH4B872V2xBYFScYvyGjcd_VVVDQiHcTBG-OKbZlFOgrTIAvJNVtBhW4-meJWxIYtPchnykhLOvSPEOImUcypvP5aRw8KxCjPpKlBEMrRmMYf34zT7sP-IBTPsw3TxLLX0YPR59lX8S64dt7uV3Sgn1fXBlZENCDJhLOP6pwpOdyeezrPJDsKmm5CPh1yQ6774cLNbz9HEg5Sxdno1oAYq4ANmJWNntMi6K4z8Mq5TtAe5sakBzr4UV3sLE77ex76gykGDEidi-vlWbig0ITqLvQ4J8TYOHSq7mzrnRbFgfnersGLjNp25aajSnwt29dog77njZiEtj7zb6fQZx47arBUByWqVkuvsN6CbM94xoWdPGD1Pdi6Kd1CoFPDhWWaJgpYhdZl6amXL_Am4Y1nYAowWgY4M5o9BzuCXkG5obZw1n9HZ25qS7Hdjoy510ASWDyF96FlvRpP336iW3utH3pqmED-patuGHvcDWIi1oirlYKwrTRgGbyEuko_MjdJXSSRnzEansD6k4Bp6NNsjpxqTMy1cd9mCRZsd9MNQa4KxY6A2iDAUTLCwwZ9A1DGh2mP7p7AbaP7X_0nxhf4usyc0JriDew5zGnEwdtonOmsPysfSYpV9E7RzLSybTkMoV6oJz9a0mRHT5hsMgb12HBHqvU3jlzNbnt8"
}
]
}
Go to Step 4.
Generate your own private/public key pair - for production or test environments
On Windows, the easiest way to get the BASH shell tools to do this is to install Git For Windows.
On Linux and Mac OS, the BASH shell comes as standard.
Open a BASH shell command prompt and define your KID:
KID=YOUR_KID
Then run both of the following commands:
- openssl genrsa -out $KID.pem 4096
- openssl rsa -in $KID.pem -pubout -outform PEM -out $KID.pem.pub
These commands create the following files:
- YOUR_KID.pem - your private key in PEM format
- YOUR_KID.key.pub - your public key in PEM format
Important - always keep your private key private. Do not send it to us!
If this is a key pair for a production application, and you want us to host your public key, go to Step 4.
If this is a key pair for development or integration testing environments, or a production environment key you want to host yourself, you also need to create a JWKS file to upload.
To do this, first get the "modulus" of your private key, by entering the following BASH shell commands:
MODULUS=$(
openssl rsa -pubin -in $KID.pem.pub -noout -modulus `# Print modulus of public key` \
| cut -d '=' -f2 `# Extract modulus value from output` \
| xxd -r -p `# Convert from string to bytes` \
| openssl base64 -A `# Base64 encode without wrapping lines` \
| sed 's|+|-|g; s|/|_|g; s|=||g' `# URL encode as JWK standard requires`
)
Next, build your JWKS file (using the RS512 algorithm) from your KID and public key modulus by entering the following BASH shell commands:
echo '{
"keys": [
{
"kty": "RSA",
"n": "'"$MODULUS"'",
"e": "AQAB",
"alg": "RS512",
"kid": "'"$KID"'",
"use": "sig"
}
]
}' > $KID.json
This creates the JWKS file YOUR_KID.json for uploading in Step 4.
Step 4: Register your public key
We use your public key to verify your client_assertion later in the process.
There are two ways to do this - either host your own public key, or ask us to host it for you.
Host your own public key
To do this, for applications in development or integration test environments:
- Create a JWKS endpoint to publicly host your public key and note the URL.
- Sign in to your developer account.
- Select 'My applications and teams', 'My applications' and then 'Manage your applications'.
- Select the application you want to add your JWKS endpoint to.
- Edit the public key URL
- Enter the URL of your JWKS endpoint and click Save.
If this public key is for a production application, contact us and tell us:
- your application ID
- the public key URL you want to add, or update
Ask us to host your public key
For applications in development or integration test environments:
- Sign in to your developer account.
- Select 'My applications and teams', 'My applications' and then 'Manage your applications'.
- Select the application you want to add your JWKS public key to.
- Edit the public key URL.
- Choose the JWKS file in JSON format for your public key and click Upload.
- Once it's confirmed as a valid public key, click Save.
We use this public key to create a JWKS endpoint to host your public key and link it to your application in the development or integration environment.
For production applications, contact us and make sure you tell us:
- your application’s App ID, from step 2
- your KID, from step 3
- your public key, from step 3, as an attachment in PEM format
- the APIs you want to use
We use this information to create a JWKS endpoint to host your public key and link it to your application in production.
In the future, we hope to make this process more self-service for production applications. You can track progress or vote for this feature on our interactive product backlog.
Step 5: Authenticate the healthcare worker with CIS2 Authentication
You need to authenticate the healthcare worker with CIS2 Authentication. CIS2 Authentication returns an ID token to the registered callback endpoint on successful authentication.
Your software needs to follow a standard OIDC authorisation code flow with CIS2 Authentication. OIDC clients initiate the CIS2 Authentication authorisation sequence from the browser by calling the CIS2 Authentication authorize endpoint.
Then, the end user signs in to their CIS account using their smartcard or modern alternative.
If they are using a smartcard, they need the following:
- a smartcard reader attached to their device
- the Identity Agent software installed on their device
- the NHS Credential Management software installed on their device
To see which versions of Windows operating systems and browsers are supported, see Supported operating systems and browsers.
To download Identity Agent and NHS Credential Management software, see guidance on setting up and troubleshooting.
If testing with our mock authorisation service, enter the user ID for the test user you want to use.
Once the healthcare worker's authentication is successful, your web server should call the CIS2 Authentication token endpoint and receive a number of tokens, including the ID token.
The ID token has a lifetime of 1 hours, after 1 hour the user must re-authenticate. We strongly recommend to exchange the ID token for our access and refresh tokens as soon as possible.
You need this ID token in step 7 and step 9.
Step 6: Generate a client assertion
Before you can call a user-restricted API, you first need to generate a client assertion. This happens at runtime, so you need to code it into your application.
A client assertion is a JSON Web Token (JWT) that consists of three parts: a header, a payload and a signature. The header specifies the authentication method and token type. The payload contains data (detailed below) and the signature is used to verify the token itself.
We strongly recommend that you use a library to generate your JWT, as this can be a complicated process to perform by hand.
Header
The JWT header includes the following fields:
Field | Description | Type |
---|---|---|
alg | The algorithm used to sign the JWT, which must be RS512. | string |
typ | The token type - JWT. | string |
kid |
The Key Identifier (KID) used to select the public key to use to verify the signature of the JWT, for example test-1. If you have multiple public/private key pairs, this will be used to select the appropriate public key. |
string |
Example
{
"alg": "RS512",
"typ": "JWT",
"kid": "test-1"
}
Payload
The JWT payload includes the following fields:
Field | Description | Type |
---|---|---|
iss | The issuer of the JWT. Set this to your API Key. | string |
sub | The subject of the JWT. Also set this to your API Key. | string |
aud | The audience of the JWT. Set this to the URI of the token endpoint you are calling, for example https://api.service.nhs.uk/oauth2/token for our production environment. | string |
jti | A unique identifier for the JWT, used to prevent replay attacks. We recommend a randomly-generated GUID. | string |
exp | Expiry time of the JWT, expressed as a Numeric Time value - the number of seconds since epoch (for example, a UNIX timestamp). Must not be more than 5 minutes after the time of creation of the JWT. | number |
Example
{
"iss": "<test-app-api-key>",
"sub": "<test-app-api-key>",
"aud": "https://api.service.nhs.uk/oauth2/token",
"jti": "<unique-per-request-id>",
"exp": <current-time-plus-5mins-from-jwt-creation>
}
Signature
The JWT signature consists of the contents of the header and payload, signed with your private key. We recommend you use a library to generate this.
Assembling the JWT
The JWT consists of:
- the header, base64 encoded
- a period separator
- the payload, base64 encoded
- a period separator
- the signature, base64 encoded
Examples
The following code snippets show how to generate and sign a JWT in Python and C#.
For the Python example, PyJWT requires the installation of the crypto extra in order to use RSA keys. To install this:
python -m pip install PyJWT[crypto]
import uuid
from time import time
import jwt # https://github.com/jpadilla/pyjwt
with open("jwtRS512.key", "r") as f:
private_key = f.read()
claims = {
"sub": "<API_KEY>",
"iss": "<API_KEY>",
"jti": str(uuid.uuid4()),
"aud": "https://api.service.nhs.uk/oauth2/token",
"exp": int(time()) + 300, # 5mins in the future
}
additional_headers = {"kid": "test-1"}
j = jwt.encode(
claims, private_key, algorithm="RS512", headers=additional_headers
)
using System; using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using IdentityModel;
using Microsoft.IdentityModel.Tokens;
public class JwtHandler
{
private readonly X509Certificate2 _cert;
private readonly string _audience;
private readonly string _clientId;
private readonly string _kid;
public JwtHandler(string pfxCertPath, string audience, string clientId, string kid)
{ _audience = audience; _clientId = clientId; _kid = kid; _cert = new X509Certificate2(pfxCertPath); }
public string generateJWT(int expInMinutes = 1)
{
var now = DateTime.UtcNow;
var token = new JwtSecurityToken(
_clientId,
_audience,
new List<Claim>
{ new ("jti", Guid.NewGuid().ToString()), new (JwtClaimTypes.Subject, _clientId), }
,
now,
now.AddMinutes(expInMinutes),
new SigningCredentials(
new X509SecurityKey(_cert, _kid),
SecurityAlgorithms.RsaSha512
)
);
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}
}
Step 7: Exchange CIS2 Authentication ID token for access token
Call our token endpoint to exchange the ID token you obtained in step 5 for an access token. This is an HTTP POST to the following endpoint:
https://api.service.nhs.uk/oauth2/token
Note: the above URL is for our production environment. For other environments, see Environments and testing.
You need to include the following form parameters in your call:
Parameter | Description |
---|---|
subject_token | ID token from step 5 |
client_assertion | A signed JWT from step 6 |
subject_token_type | urn:ietf:params:oauth:token-type:id_token |
client_assertion_type | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
grant_type |
As per Section 4.5 of RFC6749, the grant type to receive an access token should be urn:ietf:params:oauth:grant-type:token-exchange |
Here is a complete example, as a CURL command:
curl --location --request POST 'https://api.service.nhs.uk/oauth2/token'\
--header 'Content-Type: application/x-www-form-urlencoded'\
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange'\
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:id_token'\
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer'\
--data-urlencode 'subject_token={CIS2 Authentication ID token}\
--data-urlencode 'client_assertion={jwt token}
If the request is valid, the OAuth 2.0 authorization server responds with an HTTP status 200 and a normal OAuth 2.0 response from the token endpoint. This response contains the following parameters:
Parameters | Description |
---|---|
access_token | The access token issued by the OAuth 2.0 authorization server which can be used to call our user-restricted endpoints |
expires_in | The time after which the access token expires, in seconds |
issued_token_type | urn:ietf:params:oauth:token-type:access_token |
token_type | Bearer |
refresh_token | A token for refreshing the access token once it has expired (see step 11 below) |
refresh_token_expires_in | The time in seconds, after which the refresh token expires |
refresh_count | The number of times the token has been refreshed so far |
Here’s an example:
{
"access_token":"Sr5PGv19wTEHJdDr2wx2f7IGd0cw",
"expires_in":"599",
"issued_token_type":"urn:ietf:params:oauth:token-type:access_token",
"refresh_count":"0",
"refresh_token":"7qvwCqqUUAmzMjRbQyrhdddwBQUJ9vmt",
"refresh_token_expires_in":"43199",
"token_type":"Bearer"
}
Error scenarios
If there are any issues with your call to our token endpoint, we return an error response, refer to the error scenarios section for errors regarding client assertion and the below table for token exchange specific errors:
Error Scenario | HTTP status | Error code | Error message |
---|---|---|---|
Grant type is missing | 400 (Bad Request) | invalid_request | grant_type is missing |
Grant type is invalid | 400 (Bad Request) | unsupported_grant_type | grant_type is invalid |
Grant type is invalid but is supported | 400 (Bad Request) | invalid_grant_type | grant_type is invalid |
Client assertion type is missing or invalid | 400 (Bad Request) | invalid_request | Missing or invalid client_assertion_type - must be 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' |
Subject token type is missing or invalid | 400 (Bad Request) | invalid_request | Missing or invalid subject_token_type - must be 'urn:ietf:params:oauth:token-type:id_token' |
Client assertion (signed JWT) is missing | 400 (Bad Request) | invalid_request | Missing client_assertion |
Client assertion (signed JWT) is malformed | 400 (Bad Request) | invalid_request | Malformed JWT in client_assertion |
Subject token is missing | 400 (Bad Request) | invalid_request | Missing subject_token |
Subject token is invalid | 400 (Bad Request) | invalid_request | subject_token is invalid |
kid header is missing in client_assertion | 400 (Bad Request) | invalid_request | Missing 'kid' header in client_assertion JWT |
kid header is invalid in client_assertion | 401 (Unauthorized) | invalid_request | Invalid 'kid' header in client_assertion JWT - no matching public key |
kid header is missing in subject_token | 400 (Bad Request) | invalid_request |
Missing 'kid' header in subject_token JWT |
kid header is invalid in subject_token | 401 (Unauthorized) | invalid_request | Invalid 'kid' header in subject_token JWT - no matching public key |
typ header is missing or invalid in client_assertion | 400 (Bad Request) | invalid_request | Invalid 'typ' header in client_assertion JWT - must be 'JWT' |
typ header is missing or invalid in subject_token | 400 (Bad Request) | invalid_request | Invalid 'typ' header in subject_token JWT - must be 'JWT' |
alg header is missing in client_assertion | 400 (Bad Request) | invalid_request | Missing 'alg' header in client_assertion JWT |
alg header is invalid in client_assertion | 400 (Bad Request) | invalid_request | Invalid 'alg' header in client_assertion JWT - unsupported JWT algorithm - must be 'RS512' |
alg header is missing in subject_token | 400 (Bad Request) | invalid_request | Missing 'alg' header in subject_token JWT |
sub and iss claims match but are not a valid API Key in client_assertion | 401 (Unauthorized) | invalid_request | Invalid 'iss'/'sub' claims in client_assertion JWT |
sub and iss claims don't match or are missing in client_assertion | 400 (Bad Request) | invalid_request |
Missing or non-matching 'iss'/'sub' claims in client_assertion JWT |
iss claim is missing in subject token | 400 (Bad Request) | invalid_request |
Missing 'iss' claim in subject_token JWT |
jti claim is missing in client_assertion | 400 (Bad Request) | invalid_request | Missing 'jti' claim in client_assertion JWT |
jti claim has been reused in client_assertion | 400 (Bad Request) | invalid_request | Non-unique 'jti' claim in client_assertion JWT |
jti claim is invalid type in client_assertion | 400 (Bad Request) | invalid_request |
Invalid 'jti' claim in client_assertion JWT - must be a unique string value such as a GUID |
aud claim is missing or invalid in client_assertion | 401 (Unauthorized) | invalid_request | Missing or invalid 'aud' claim in client_assertion JWT |
aud claim is missing in subject_token | 400 (Bad Request) | invalid_request |
Missing aud claim in subject_token |
exp claim is missing in client_assertion | 400 (Bad Request) | invalid_request | Missing 'exp' claim in client_assertion JWT |
exp claim is in the past in client_assertion | 400 (Bad Request) | invalid_request | Invalid 'exp' claim in client_assertion JWT - JWT has expired |
exp claim is more than 5 minutes in the future in client_assertion | 400 (Bad Request) | invalid_request | Invalid 'exp' claim in client_assertion JWT - more than 5 minutes in future |
exp claim is invalid type in client_assertion | 400 (Bad Request) | invalid_request |
Invalid 'exp' claim in client_assertion JWT - must be an integer |
exp claim missing in subject_token | 400 (Bad Request) | invalid_request | Missing 'exp' claim in subject_token JWT |
exp claim is in the past in subject_token | 400 (Bad Request) | invalid_request | Invalid 'exp' claim in subject_token JWT - JWT has expired |
exp claim is invalid type in subject_token | 400 (Bad Request) | invalid_request |
Invalid 'exp' claim in subject_token JWT - must be an integer |
JWT signature is invalid | 401 (Unauthorised) | public_key error | JWT signature verification failed |
Public key not set up | 403 (Forbidden) | public_key error |
You need to register a public key to use this authentication method - please contact support to configure |
Public key misconfigured | 403 (Forbidden) | public_key error | The JWKS endpoint for your client_assertion can not be reached |
Step 8: Store access token for later use
Your access token lasts for 10 minutes and you can use it multiple times. To make more than one API call for the signed in healthcare worker, store your access token securely for later use.
Storing your access token reduces the load on our authorisation server and the chance of your application hitting its rate limit.
For details on what to do when your access token expires after 10 minutes, see refresh your access token below.
Phase 3: Interact with our user-restricted APIs
This section explains the detailed activities that you need to perform to call a user-restricted API. The activities involved are:
- determine the healthcare worker's role
- call a user-restricted API
- refresh our access token
- request a new access token from us
Step 9: Determine the healthcare worker's role
With CIS2 Authentication, healthcare workers are allocated one or more roles by their Registration Authority, for example, Clinical Practitioner or Nurse.
Some of our APIs require you to send the healthcare worker's role when you call the API.
Some of our APIs also require you to implement national role-based access control (RBAC) within your software to ensure that the healthcare worker only has access to functions and information that are appropriate for their role.
If the healthcare worker has more than one role, they must select a role before using your software.
There are two ways to determine the healthcare worker's role:
1. Custom approach for smartcard users
If the healthcare worker signs in (to a system that has integrated with CIS2 Authentication) with an NHS smartcard, the Identity Agent software on their device prompts them to select a role immediately after they sign in.
Healthcare workers with one role
If the healthcare worker has only one role, the Identity Agent automatically selects it for them. You do not need to send the role in the request header as the API retrieves it from the Identity Agent software on the healthcare worker's machine.
Healthcare workers with more than one role
If the healthcare worker has more than one role, they have to select one of them when prompted by Identity Agent software immediately after they sign in. You do not need to send the role in the request header, as the API retrieves it from the Identity Agent software on the healthcare worker's machine.
For full details on how to do this, see parts 6 and 7 of the Spine External Interface Specification.
Here's a summary:
- Call the Identity Agent Ticket API to get the ID token for the healthcare worker's session.
- Call the Identity Server to get an SSO Token.
- Call the Identity Server to get the SAML Assertion.
- Get the ssbSessionRoleUid field from the Person block in the SAML assertion - this is the healthcare worker's selected User Role ID, for example 555021935107.
If the API also requires you to implement national RBAC:
- Find the Job Role Profile block in the SAML assertion where the uniqueIdentifier field record matches the above ssbSessionRoleUid.
- Get the nhsJobRoleCode field from the same Job Role Profile, for example S0030:G0100:R0570.
- Check this against the roles and activities in the national RBAC database.
This is not a standards-compliant approach and does not work for other sign-in methods. If your software is already integrated directly with the Identity Agent, it might be appropriate to retrieve the selected healthcare worker role directly from CIS2.
2. Standards-compliant approach
You can get a full list of the healthcare worker's roles from CIS2's authentication server, and present them to the healthcare worker to select a role. If the healthcare worker only has one role, you can select it for them automatically.
This approach uses open standards - OAuth 2.0 and Open ID Connect - and works for all types of authentication, not just smartcards.
If you take this approach, healthcare workers signing in with an NHS smartcard will actually be prompted to select a role twice - once by the Identity Agent and once by your software. Their first selection will be ignored.
To use this approach, make an HTTP GET request to CIS2's /userinfo endpoint. For more details on CIS2 environments, see Onboard to CIS2 Authentication.
You need to include the following headers in your call:
- Authorization = Bearer <your access token from step 5>
Note: you should only call this endpoint within an hour of obtaining an access token. Requests made outside of this window will be rejected.
CIS2 uses standard OpenID Connect scopes to manage what information is available when you call the CIS2’s /userinfo endpoint. To get a list of roles you must include the scope nationalrbacaccess. For a full list of claims returned by CIS2, see Scopes and Claims.
You will receive a response with a JSON response body, containing at least the following fields:
Field | Description |
---|---|
nhsid_useruid | A 12-digit identifier uniquely identifying the healthcare worker and is commonly referred to as 'SDS User ID' |
name | The healthcare worker's full name. |
nhsid_nrbac_roles | An array of the healthcare worker's registered roles |
Each of the entries in nhsid_nrbac_roles contains at least the following fields:
Field | Description |
---|---|
org_code | The organisation's ODS code |
person_orgid | A 12-digit identifier that uniquely identifies the healthcare worker's association with the organisation |
person_roleid | A 12-digit identifier that uniquely identifies the healthcare worker's role at the organisation. This is commonly referred to as 'SDS Role Profile ID' |
role_code | A colon-separated string of codes comprising a Primary, Secondary and Tertiary Job Role Code. This is commonly referred to as 'SDS Job Role Code' |
role_name | A colon-separated string of names comprising a Primary, Secondary and Tertiary Job Role Name |
Here's an example:
{
"nhsid_useruid": "910000000001",
"name": "USERQ RANDOM Mr",
"nhsid_nrbac_roles": [
{
"org_code": "RBA",
"person_orgid": "555254239107",
"person_roleid": "555254240100",
"role_code": "S8000:G8000:R8001",
"role_name": "\"Clinical\":\"Clinical Provision\":\"Nurse Access Role\""
},
{
"org_code": "RBA",
"person_orgid": "555254239107",
"person_roleid": "555254242102",
"role_code": "S8000:G8000:R8000",
"role_name": "\"Clinical\":\"Clinical Provision\":\"Clinical Practitioner Access Role\""
},
{
"org_code": "RBA",
"person_orgid": "555254239107",
"person_roleid": "555254241101",
"role_code": "S8000:G8000:R8003",
"role_name": "\"Clinical\":\"Clinical Provision\":\"Health Professional Access Role\""
}
]
}
The response may include extra optional fields. For more details on the possible fields returned, see the nationalrbacaccess section of the CIS2 Authentication specification.
API error scenarios
If the healthcare worker role validation is not successful, you get the following error messages when you call a user-restricted API.
Error scenario | HTTP status | Error code | Description |
---|---|---|---|
NHSD-session-URID is invalid | 400 | BAD_REQUEST | nhsd-session-urid is invalid |
selected_roleid is missing in your token | 400 | BAD_REQUEST | selected_roleid is missing in your token |
Step 10: Call a user-restricted API
To call our user-restricted APIs, you need to include the following header in your call:
- Authorization = Bearer <your access token from step 3>
Here's an example, using a CURL command:
curl -X GET https://sandbox.api.service.nhs.uk/hello-world/hello/user\
-H "Authorization: Bearer [your access token from step 3]"\
Note: the URL in the above example is for our sandbox environment. For other environments, see Environments and testing.
All being well, you receive an appropriate response from the API, for example:
HTTP Status: 200
{
"message": "Hello User!"
}
Error scenarios
If there is an issue with your access token, we return one of the following error responses:
HTTP status | Code | Description |
---|---|---|
401 (Unauthorized) | invalid_credentials | Access token has expired |
401 (Unauthorized) | invalid_credentials | Access token is invalid |
401 (Unauthorized) | invalid_credentials | Access token is missing |
For detailed error conditions, see the relevant API specification in our API catalogue.
Step 11: Refresh your access token
Your access token from step 7 above expires after 10 minutes and must be refreshed to avoid user re-authentication (see step 5 above).
If your access token has expired, when you call a user-restricted API, we return the following error:
HTTP status | Code | Description |
---|---|---|
401 (Unauthorized) | invalid_credentials | Access token has expired |
Each access_tokens has a corresponding refresh_token. To refresh the access token, submit the expired token’s corresponding refresh token to our token endpoint using grant_type of refresh_token. We return a new pair of access token and it's corresponding refresh token without re-authenticating the healthcare worker.
You need to include the following form parameters in your request to the token endpoint:
Parameters | Description |
---|---|
client_id | The API Key (also known as the Client ID) of your application |
client_secret | The secret assigned to your application’s API Key |
grant_type | As per the OAuth standard, the grant type to refresh your access token should be refresh_token |
refresh_token | The refresh token issued by the OAuth 2.0 authorization server which can be used to refresh the access token |
If you refresh an access token before it expires, the new access token invalidates the original access token immediately.
You can continue to refresh and get new access tokens for up to 12 hours. After 12 hours, you must repeat step 5 above.
Example request
curl -X POST -H "content-type: application/x-www-form-urlencoded" --data \
"client_secret=[YOUR-SECRET]\
&client_id=[YOUR-API-KEY]\
&grant_type=refresh_token\
&refresh_token=[REFRESH_TOKEN]" \
https://api.service.nhs.uk/oauth2/token
Note: the above URL is for our production environment. For other environments, see Environments and testing.
Example response
HTTP/1.1 200 OK Content-Type: application/json { "access_token":"xjPOKAmxlLXQcHkRkBf37AiaGyEx", "expires_in":"599", "refresh_token":"A6dpFV8F1yjsca4zPr5GDO0n7raSB6TQ", "refresh_token_expires_in":"43199", "refresh_count":"1", "token_type":"Bearer" }
Error scenarios
If there are any issues with your call to our token endpoint, in addition to the errors mentioned in step 7, we return the following refresh token specific errors:
Error scenario | HTTP status | Error code | Error message |
Client secret is missing | 401 (Unauthorized) | invalid_request | client_secret is missing |
Client secret is invalid | 401 (Unauthorized) | invalid_client | client_id or client_secret is invalid |
Client ID (API key) is missing | 401 (Unauthorized) | invalid_request | client_id is missing |
Client ID (API key) is invalid | 401 (Unauthorized) | invalid_client | client_id or client_secret is invalid |
Refresh token is missing | 400 (Bad Request) | invalid_request | refresh_token is missing |
Refresh token is invalid | 401 (Unauthorized) | invalid_grant | refresh_token is invalid |
Refresh token has already been used | 401 (Unauthorized) | invalid_grant | refresh_token is invalid |
Access token refresh period has expired | 401 (Unauthorized) | invalid_grant | access token refresh period has expired |
Step 12: Request a new access token from us
Using smartcard for authentication will result in a session up to 12 hours. Other authentication methods result in a shorter session of up to an hour. After that time, calls to our token endpoint with a refresh token (so that you can obtain a new access token) returns the following error:
HTTP status | Code | Description |
---|---|---|
401 (Unauthorized) | invalid_grant | refresh token refresh period has expired |
If this happens, you must send the healthcare worker back through the full authentication process from step 5 above.
If you have any concerns about the session length regarding your use case then contact us about it and we will work with you to make it a smooth as possible healthcare worker experience.
Last edited: 11 June 2025 1:46 pm