JWT claims for access control on FHIR servers

We’re experimenting using json web tokens as a mechanism for access control on FHIR servers. It seems to work to provide a basic RBAC model …

Our latest (experimental!) version is here:

Interesting. Did you consider SMART tokens?

It looks as though we’re going to have use spine roles for our RBAC. Haven’t defined anything special on the tokens - it’s effectively a session ID at the moment.

We’re using oauth2 as we’ve micro service setup and want to move to finer security and access controls on the API.

We looked at existing proposals around authorisation.

SMART (SMART on FHIR Authorization: Scopes and Launch Context)
still has a concept of read/write. I think this is partly legacy because they were ahead of the curve and had to adopt FHIR later. The tokens are also quite specific to the requirements of SMART.

Our first draft included read/write but we found that it requires additional interpretation as the claim and the server use different languages. As an implementer, consider how you authorise the following requests:
GET /fhir/Foo?bar=123 is a ‘search’ interaction - i.e. a ‘read’
POST /fhir/Foo?bar=123 is also a ‘search’ interaction - i.e. a ‘read’
POST /fhir/Foo is a ‘create’ interaction - i.e. a ‘write’
GET /fhir/Foo$do is an operation interaction - is this ‘read’, ‘write’, ‘read’ AND ‘write’?
POST /fhir/Foo$do is an operation interaction - etc.

Interestingly, John Moehrke chose to use the restful interaction valueSet:


For me, this is close, but not quite there, as it doesn’t allow us to differentiate:
POST /fhir/Foo$doThis
POST /fhir/Foo$doThat

John’s use case is different to the one I’m seeing at the moment but I’m seeing that as a future state. I’ve been looking at enterprise security and centrally controlling access to FHIR servers rather than application security.

So system accounts from TIE/Business Process servers accessing data - no actual user.
I’m not going to request a token for each resource I access, so will want Patient/* permission rather than Patient/123,etc mainly for performance. I may want to block a number of requests to sensitive resources/patients (403 forbidden with no option of getting an access token?)

I’m not criticising the jwt token just considering how different is this use case? The use of system accounts is limiting the amount of access controls required but we will probably move away from them (how long). Might need a combination - an enterprise/NHS for simple access token and then an application/detailed access token (user@trustA accessing patient data @trustB e.g. XDS FHIR server)

It’s not a problem using tokens for B2B. This is how I would set up your enterprise model, assuming a toy system with a portal accessing 2 FHIR servers that provide MPI and XDS capability.

The consumer system IS a user, just a system rather than a person. The auth system issues a token as normal:

{
    iss: 'https://auth.mayfield.net',          // This is your locally trusted issuer
    sub: 'myportal@mayfield.net',              // This is the consumer system wanting to access FHIR servers
    nbf: 1463059456166,
    exp: 1463064578213,
    iat: 1463059366160,
    aud: ['https://mpi.mayfield.net', 'https://xds.mayfield.net'], // Some FHIR servers that can be accessed
    jti: '5794b4f6-90bb-41a2-8e11-27ff4adb8880',
    fhir_scp: ['*'],                           // Allow the consumer to access all scopes on the FHIR servers
    fhir_act: [
        'read:Patient,DocumentReference'       // Allow the consumer to read resources
    ]
}

The token is then used by the FHIR servers to authenticate and authorise the requests with minimal business logic. We’ve shown that this can be done generically as a standard algorithm.

There may be an additional business rule prohibiting access to sensitive records. The FHIR servers would need custom code to reject any requests for sensitive records with status 403.

As extra sugar, you could have ‘break glass’ functionality that would allow the server to request a token scoped to a specific sensitive record:

{
    iss: 'https://auth.mayfield.net',          
    sub: 'myportal@mayfield.net',              
    nbf: 1463059456166,
    exp: 1463059457123,
    iat: 1463059366160,
    aud: ['https://mpi.mayfield.net', 'https://xds.mayfield.net'],
    jti: '5794b4f6-90bb-41a2-8e11-27ff4adb8899',
    fhir_scp: ['Patient/123'],                           // Allow the consumer to access only Patient 123 on the FHIR servers
    fhir_act: [
        'read:Patient,DocumentReference'      
    ]
}

Assuming that some other authentication services become available in the future, the granular controls that may then be needed can still be provided in the token.

{
    iss: 'https://auth.nhs.net',          // This is a nationally trusted issuer
    sub: 'john.doe@nhs.net',              // This is the user wanting to access FHIR servers
    nbf: 1463059456166,
    exp: 1463064578213,
    iat: 1463059366160,
    aud: ['https://mpi.mayfield.net', 'https://xds.mayfield.net'], // Some FHIR servers that can be accessed
    jti: '5794b4f6-90bb-41a2-8e11-27ff4adb8880',
    fhir_scp: ['Patient/456'],            // Allow John Doe to Patient 456
    fhir_act: [
        'read:Patient,DocumentReference'  // Allow John Doe to read Patient and DocumentReference resources
    ]
}

And as the token format remains the same, the only change to the FHIR servers would be to trust an additional issuer.

What about user@acutetrust.nhs.uk accessing xds-system@communitytrust.nhs.uk

I’m thinking spine LDAP provides authentication and role in a NHS token but the acutuetrust user will need to get a new communitytrust token (which can be as you’ve described)?

Sounds simple (and we need to keep it simple). The NHS token could hold a generic set of permissions.

This is reasonable - user authenticates using spine LDAP to get a generic NHS token which can be presented to a local auth provider and exchanged for a specific token.

We’re having fun at the moment with tokens when an app needs to access more than one service (e.g. to read from one and write to another). After much head-scratching, we came to the conclusion that the user often needs to end up with a number of tokens …