Digital Staff Passport API standard - authorisation using OAuth 2.0 private key JWT
How to secure external applications for the Digital Staff Passport using OAuth 2.0 private key JWT.
Overview
The Digital Staff Passport API standard requires external systems to secure their API using OAuth 2.0 private key JWT. This page explains how to do that.
OAuth 2.0 is an open standard for API authorisation and is generally considered to be the best way to secure modern RESTful APIs.
Private key JWT is a type of OAuth 2.0 authorisation where the calling application strongly authenticates itself by creating and signing a JSON Web Token (JWT). Unlike some other types of OAuth 2.0, an end user is not involved - this is a system-to-system, or 'application-restricted', form of authentication.
This approach is deemed suitable for APIs that handle personal or sensitive data within the NHS in England.
How this pattern works
In this pattern, Digital Staff Passport (DSP) authenticates itself by sending a signed JSON Web Token (JWT) to an OAuth 2.0 authorisation server that you provide.
DSP provides you with its public key and signs the JWT with its private key. In return, your OAuth 2.0 server gives DSP an access token, which it then includes with each API request.
The following diagram illustrates the pattern:
The following sequence diagram shows how the various components interact:
In words:
-
The end user, if present, launches the calling application - in this case DSP.
-
Time passes, until the user needs to access an application-restricted API.
-
The calling application generates and signs a JWT, using its own private key.
-
The calling application calls the OAuth 2.0 token endpoint with the signed JWT. In particular, this uses the OAuth 2.0 client credentials flow.
-
The OAuth 2.0 server checks the signature against the application's public key, and returns an access token to the calling application.
-
The calling application calls the application-restricted API, including the access token.
Pattern variations
In order to support a wide variety of tech stacks, we've provided pattern variations that we know are suitable for specific platforms and tools.
If you would like us to consider any further variations, contact us.
Default pattern
The default pattern uses 'vanilla' private key JWT, in which:
- in step 2, we'll supply you with the URL for our public key JWKS
- in step 3, we'll populate the iss field with the client_id you provide
Pattern variation 1: federated identity
This pattern variation uses OpenID Connect Discovery to publish our public keys. This is suitable for teams using Microsoft Azure because it aligns with using Microsoft identity platform with a federated credential.
In this pattern:
- in step 2, we'll supply you with the URL for our OIDC 'well known' endpoint
- in step 3, we'll populate the iss field with our OIDC issuer URL
Detailed integration instructions
The following sections explain in detail how to implement this security pattern.
You might also find it useful to refer to the guidance that explains how we use signed JWT to secure our own APIs: Application-restricted RESTful APIs - signed JWT authentication.
Step 1: DSP generates a key pair
This step happens during system configuration.
DSP generates a 4096-bit RSA private/public key pair.
Note that:
- DSP generates separate key pairs for each environment.
- Within each environment, DSP generates a separate key pair for each NHS organisation.
- Within a given environment, DSP might use the same private/public key pair across multiple integrations.
DSP then:
- stores the private key in a key vault
- publishes the public key as a JWKS at a well-defined URL
- publishes an OIDC 'well-known' endpoint which includes details of our public key JWKS (for use with pattern variation 1)
For more details on how DSP generates a key pair, or to generate a key pair and JWKS for testing, see Step 2: generate a key pair on our Signed JWT authorisation guidance page. You can also use our API platform to publish your public key JWKS for testing - see Step 3: register your public key with us, and use the option to "Ask us to host your public key".
Here's an example public key JWKS containing two separate public keys (the purpose of having multiple keys is to allow for periodic key rotation):
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "DSP_TEST_1",
"alg": "RS512",
"n": "ApKKL1BY_itCl3EDTTlLJzuP33Rl6LbEaDsk75GxdyXzXb0jsbWQ-fG3b2i0T-efcJfmuHvzzAk6NoOSwnG_kRhb6b_n924MyqkGS6_Mb1wMhZdd4GQCU1c0vJLRVYTvrySq9L4M4CP2yCdyHXFv57kanA-R3nRUNO6tIkRWsdD2VVwm6dII_QTnSpKrRgyt0DyYImRxsa52AdpdFAArXiIR1OZgkuwJn8l-6yKT6iXUYhgfI6Pgl3XgJ3i06DDjpkUG-zTpyFlpnxLfgvZ8P-Q6LXvfazWItzDYM5xe37RL8zkus2mip5FCgKHu5Ed59CPYkto2YRaRUKvSoZ1BYPLfMjfEQuHPpqvmfL4MG8N7dzUAHRI6mDuOrWTsxyqJHV-I2MYPEPJx1YlZS-mbpbJj6oQmwWxXvld2X-clvNjePa6CD3R6U6pBnc9y_3luj_JrkKItUqAxBxhIBGM1GdeCJnogDEypXQIOKBMZpy6er_EHFbRXtgQEJ-snIY0TITnsOyjmpyrFWv2pa-fe4jtAPeeo4jUJfcw6iAHbN31WcMfg0GNnV4Td_BmcdmvihbV827_3OmqHdt9HEWJCtkWJvZcDN09WifTdBNvXwy9vKyAHRdmkGG-pdq_JFWt661Y7Dm9ubSN58OzO6uyVOJMHdIpcCjtPb8mkQ8rFheax"
},
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "DSP_TEST_2",
"alg": "RS512",
"n": "Ah-ekOAyAvmO3ZCxCyUWNwGQRR52sMbtWLVPLoFvA0JlCg_qWdtQvsEt4t5KuT0Ujf7h0vcOJJvDwfWX8j1dVFlDR7SMEa48hodw0xqlKRp82LCwl4nScK_OWN7eVgsofyLedfnTKnGlixaROYn7Tt6RGPPO9A1PizEI3bUhC39fo6JwxWNFba_PIMdLFwI2jPwpja4gxNlFUOK-T9HXPxhtLgx6lcrJOL5YHLld9g5pP2JwLl8XvvzlQ295p-tFfV6zRGcIf5t2oHNkFbADo4aYK7FvbK1afJGILYjQgYCJtRZarkU2fb47izyCRJBXnzqPWlh2X3FI7ib6fRwlaTiTj6Rcj2XKrYUBI9nCSaQa8euSw2tNLYAUNPXhKW35hK1CYG5ZKNGrXph6tjJlqTxgQjE2oyciyVPAI_cjyNxaXiPbor7r9ffiFL-Ck75q9IeH1saj7zISziTTMLmK84IC9l3ARRbsZ9eFr7YswDNuHL0ghqTpr9RejzXinWydBLoHSyl06dG4hjX5tlDqutK0PGRhi9pX9EpPqn0wTm3GfOn06C8wtjQDuSECrAlAUskVKDJ0q9e7bZnX5R3izBm30mx2yXTJf7w_iR079h_LMEijzuKhs8LZIpNU3RgVgqkGPnOCPtLt3SpFfExSYAunhslZ9i_bvrX5u-Kp-fLH"
}
]
}
We've also published this public key JWKS at a URL that you can use for testing.
The associated private and public keys are also available below in X.590 PEM format.
DSP_TEST_1 private key
-----BEGIN PRIVATE KEY----- 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 2jAus18iWG8xu+oNjZjM0eDgW1zJYI11ag== -----END PRIVATE KEY-----
DSP_TEST_1 public key
-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgECkoovUFj+K0KXcQNNOUsn O4/fdGXotsRoOyTvkbF3JfNdvSOxtZD58bdvaLRP559wl+a4e/PMCTo2g5LCcb+R GFvpv+f3bgzKqQZLr8xvXAyFl13gZAJTVzS8ktFVhO+vJKr0vgzgI/bIJ3IdcW/n uRqcD5HedFQ07q0iRFax0PZVXCbp0gj9BOdKkqtGDK3QPJgiZHGxrnYB2l0UACte IhHU5mCS7AmfyX7rIpPqJdRiGB8jo+CXdeAneLToMOOmRQb7NOnIWWmfEt+C9nw/ 5Dote99rNYi3MNgznF7ftEvzOS6zaaKnkUKAoe7kR3n0I9iS2jZhFpFQq9KhnUFg 8t8yN8RC4c+mq+Z8vgwbw3t3NQAdEjqYO46tZOzHKokdX4jYxg8Q8nHViVlL6Zul smPqhCbBbFe+V3Zf5yW82N49roIPdHpTqkGdz3L/eW6P8muQoi1SoDEHGEgEYzUZ 14ImeiAMTKldAg4oExmnLp6v8QcVtFe2BAQn6ychjRMhOew7KOanKsVa/alr597i O0A956jiNQl9zDqIAds3fVZwx+DQY2dXhN38GZx2a+KFtXzbv/c6aod230cRYkK2 RYm9lwM3T1aJ9N0E29fDL28rIAdF2aQYb6l2r8kVa3rrVjsOb25tI3nw7M7q7JU4 kwd0ilwKO09vyaRDysWF5rECAwEAAQ== -----END PUBLIC KEY-----
DSP_TEST_2 private key
-----BEGIN PRIVATE KEY----- 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 c+YeSof6qLOv3HbDX+oVVeNec/dQ3epZ6g== -----END PRIVATE KEY-----
DSP_TEST_2 public key
-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgECH56Q4DIC+Y7dkLELJRY3 AZBFHnawxu1YtU8ugW8DQmUKD+pZ21C+wS3i3kq5PRSN/uHS9w4km8PB9ZfyPV1U WUNHtIwRrjyGh3DTGqUpGnzYsLCXidJwr85Y3t5WCyh/It51+dMqcaWLFpE5iftO 3pEY8870DU+LMQjdtSELf1+jonDFY0Vtr88gx0sXAjaM/CmNriDE2UVQ4r5P0dc/ GG0uDHqVysk4vlgcuV32Dmk/YnAuXxe+/OVDb3mn60V9XrNEZwh/m3agc2QVsAOj hpgrsW9srVp8kYgtiNCBgIm1FlquRTZ9vjuLPIJEkFefOo9aWHZfcUjuJvp9HCVp OJOPpFyPZcqthQEj2cJJpBrx65LDa00tgBQ09eEpbfmErUJgblko0atemHq2MmWp PGBCMTajJyLJU8Aj9yPI3FpeI9uivuv19+IUv4KTvmr0h4fWxqPvMhLOJNMwuYrz ggL2XcBFFuxn14WvtizAM24cvSCGpOmv1F6PNeKdbJ0EugdLKXTp0biGNfm2UOq6 0rQ8ZGGL2lf0Sk+qfTBObcZ86fToLzC2NAO5IQKsCUBSyRUoMnSr17ttmdflHeLM GbfSbHbJdMl/vD+JHTv2H8swSKPO4qGzwtkik1TdGBWCqQY+c4I+0u3dKkV8TFJg C6eGyVn2L9u+tfm74qn58scCAwEAAQ== -----END PUBLIC KEY-----
Step 2: DSP registers with your API
This step happens during system configuration.
The DSP team registers DSP with you for a specific NHS organisation.
Ideally, you provide a user interface for the DSP team to use, but alternative processes, such as a service request, are acceptable.
During registration:
- the DSP team shares its NHS organisation-specific public key URL with you
- you provide the DSP team with an organisation-specific client ID as per the OAuth2.0 standard
Step 3: DSP generates and signs a JWT
This step happens during operational use of DSP.
Specifically, it happens when DSP needs to use your API, but only if DSP doesn't already have an active access token.
DSP generates and signs a JWT for use in step 3.
The JWT consists of three parts:
- header
- payload
- signature
Header
The JWT header includes the following fields:
| Field | Description | Type |
|---|---|---|
| alg | The algorithm used to sign the JWT, which is always 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 DSP has multiple public/private key pairs, you must use this to select the appropriate public key from its JWKS. |
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. By default this is the client ID you issued to DSP in step 1, but if you're using pattern variation 1, it is the issuer URL as per our OIDC 'well known' endpoint. | string |
| sub | The subject of the JWT - the client ID. | string |
| aud | The audience of the JWT - the URI of the token endpoint DSP is calling, for example https://<YOUR-DOMAIN>/oauth2/token for your production environment. | string |
| exp | Expiry time of the JWT, expressed as a Numeric Time value - the number of seconds since epoch (for example, a UNIX timestamp). This is set to 5 minutes after the time of creation of the JWT. | number |
| iat |
Issue / creation time of the JWT, expressed as a Numeric Time value - the number of seconds since epoch (for example, a UNIX timestamp). API providers can choose whether to do anything with this claim. It has only been included in the specification because the Microsoft identity platform federated credential pattern mentioned in security pattern variation 1 requires it to be present. |
number |
Example
{
"iss": "<test-app-api-key>",
"sub": "<test-app-api-key>",
"aud": "https://<YOUR-DOMAIN>/oauth2/token",
"exp": "<current-time-plus-5mins-from-jwt-creation>",
"iat": "<current-time>"
}
Signature
The JWT signature consists of the contents of the header and payload, signed with the appropriate private key.
Assembling the JWT
The complete JWT consists of:
- the header, base64 encoded
- a period separator
- the payload, base64 encoded
- a period separator
- the signature, base64 encoded
Generating a test JWT
You can generate a JWT for testing at https://jwt.io/.
Step 4: DSP gets an access token
This happens immediately after step 3.
Once it has a signed JWT, DSP makes a call to your OAuth 2.0 server's token endpoint.
Specifically, this is an HTTP POST . We recommend the following URLs for your token endpoint:
| Environment | Token endpoint |
|---|---|
| Integration test | https://integration-test.<yourdomain>/oauth2/token |
| Production | https://<yourdomain>/oauth2/token |
DSP includes the following data in the request body in x-www-form-urlencoded format:
- grant_type = client_credentials
- client_assertion_type = urn:ietf:params:oauth:client-assertion-type:jwt-bearer
- client_assertion = <the signed JWT from step 3>
Here's a complete example, as a CURL command:
curl -X POST -H "content-type:application/x-www-form-urlencoded" --data \
"grant_type=client_credentials\
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer\
&client_assertion=<DSP'S-SIGNED-JWT>" \
https://<YOUR-DOMAIN>/oauth2/token
Your token endpoint must validate the request as per the OAuth2.0 specification, including, but not limited to:
- verifying the signature against the appropriate public key for the given client ID (iss field) and key identifier (kid field)
- checking the JWT hasn't expired (exp field)
Your token endpoint must then respond with a JSON response body containing the following fields:
- access_token - the access token DSP uses when calling your API
- expires_in - the time after which the access token will expire, in seconds, which is ideally 600 and must be a maximum of 3600
- token_type - must be Bearer
Here's an example response:
{'access_token': 'Sr5PGv19wTEHJdDr2wx2f7IGd0cw',
'expires_in': '600',
'token_type': 'Bearer'}
Refresh tokens
You do not need to include a refresh token in your response.
Error scenarios
If there are any issues with DSP's call to your token endpoint, you must return an appropriate error response, as per the OAuth2.0 specification.
Step 5: DSP calls your API
As appropriate, DSP calls your API as described in the Digital Staff Passport API standard.
DSP includes the following header in its call:
- Authorization = Bearer <access token from step 4>
Here's an example, using a CURL command:
curl -X GET https://<YOUR-DOMAIN>/dsp-api/employees/12345 \
-H "Authorization: Bearer <access token from step 4>"
Your API must validate the Authorization header as per the OAuth2.0 specification, and return a suitable error if validation fails.
Step 6: DSP refreshes its token
DSP's access token expires after a period of time, as per the expires_in parameter - by default this is 600 seconds (10 minutes).
After that, calls to your API must return an HTTP status code of 401 (Unauthorized).
You do not need to provide a method for DSP to refresh its access token. If the token expires, DSP will get a new token, repeating the above process from step 3.
Step 7: DSP rotates its key pairs
Periodically, DSP rotates its key pairs, as follows:
- DSP generates a new key pair, as per step 1.
- DSP adds the new public key to its published public key JWKS, with a new kid, alongside the existing public key.
- DSP starts using the new private key to sign its JWTs, as per step 3.
- DSP removes the old public key from its published public key JWKS.
You do not need to take any action when DSP rotates its key pairs.
You just need to make sure you always validate the JWT signature against the appropriate public key for the given client ID and KID in step 4.
Last edited: 15 January 2025 2:17 pm