API access with auto-enrolment

Will Jehring -

 

Use case

As a third party with a web app or service

  • I want to use data from Core within my system
  • I want to use identities from my own system
  • I expect that the owner of the Core service will trust my app or service

High level workflow

  • A third party client and identity provider are registered with Black Pear.
  • The data controller for the Core service confirms that they trust both the client and the identity provider.
  • The service sends user information from the identity provider to the Core Auth service.
  • Core Auth registers the user and role, issuing an access token.
  • The service selects the role the user is working in from those offered by Core Auth for that user.
  • The service uses the access token and role id to connect to Core APIs.

Screenshot

API Agreement

This workflow requires an Extended Core API Agreement and API use is controlled by a Usage Plan.

Technical workflow

Background

Core APIs require the following data to be sent on a request:

  1. An access token issued by Core Auth.
  2. The id of a PractitionerRole resource associated with a user that exists within Core.
  3. The API key provided to the third party by Black Pear.

All requests in Core must come from a user with a role. Therefore, a third party service needs to obtain a Core access token that proves the identity and access rights of the user, and a role identifier.

To gain the access token, the third party needs to carry out an OAuth token exchange. The token exchange process will return an access token, which can be sent to Core Auth in order to get additional information about the user bound to the access token. Included in this information are the roles assigned to the user. There may be multiple roles returned, and the third party is responsible for picking the correct one. With this information, as well as the API key provided by Black Pear, the third party service will be able to make requests to Core services.

Prerequisites

Before a third party can exchange tokens, they first need to be registered as a trusted client by Black Pear. In order to do so, please follow the guide here. Once the client has been configured, they will be given their client id for use in the token exchange process.

 

Step 1: Token Exchange

There are three pieces of information a third party needs to provide in the token exchange:

  1. the client assertion, to prove the identity of the third party making the request
  2. the subject token, to prove the identity of the user account making the request
  3. the id of the service to access

The token exchange request will be made against our Core Auth service. This has two different environments:

To get a full statement of supported routes and grant types, make a GET request to <environment url>/.well-known/openid-configuration.

 

Token Exchange Request

To get an access token for the user, the third party must make a POST request to the token endpoint (`<Core Auth URL>/token`) with an `application/x-www-form-urlencoded` payload containing the following fields:

  • grant_type - This will always be `core-token-exchange`.
  • client_assertion - The generated client assertion JWT. This must be generated per-request - reuse of a client assertion will result in a rejected request.
  • client_assertion_type - This will always be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
  • subject_token - The generated subject token.
  • subject_token_type - This will always be `urn:ietf:params:oauth:token-type:jwt`.
  • scope - The OAuth scopes that are required for the returned access token. These will likely be `openid profile email directcare`.
  • service - The URL of the Core Metadata service and the id of the `HealthcareService` the user requires access to.

Example cURL request:

curl --location 'https://auth.core-beta.blackpear.com/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=core-token-exchange' \
--data-urlencode 'client_assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'subject_token=eyJraWQiOiJJUmxJTW5HZmZSNT...' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:jwt' \
--data-urlencode 'scope=openid profile directcare email' \
--data-urlencode 'service=https://13e6449ed9d24e8682.core-beta.eu-west-2.quickfhir.cloud|72c860ce-4cd9-4ea2-8e53-caa65b6d3e0a'

If the exchange was successful, Core Auth will return a `200` response with a payload containing the following fields:

  • access_token: The access token used in requests to Core services.
  • expires_in: the number of seconds until the access token expires.
  • token_type: the type of token (this will always be 'access token').
  • scope: the scopes provided in the access token. If the third party does not have the right to access some of the scopes requested, this value may contain a subset of the requested scopes.
  • practitioner_role_id: The id of the PractitionerRole selected by the third party.

Example response body:

{
"access_token": 'miU3dGvVxTQuO-HF7eXGv9j3iFA8KvgcFe6MsiqkRKb',
"expires_in": 3600,
"token_type": 'access_token',
"scope": 'openid email directcare',
"practitioner_role_id": '93983599-abb7-4b1c-9d6d-a1b91890f19a'
}

 

Client Assertion

The client assertion must be a JWT signed (using RS256) with a JWK that can be found in the third party's JWKS endpoint (provided during registration). The claims it MUST contain are:

  • sub - The subject of the token, in this case the client id provided by Black Pear Pear.
  • iat - The timestamp (NumericDate value, i.e. seconds since 1970) of the datetime the token was issued.
  • exp - The timestamp (NumericDate value, i.e. seconds since 1970) of the datetime the token will expire.
  • jti - The unique identifier of the JWT.
  • iss - The issuer of the token. This should match the client id provided to the third party by Black Pear.
  • aud - The audience of the token, in this case the URL of the Core Auth service.
  • system - The identifier of the service making the request, including version/build number.

An example payload:

{ 
"sub": "third-party-client",
"iat": 1686128808,
"exp": 1916239022,
"jti": "06dcf047-8e0e-4d43-b0c0-22d090dfdf28",
"iss": "third-party-client",
"aud": "https://auth.core-beta.blackpear.com",
"system": "PIP@1.2.0"
}

All of the above claims are part of the standard JWT claims except 'system'. For more details on those claims, please consult RFC 7519 JSON Web Token (JWT).

Using the private key that corresponds to a public key returned from the third party client's JWKS endpoint, the third party must sign the payload in a JWT. The header of the JWT must contain a 'kid' that matches the 'kid' of the JWK used to sign the token. For more information on JWKs, please see RFC 7517 JSON Web Key (JWK).


The created JWT will take the form of three base64 strings, separated by a period, for example:


eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ4TlpzVWtzSlFORTMxRFRXYXRHNDU4ODRZZUVaMVNuT21KVHg4M3FPUUEifQ.eyJzdWIiOiJodHRwczovL3RoaXJkLXBhcnR5LWlzc3Vlci5jb20iLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTkxNjIzOTAyMiwianRpIjoiMDZkY2YwNDctOGUwZS00ZDQzLWIwYzAtMjJkMDkwZGZkZjI4IiwiaXNzIjoiaHR0cHM6Ly90aGlyZC1wYXJ0eS1pc3N1ZXIuY29tIiwiYXVkIjoiaHR0cHM6Ly9hdXRoLmNvcmUtbGF0ZXN0LmJsYWNrcGVhci5jb20iLCJzeXN0ZW0iOiJQSVBAMS4yLjAifQ.G0nP3ppKMWx2V9USKVOAh3QtBnI5WvsYkwOLJJz2i3bUxe2QZ94mHqieBHVirgnl6V5etB7OQ3Z5NWszYAPx6S0nbjNnUAQRfioK4nAN1PjjhHdjSR_TnIZ8EN5PammOdNKWq-uZJeOa-jd7BfxpnCs2cuvbVC8mXtb1rEYODzRtfyvzV5GCg8D226isPN4Uxtr7Xe0TX5PGi8DMSo9DSxuqyP1bb10VYmAB6_Yg2rQamHPCd7rtuNsFXcNmorZo6S7ETTNObgoSrsfwixh18zL8Rm7SmnBeDodzv2o3kDDzVNX3N8vYoC31vps5L3z2IXIobAsqOrgeJwXadGtRqA

You can copy and paste the above into https://jwt.io to see the constituent parts of the token.

 

 

Subject Token

The subject token is also a JWT signed by a JWK provided by the client during registration. The process for obtaining the subject token is similar to that of the client assertion. Ideally this should be signed using RS256 if possible. Since the subject token could come from a different source (NHS CIS2, for example), then a separate JWKS endpoint can be supplied to Black Pear in order to verify the token. The required claims for the subject token are:

  • iss - The issuer of the token. The expected issuer(s) should be provided to Black Pear as part of registration.
  • sub - The subject of the token, in this case the end-user id provided by the identity provider.
  • aud - The audience of the token, in this case the URL of the Core Auth service.
  • organization - The ODS code of the organisation where the user will be working, in the format `https://fhir.nhs.uk/Id/ods-organization-code|A29390` .
  • name - The full name of the user including titles (for display purposes).
  • role - The user's SDS role code (from a subset of roles supported by Black Pear), in the format `https://fhir.nhs.uk/Id/sds-role-code|R8000|Clinical Practitioner Access Role`.
  • nbf - The timestamp (NumericDate value, i.e. seconds since 1970) of the datetime the token will become valid.
  • exp - The timestamp (NumericDate value, i.e. seconds since 1970) of the datetime the token will expire.

An example payload:

{ 
"iss": "https://third-party-identity-issuer.thirdparty.com",
"sub": "d71a7ce8-2246-4a7a-b4e0-a36118dc3792",
"aud": "https://auth.core-beta.blackpear.com",
"organization": "https://fhir.nhs.uk/Id/ods-organization-code|P8TNR",
"name": "Mrs Test User",
"role": "https://fhir.nhs.uk/Id/sds-role-code|R8000|Clinical Practitioner Access Role",
"nbf": 1686321374,
"exp": 1786321374
}

 

The system also supports the standard claims from OpenID Connect Core, so if these are available they can be provided to enrich the provided user information.

If the subject token is provided by another identity provider, Black Pear can map claims from the provider's token, as long as the token contains the required information for role creation.

 

Step 2: User Role Lookup

To make a request it is necessary to know the id of the `PractitionerRole` the third party will be acting in. In order to lookup the roles available, the third party should make a `userinfo` request. This takes the form of a GET request to the `userinfo` endpoint (`<Core Auth URL>/me`) with an `Authorization` header containing the access token. The value of the header should be formatted `Bearer miU3dGvVxTQuO-HF7eXGv9j3iFA8KvgcFe6MsiqkRKb`.

The returned payload will contain a `practitioner_roles` claim, which will be an array of `PractitionerRole` resources (see https://hl7.org/fhir/R4/practitionerrole.html for more information). It is the responsibility of the third party to implement a mechanism to decide which `PractitionerRole` should be used.

 

Example response from userinfo endpoint:

{
"sub": "https://third-party-identity-issuer.thirdparty.com|d71a7ce8-2246-4a7a-b4e0-a36118dc3792",
"organisations": [
{
"ods_code": "P8TNR",
"name": "Partner Organisation"
}
],
"practitioner_roles": [
{
"resourceType": "PractitionerRole",
"meta": {
"tag": [
{
"system": "https://www.core.blackpear.com/ig/metadata/CodeSystem/CorePractitionerRoleAccessTagCodes",
"code": "enabled"
}
],
"profile": [
"https://www.core.blackpear.com/ig/metadata/StructureDefinition/CorePractitionerRole"
],
"versionId": "e108ad3b-3b14-436e-bbb0-a0379c824b74",
"lastUpdated": "2023-06-21T14:56:47.310+00:00"
},
"organization": {
"identifier": {
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
"value": "P8TNR"
},
"display": "Partner Organisation"
},
"active": true,
"code": [
{
"coding": [
{
"code": "R8000",
"display": "Clinical Practitioner Access Role",
"system": "https://fhir.nhs.uk/Id/sds-role-code"
}
]
}
],
"practitioner": {
"identifier": {
"system": "https://third-party-identity-issuer.thirdparty.com",
"value": "d71a7ce8-2246-4a7a-b4e0-a36118dc3792"
},
"display": "Test User"
},
"healthcareService": [
{
"reference": "https://13e6449ed9d24e8682.core-beta.eu-west-2.quickfhir.cloud/HealthcareService/72c860ce-4cd9-4ea2-8e53-caa65b6d3e0a",
"display": "Test Service"
}
],
"period": {
"start": "2023-06-21T14:56:47.219Z",
"end": "2023-06-23T14:56:47.220Z"
},
"id": "b94d71d6-4211-4aea-855e-0f00891dcb93"
}
]
}

 

Step 3: Send request to Core API

To make a request against a Core API, the third party must make a valid REST request agains the service in question and, in addition, provide the following three headers:

  • Authorization: `Bearer ` followed by the access token, e.g. `Authorization: Bearer miU3dGvVxTQuO-HF7eXGv9j3iFA8KvgcFe6MsiqkRKb.
  • x-api-key: The api key provided by Black Pear.
  • x-fhir-practitionerrole-id: The id of the PractitionerRole selected by the third party.

 

 

 

Have more questions? Submit a request

0 Comments

Article is closed for comments.
Powered by Zendesk