Learn more about a new sample app that uses FHIR APIs.
Heart attack warning signs, can you recognize the symptoms of a heart attack? Would you know what to do if you see someone experiencing a sense of tightness, pressure, discomfort or pain in any of these areas: chest, shoulder, jaw, arm, neck or back? You might also see sweating, shortness of breath, nausea, fatigue or dizziness.
Heart disease and heart attacks are one of the leading causes of chronic disease and premature death globally. The Framingham Risk Score is a scoring system used to determine an individual's chances of developing heart/cardiovascular disease. The Framingham Cardiovascular Risk Calculator, gives an estimate of the likelihood, as a percentage, of an individual having a cardiovascular event (e.g. a heart attack) in the subsequent 10 to 30 years.
In the previous two week’s posts How can FHIR APIs help to prevent heart attacks? And Calculate your risk of heart disease using an App we described the overall operation of the new demo app which uses HL7®FHIR®.
As with any healthcare data that reveals a patient’s personal health information, security is of prime importance. So for the final post in this series of three we are focusing on how to ensure security within the app.
Security using OAuth
What are the security aspects of this interaction? Let’s start by looking at OAuth2. A couple of comments before we get started:
- The calls must be using TLS – Transport Level Security – i.e. HTTPS and not HTTP.
- This is not quite the same as SMART. SMART is a ‘profile’ on OAuth2 which we are in the process of implementing. This is ‘vanilla’ OAuth2, SMART will come later
OAuth2 is a commonly used framework that manages user access to data. It can be used for a number of different scenarios – one of which is to control access by an application (and a user of that application) to data provided by an EHR over an API (Application Program Interface) – exactly what we want to do.
The overall flow of OAUth2 is described in some detail here (with links to the formal specification), but the following is a high level description from the perspective of the user of an external application wishing to access data in an EHR.
There are a number of key actors in the process:
- An application that can access data from the EHR (read or write)
- A user of that application who has an account in the EHR (i.e. is allowed to access data to some level)
- The server that has the data to be accessed (the Resource Server, in this case the EHR)
- The server that can identify that the person is who they say they are (the Authorization (Auth) server) – e.g. by supplying a login and password. This may be the same as the her (and is in this case), but doesn’t have to be.
(These aren’t quite the same as in the OAuth2 spec – but hopefully a bit easier to understand in the context of this discussion)
The overall idea is that the Resource server doesn’t have to actually authorize the user directly (which is really hard to do properly) – it can delegate that task to someone that it trusts, rather than having to do that itself (that’s how you can ‘login with google’ to a completely separate web application).
The OAuth2 spec itself doesn’t dictate how this trust occurs, though does describe a number of patterns. One of these is the use of a ‘token’ – issued by the Auth Server, and used by the Resource Server to decide what services to offer. The Resource server trusts that the Auth Server has correctly identifier the user and app.
The overall flow for a web based application using OAuth2 to access data via an API is as follows.
- The application is pre-registered with the Auth server, and given a code that identifies it.
- The location of the required end points on the Auth and Resource server are known (more on this later) to the application
- The User of the application is known to the Auth server, and has some way of identifying themselves (in this case a user name and password – the same that you’d use to log into Portal)
- The Auth server can issue a token that the Resource server can use to identify the user and application – ie there is a trust mechanism between the two
- The user starts the app, and initiates the ‘log in’ process.
- The app contacts the Auth server, passing across its app code (so the Auth server knows who is contacting it). The Auth server then displays a login page (assuming that username/password are being used to identity the user)
- The user enters their username and password which is validated by the Auth Server. Assuming it is correct, the Auth server then ‘redirects’ back to the app, giving it an ‘authorization code’. If incorrect, then the Auth server sends back a ‘fail’.
- The app then calls the Auth server again, passing across the authorization code, and receiving in return an Access Token. Whenever it subsequently makes a call to the Resource Server, it will include the Access Token in the call, so that the Resource Server can decide whether to honor the request (and know who is making it for audit purposes).
You may wonder why there are 2 calls required to the Auth server – one to get the Authorization code, and a second one to get the Access Token. The reason is that the Access Token usually only lasts for a short time (commonly 1 hour) before it expires. However, in step 4 above the Auth server also returns another token – the Refresh Token - that the app can use to get another Access token when it has expired (the user won’t need to log in again).
So, in effect, what is happening is that the user can continue to access the EHR API for as long as the Refresh token is valid (which is controlled by the Auth server), but every hour the app has to check that the user is still allowed to access the EHR API. This allows the Auth server to revoke access if needed, and the maximum time that the user can continue to invoke the EHR post invalidation is one hour.
So with the theory behind us, let’s describe how we’ve used OAuth to login to the platform, and also to identify the user.
The server exposes a start page (actually named login.html in this app) which has some descriptive text, and a ‘login’ button that calls an ‘/auth’ endpoint on the server. This end-point constructs a url that will reference the authorization endpoint in the platform and issues a ‘redirect’ to the browser, indicating that it should load an html page at that location. The Url contains some specific attributes needed for the authorization – namely:
- The application id (client id)
- The callback url (where the browser should be re-directed after successful authentication
- The response type (fixed to ‘code’)
Here a screenshot of the server code:
Note that the actual values for the parameters are stored in a config file – for obvious reasons they aren’t shown here!
The redirect causes the Portal login page to be loaded.
At this point you enter your username and password, and then press the login button.
Your login details are securely sent back to the platform (over TLS) to the Authorization server and if they are correct, then the platform will issue another redirect – this time to the callback url that was configured when the app was registered with the platform, and sending a code that it has created.
This url is exposed by the app server, and when invoked it:
- Checks that the authorization server sent it a code
- Calls the /token endpoint on Authorization server passing across the code and a previously agreed secret and receiving back an access token.
- Saves the token in the session (so it doesn’t need to be sent back to the client)
- Re-directs to the main application page.
Here’s the code:
The purpose of all of this is that the Access Token (just called the token) is used by the platform to validate any subsequent calls. It is included in every call (in the http Authorization header) and effectively identifies the previously authenticated user – think of it like an electronic passport – so the platform knows who you are, and what you are allowed to access. Here is an example of a call to the platform including the token.
Note that we’ve used a different HTTP library to make the call – synRequest – which waits until the call has completed before continuing. This is OK in a demo, but you’d never do this in a real app.
The token is only valid for a short time – commonly an hour – after which it must be re-issued. Either the user can log in again (which is tedious) or it is possible to get a new one directly using the ‘/refresh’ endpoint on the authorization server. We haven’t implemented that here, though it would be straightforward to do so.
It should be noted that although we’ve implemented all the steps manually here, in real life you’d generally use a library to do so. Implementing security can be tricky and the consequences of failure can be devastating, so using well tested code is always a good idea.
To learn more about FHIR for Clinicians download the white paper now.