Provider Directories – part 2

In the previous post, we set up the environment for our provider registry and imported some test data (Practitioner only at this stage) from an existing reference server. Now let’s think about how we can go about exposing that information through an API. We’ll work with 2 use cases – which can then act as a pattern for future APIs.

First up is a search against the Practitioner resource. Given that we’re using the feature-complete HAPI server as the back end, we can support any query. In a production system we may want to limit the searches we want to support – or define new searches. (Naturally we’d include those in the CapabilityStatement resource we expose, and also expose SearchParameter resources for custom one). The ImplementationGuide then wraps all these into a coherent whole.

Here are some search examples:

Just to make it a little more interesting, let’s decide to return the VerificationResult resource (if any exist) in the response as well. It’s completely legitimate to be adding ‘extra’ resources in a query response – although it could be argued that it’s better for the client to be specifying this.

We need to be thinking about security related issues from the outset. This adds a few requirements:

  • The access will need to be SSL
  • We need to identify the user – using SMART of course
  • We want to know who is making queries, and the simplest way for that is to create an AuditEvent resource for each query that we can subsequently search on
  • Updates should have Provenance resources – and we’ll need to think about the access control around them

There will be others of course, but they are a start.

Let’s think a bit more about the SMART requirement. We’ve discussed SMART a few times (and have more posts planned). Fundamentally, SMART (and OAuth2 that it’s based on) is about using a separate component (the Authorization Server) to authenticate the caller, and to issue Access Tokens that accompany any call to the API. The API can then use the Access Token to decide whether to return the requested data.

The actual manner in which this is done is up to the implementation, and there are a number of options we can choose from. For now, we’ll assume that the information we need to decide whether to return the data (the user identity and the scope) is included in the Access Token and just examine that. So we can concentrate on functionality, we’ll just include this in the token in clear text for now – later we’ll secure that, likely using signed JWT tokens. We’ll also defer a discussion of how the authentication occurs till later as well, and just assume that we have an access token provided.

The second Use case is to return a Practitioner based on their id like this:

  • GET [host]/Practitioner/{id}

In this case, we still want to authenticate the user and create an AuditEvent record, but we won’t return the VerificationResult  resource – as the response to a request like this is a single resource, not a bundle. We can always add a query for the VerificationResult  if we want to – or the caller can use the query use case above to get it in a single call

Here’s the format for a VerificationResult query for a given practitioner:

  • GET [host]/VerificationResult?target=Practitioner/100

So let’s take a look at how the flows work out. We’ll start with the search:

So it starts (as usual) with an HTTP input node. This will actually be HTTPS of course – there’s Node-RED configuration that sets this up that we won’t go into here.

Next up is a sub-flow called ‘Check user’. We’ll look at this in more detail in a minute, but basically it examines the incoming call, checks the Access Token for a user and a scope and returns a pass or a fail. If the check fails, then the flow terminates with an ‘Unauthorized’ response.

After the user check there’s a javascript node that converts the incoming query to point to the internal server rather than the Node-RED proxy (it just replaces the [host] part of the query and adds the _revinclude for the VerificationResult).

Next up is an interesting sub-flow – ‘Make transaction query’. We need this because of the requirement to create an AuditEvent resource for every query. Our strategy is that instead of just sending separate GET Practitioner and POST AuditEvent requests to the local server, we’ll use a transaction instead. The transaction will have 2 entries:

  • A GET request that is the same as the one we created in the previous node
  • A POST request that has an AuditEvent that we create in the subflow

This approach means that we only get an AuditEvent for a successful query – and only need a single call. It does mean that we don’t get to record the outcome of the query – but I’m not sure that’s really needed here – and if it was, we could just add a logging call node after query was complete.

After this we POST the transaction to the internal server, then return the result to the user.

Lets’s take a closer look at the 2 subflows. First up the check user subflow.

Screen Shot 2019-01-15 at 12.42.53 PM.png

The first function node (Examine Access token) pulls out the userid (represented as a Practitioner resource) and the scopes from the access token and saves them in the message object. As described above, we’re likely going to include these in the token itself (as a signed & encrypted JWT token) but for now we’ll have them as clear text in the format: Bearer {userId} {scope1} {scope2…}. And – if we change our mind and decide to call the Authorization server to get this information then we only need to change it in one place.

The next node is a switch that checks that there was a userId in the access token – rejecting if not.

Lastly we examine the scopes from the access token. The following scopes will allow the flow to continue – if none are present then the query is rejected.

  • patient/*.*    (read/write on all patients)
  • patient/Practitioner.*    (read/write on Practitioner)
  • patient/    (read on Practitioner)

We’ve been a wee bit cunning in the scope check – we look for the resourceType (which is included in the query) rather than hard coding the scope checks. That means that the subflow remains useable for other queries.

The other subflow is the Make Transaction Query. As we noted above, this constructs a transaction to both execute the query and to create an AuditEvent resource on the internal server (the javascript node is open so you can see the code – it’s quite straightforward).

So that’s the Practitioner search API. The ‘get Practitioner by Id’ is quite similar:

In fact it’s identical up until the response from the internal server – we examine the bundle that is returned and extract the Practitioner resource to return to the caller (remember that this type of query expects a single resource to be returned – not a bundle)

So that’s enough for now – there are a couple more things to think about though.

  • It would be good to dig into the details of the SMART implementation a bit more. This ties in nicely with some of the other posts on SMART – indeed, we should be able to use the SMART client app described in this post to make queries against the directory once all the bits are in place.
  • We need to think about populating the registry. While a lot of the ‘official’ sources like the Medical or Nursing Council will populate the registry directly (ie not through an external API) it would be nice to be able to provide some sort of update facility like an address change for these resources. Also, why should we just confine the registry to Doctors and Nurses? There a lot of other healthcare providers out there, it would be great if they could use the directory as well, but there are some big issues to consider…

About David Hay
I'm an independent contractor working with companies like Orion Health, HL7 Fellow, Chair emeritus of HL7 New Zealand and a co-chair of the FHIR Management Group. I have a keen interest in health IT, especially health interoperability with HL7 and the new FHIR standard.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: