If you’ve read the previous posts talking about OAuth2, then (hopefully) it’s clear how a user can authorize an application to retrieve their data from some server without needing to expose their login credentials. The Authorization Server is the only component that needs to have the persons username and password (or whatever authorization method is being used – there are plenty of other options).
But – beyond this login stuff – how does the application know who the user is? When they query the FHIR server to get the data, exactly whom are they getting it for? (Incidentally, this process of identifying the patient/user is often called passing the ‘context’ between applications and there are lots of ways of doing this, such HL7s CCOW standard that is designed for desktop usage and so won’t help us here).
To answer this we need more information about the users Identity – or put another way, one or more properties of the users identity that both the application and FHIR server recognize so that they know they are dealing with the same person. In FHIR, we think of this as an Identifier – and there are many entities/resources that can have them, including the patient.
Another way to look at this is that the application needs to be able to make a query like this:
to get the conditions for the patient whose identifier is PRP1660. (Or any other FHIR mechanism for that matter).
So let’s think about Identifiers for a second. We’ve actually touched on this a number of times in this blog – often in the context of separating the concepts of FHIR resource ID from Identifier (confusingly, they are often both called ‘ID’ ) – but they are quite different.
An identifier is usually a string that uniquely identifies an entity within the context of a ‘system’ of some sort and is not confined to healthcare. Examples of identifiers include a Drivers License, a Social Security Number or – in New Zealand – a National Health Index (NHI number). So this implies that there is some ‘registry’ that holds and assigns these identifiers that everyone participating in the exchanges recognizes. (Of course, there may be more than one identifier for any one entity).
(as an aside, because it’s uncommon to have a single patient identifier that can be used for healthcare delivery, there’s a component – the Enterprise Master Patient Index (EMPI) that is often used in real-world implementations to provide this consistent identifier for a patient – and link to redundant or duplicate identifiers).
So, to be of use to us, an Identifier has a minimum of 2 parts: The actual identifier string, and the system within which the identifier string is unique. (and yes, I know the example above hasn’t got the system!)
Having decided that, the next question is where do we get the identifier from? In the exchanges we’ve discussed thus far the closest we’ve got to this are the logon credentials with the Authorization Server and that’s not the same (the Authorization Server could be Google, Facebook or Amazon for example). What we need is an ‘Identity Server’ , and some way to get the patients identity from it – and that’s where OpenID Connect comes in.
OpenID Connect is a protocol that sits ‘on top’ of OAuth2 to provide identity services – which means that it uses the same components and flows as OAuth2 does (and what we’ve looked at in the last couple of posts), but adds a few extra pieces that relate to identity – ie more attributes about the person than just the login that they use to the Authorization server, such as name, email address, birthday – and identifiers!
Now, there’s actually a wrinkle here. OpenID Connect defines a number of ‘standard’ attributes about a user (which it calls ‘claims’) that are listed here, but identifier is not one of them! However, it does allow you to define extra ones – and this is what we’re going to use for the patients identifier. But because this is not in the standard, we need a separate agreement between all those who are going to use this approach about what code we are going to use to signify the identifier. This is sometimes called a ‘trading party agreement’ or a part of an Implementation guide. Just ignore the details of this for now – we’ll come back to it later.
Another way of looking at OpenID Connect is that it takes the existing OAuth components, uses the existing flows and adds specific extra functionality to them. (If you read the spec you’ll see that it does give different names to some of the OAuth2 components – eg ‘Client’ becomes ‘Relying Party’ and ‘Authorization Server becomes OpenID Connect Provider. We’ll continue to use the names we’re used to here, but don’t get confused if you see them in the spec.)
The actual flow is very similar to the standard OAuth2 flow that we’re already discussed (unsurprisingly). The following description gives an overview of this flow – calling out the differences (or additions) to OAuth2.
- The browser interacts with the app, and is redirected to the Authorization Server. The parameters in the call include a scope parameter which lists the identity claims that the app wants plus the value of ‘openid’ to indicate to the Authorization Server that this is an OpenID Connect request.
- The redirected browser calls the Authorization Server and a login page is rendered. The page will usually include a list of the claims requested by the app, so the user can authorize it. (This facility is part of standard OAuth2).
- The user enters their login details, and authorizes the release of information.
- The details are sent to the Authorization Server
- which validates the user…
- and issues a redirect back to the application containing an authorization code.
- The application receives the redirected request with the authorization code. It then makes a request to the Token endpoint of the Authorization Server with the authorization code, the Authorization Server validates the code and returns all 3 tokens:
- Access Token
- Refresh Token
- ID token
- … and the flow continues as described before
(Just a reminder that a whole lot of detail is omitted from the description above in the interest of clarity – refer to the spec if you want the details. They’re helpful to understand the properties of whatever library you’re using to do this stuff).
It’s also worth mentioning that it’s quite possible to have a completely separate ‘Claims Provider’ – so that in step 7 above only the Access/Refresh Tokens are returned, and there’s a separate call to the Claims Provider. The spec refers to a UserInfo endpoint that would be exposed by the Claims Provider in this case.
We’re already familiar with the Access and Refresh Tokens, but the ID Token is worth looking at in a bit more detail, as it’s specific to OpenID Connect. It’s in the form of a JSON Web Token which is described as “a compact URL-safe means of representing claims to be transferred between two parties” (Remember that in this context a ‘claim’ is another word for an attribute of a user). We won’t go into the details of this format as your library will shield you from the details (go look at the spec if you’re interested), but the key part of interest to us is that it contains the claims that we’re interested in – including the identifier – in the form of a JSON string. This makes it easy to extract and use.
Here’s an example (I filched it from the spec) of what a successful response from the Authorization Server would look like in step 7 (but remember that you’re library will shield you from this).
HTTP/1.1 200 OK
Note how there are the 3 tokens in the response as properties of the JSON object. The value of the ‘id_token’ property is the ID token in JWT format. It actually has 3 parts separated by a ‘.’, each of which is base64 encoded. The middle part contains the claims.
So in summary, the OpenID Connect protocol adds functionality to OAuth2 that allows a server (in this case the Authorization Server) to store and deliver identity data that the applications can use when manipulating patient information. Exactly how that server got that identity in the first place – and how it links it to the login credentials – is up to each implementation.
From the clients perspective it’s really simple:
- Take OAuth2
- Add the claims you want + a string with the value ‘openid’ when calling the Authorization Server (step 1)
- Get back the required data when you get the Access token (assuming everything was valid of course).
Everything else works exactly as OAuth2 does.
Easy! (well, sort of <s>). And again – at risk of being boring – use a Library!
So that’s about all we’re going to say about OpenID Connect for the moment. But there are a couple of details in this overall solution that we haven’t really thought about yet.
- When the user authorizes access to their data for an application, how can they do so in a granular fashion? eg that they are happy for the app to read their lab data, but not any other data, and not able to write anything?
- Given that OpenID Connect doesn’t have a standard ‘claim’ for identifier, how do we indicate that we want it (or any other claims for that matter)?
And – how could all these bits fit together to create a secure ‘ecosystem’ of data sources and services? And, even better, an ‘appstore’ of applications that a user can download and be confident that they will play properly in that ecosystem?
We’ll think about that next…