Skip to content
Partner Developer Portal

Authentication

EMIS-X utilises modern authentication (authN) and authorization (authZ) using two mechanisms:

  • SAML
  • OAuth/OIDC

Both of these mechanisms are provided by the EMIS-X AuthN & AuthZ solution (also referred to as EMIS-X Users, a misnomer).

How we determine the authentication mechanism

Section titled “How we determine the authentication mechanism”

There are various options and flows for each mechanism. The following process flow is how we determine which of them to use.

flowchart TD
    start([Authentication Protocol Selection]) --> app_type{Application Type?}

    %% Define implementation node (replacing reserved 'end' keyword)
    implementationNode([Protocol Implementation])

    %% Initial application type decision

    %% Simplified application type paths
    app_type -->|Web| api_access{API Access Needed?}
    app_type -->|Mobile/SPA/Desktop| api_access{API Access Needed?}

    %% Common path for all user application types
    api_access -->|Yes| oidc[OpenID Connect]
    api_access -->|No| sso_needed{SSO Needed?}
    sso_needed -->|Yes| sso_type{Only SAML supported?}
    sso_type -->|Yes| saml[SAML 2.0]
    sso_type -->|No| oidc[OpenID Connect]
    sso_needed -->|No| oidc[OpenID Connect]

    %% Machine-to-Machine paths - kept separate
    app_type -->|Machine-to-Machine| oidc[OpenID Connect]

    %% Protocol descriptions - removed intermediate "Consider" nodes
    %% Keep description annotations for protocols
    saml:::protocol
    oidc:::protocol


    %% SAML Flows
    saml --> saml_initiated{Initiation Type?}
    saml_initiated -->|"SP-Initiated
(More secure, user starts at SP)"| saml_sp[SP-Initiated Flow] saml_initiated -->|"IdP-Initiated
(Simpler, user starts at IdP)"| saml_idp[IdP-Initiated Flow] %% OIDC Flows oidc --> oidc_flow{Client Type & Requirements?} oidc_flow -->|"Web App
(User)
(Confidential Client)"| oidc_auth_code[Authorization Code Flow] oidc_flow -->|"Mobile/SPA/Desktop
(User)
(Public Client)"| oidc_pkce[Authorization Code Flow with PKCE] oidc_flow -->|"Machine-to-Machine
(User)
(Confidential Client)"| oauth_password[Resource Owner Password Flow] oidc_flow -->|"Machine-to-Machine
(No User)
(Confidential Client)"| oauth_client_cred[Client Credentials Flow] %% Decision factor annotations - using implementationNode instead of 'end' oauth_client_cred -->|"Secure: Yes
Token Types: Access only
User Present: No"| implementationNode oauth_password -->|"Secure: No
Token Types: Access, Refresh
User Present: No"
For automated testing only| implementationNode oidc_auth_code -->|"Secure: Yes
Token Types: ID, Access, Refresh
User Present: Yes"| implementationNode oidc_pkce -->|"Secure: Yes
Token Types: ID, Access, Refresh
User Present: Yes"| implementationNode saml_sp -->|"Secure: Yes
Token Types: SAML Assertion
User Present: Yes
Initiation: Service Provider"| implementationNode saml_idp -->|"Secure: Moderate
Token Types: SAML Assertion
User Present: Yes
Initiation: Identity Provider"| implementationNode

For the vast majority of partners, we expect to use OAuth/OIDC and the Authorization Code Flow with PKCE and Client Credentials Flow.

We will not detail OAuth/OIDC here, there are plenty of resources available for that. Instead we will detail the pertinent points of our implementation.

Our tokens are transparent JSON Web Tokens (JWT) that are self-contained:

  • They contain all of the necessary information within the tokens in claims.
  • The information within them can be trusted as they can be cryptographically verified to ensure they have been issued by us and have not been tampered with.
  • The tokens are signed, but not encrypted. They can be decoded and the claims viewed.

Our authN & authZ is therefore stateless and does not require further lookups to other services to make authentication decisions.

Depending on the flow used and requests made, we will potentially issue 3 types of token:

  • ID - can be used by applications as proof of authN
  • Access - passed to our APIs when making requests
  • Refresh - used to obtain new ID and/or Access tokens when they have expired

The ID & Access tokens we issue currently have a 1 hour validity but we may change this at any time.

Our authentication solution will issue one of two kinds of access token depending on the flow used to obtain it. We class these as User Restricted & Application Restricted. The key difference between these is that one is context of the user using the application that obtained the token and the other is in context of the application itself.

  • User Restricted - the requester ends up with an access token that has user context, the bearer of the access token is acting on behalf of the user.
  • Application Restricted - the requester ends up with an access token that does not have user context, the bearer of the access token is acting on behalf of itself.

The payload of an example User Restricted access token:

{
"aud": "XXXXXXXXXXXXX",
"iss": "https://XXXXXXXXXXXXX/v2.0/",
"exp": 1741106843,
"nbf": 1741103243,
"sub": "XXXXXXXXXXXXX",
"email": "luke.smith@emishealth.com",
"givenName": "Luke",
"familyName": "Smith",
"userERN": "ern:emis:user:user:XXXXXXXXXXXXX",
"title": "Mr",
"authorizations": [
"agmt-agmt.read",
"clinical-cr.read",
"clinical-cr.write",
"doc-app.addcode",
"doc-app.create",
"term-prep.read"
],
"orgERN": "ern:emis:org:org:XXXXXXXXXXXXX",
"orgName": "BURNHAM SURGERY",
"roleERNs": ["ern:emis:user:role:XXXXXXXXXXXXX"],
"roleNames": ["Test 50002 role"],
"ods": "XXXXXXXXXXXXX",
"cdb": "50002",
"emisWebUserInRoleId": 28299,
"nonce": "019561d7-006b-71d1-b704-3a79d618a17c",
"scp": "emisweb cdb ods emis-x",
"azp": "XXXXXXXXXXXXX",
"ver": "1.0",
"iat": 1741103243
}

Application Restricted access token payload:

{
"iss": "https://XXXXXXXXXXXXX/v2.0/",
"exp": 1675855606,
"nbf": 1675852006,
"aud": "XXXXXXXXXXXXX",
"appERN": "ern:emis:user:app:XXXXXXXXXXXXX",
"appName": "Partner XXXXXXXXXXXXX",
"orgERN": "ern:emis:org:org:XXXXXXXXXXXXX",
"orgName": "BURNHAM SURGERY",
"authorizations": ["clinical-cr.read", "doc-app.create", "term-prep.read"],
"azpacr": "1",
"sub": "XXXXXXXXXXXXX",
"oid": "XXXXXXXXXXXXX",
"tid": "XXXXXXXXXXXXX",
"ver": "2.0",
"azp": "XXXXXXXXXXXXX",
"iat": 1675852006
}

Note the key differences, the User Restricted token includes details about the user (userERN, roleERN, name) with the Application Restricted token only including details about the application (appERN, appName). Both tokens contain the authorizations claim, this array contains the permissions that our APIs look out for in order to make authorization decisions.

How you obtain tokens depends on what your solution is and how it has been configured in our authentication solution.

We highly recommend that you use the Microsoft Authentication Library (MSAL) as it performs many necessary steps for you by providing it with the necessary config.

For a quick example of how you can obtain a token in the Client Credentials Flow which can only be used for an Application Restricted access token:

Terminal window
curl -X POST \
'https://XXXXXXX.com/XXXXXXX/XXXXXXX/oauth2/v2.0/token' \
--header 'Accept: */*' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=XXXXXXX' \
--data-urlencode 'scope=XXXXXXX' \
--data-urlencode 'client_secret=XXXXXXX'

You will get a response similar to:

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"not_before": 1744706741,
"expires_in": 86400,
"expires_on": 1744793141,
"resource": "XXXXXXX"
}

Where a token is required for a specific organisation or site context, the token endpoint can be called with organisation query parameters.

This allows an access token to be issued in the context of a specific organisation, provided the client application has been configured and authorised to access that organisation.

https://identity.stg.emis-x.uk/{tenant}/{policy}/oauth2/v2.0/token?organisationIdentiferType=cdb&organisationIdentifer=60002

Note: For site-specific token requests, use the B2C_1A_ClientCredentialV2 policy.

Terminal window
curl -X POST \
'https://identity.stg.emis-x.uk/{tenant}/B2C_1A_ClientCredentialV2/oauth2/v2.0/token?organisationIdentifierType=cdb&organisationIdentifier=60002' \
--header 'Accept: */*' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id={client_id}' \
--data-urlencode 'scope={scope}' \
--data-urlencode 'client_secret={client_secret}'
  • organisationIdentifierType
    Specifies the type of organisation identifier being provided (for example, cdb).

  • organisationIdentifier
    The identifier value for the organisation (for example, 60002).

The returned access token will include claims representing:

  • the client application
  • the organisation context
  • the authorised permissions for that organisation

APIs use these claims when making authorisation decisions.

  • B2C_1A_ClientCredentials
    Existing policy.

  • B2C_1A_ClientCredentialV2
    Recommended for site-specific token requests.

  • B2C_1A_ClientCredentialV3
    Planned future version, which will correct the organisationIdentifer typo to organisationIdentifier.

To explore the token endpoint, supported metadata, and OpenID Connect configuration, see:

OpenID configuration endpoint.

We look forward to collaborating with you to revolutionize healthcare technology. If you have any questions or need assistance, please don’t hesitate to reach out.