Azure Active Directory Proxy for SMART on FHIR

Fast Healthcare Interoperability Resources (FHIR) is a draft standard describing data formats and elements (known as “resources”) and an application programming interface (API) for exchanging electronic health records. There is currently a lot of momentum behind this standard; most modern Electronic Health Records (EHR) systems and healthcare applications support some parts of the FHIR standard.

SMART on FHIR is a tech stack for healthcare applications that build on the FHIR standard and provide a framework for integrating EHR systems, portals, and other applications. An important part of the SMART on FHIR standard is the formalized way that healthcare applications should obtain authorization to access a FHIR server holding health records. You can read all about that in the SMART on FHIR authorization guide.

The authorization is based on OAuth 2.0 and OpenID Connect. Since Azure Active Directory (AAD) supports OAuth 2.0, it makes sense to explore if AAD can play the role of identity store and authorization server for SMART on FHIR applications. In this blog post, I will outline a few of the differences in parameter naming, etc. when comparing AAD and the SMART on FHIR flow and I will show how to use a web app proxy to bridge the gap between the SMART on FHIR conventions and AAD.

Before diving into the SMART on FHIR authorization flow, I would recommend browsing through the Azure Active Directory Developer docs. There is a lot of detail in there, but for the purposes of this discussion, you should be aware that there are two different OAuth endpoints that one can use. There is the original endpoint and the v2.0 endpoint. There are some key differences/limitations:

  1. Applications using the original endpoint will need to statically select which scopes from backend APIs that the application will need. The v2.0 endpoint allow incremental modifications of the scopes that an application may need.
  2. When using the original endpoint, an application will need to specify a backend API that it needs to access using a resource parameter (more on that later). The version v2.0 endpoint does not support the resource parameter but expects fully qualified (including backend resource or audience) scopes.
  3. When using v2.0 endpoint, the application registrations must be done using the Microsoft App Registration portal (https://apps.dev.microsoft.com). Applications using the original endpoint can be defined in the Azure Portal.

To make this a bit more concrete, an authorization flow using the original endpoint would be initiated with something like:

[plain]
GET https://login.microsoftonline.com/{TENANT}/oauth2/authorize?resource=https://resource-uri\&client\_id=XXXX&....
[/plain]

The resource parameter refers to an app registration representing a backend API (e.g., a FHIR server), and when obtaining a token from the token endpoint after authorization, it would have whatever scopes had been statically selected for the application in question. It is possible to supply a scope parameter, but it is ignored.

The flow using the v2.0 endpoint would start with something like:

[plain]
GET https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/authorize?client\_id=XXXX\&scope=https://resource-uri/access\_as\_user&....
[/plain]

Here we are requesting a specific scope (access_as_user) on the backend API (audience or resource) identified by https://resource-uri. So there is no resource parameter anymore but we can incrementally select the scopes that an application needs.

The SMART on FHIR flow expects a different naming convention for these parameters. The authentication flow might be initiated with:

[plain]
GET https://auth-server-url/authorize?aud=https://fhir-server&client_id=XXXX&scope=patient/Observation.read&....
[/plain]

Comparing to the original AAD flow, the aud (audience) parameter is equivalent to resource and the expectation is that scopes can be specified to incrementally add scopes as the application needs them.

To make this compatible with AAD, we can use a proxy between the SMART on FHIR application and AAD. I have made a simple example of such a proxy, which you can find in this GitHub repository: https://github.com/hansenms/SmartOnFHIR-AAD-Proxy. It is a .NET Core Web API that can de deployed into an Azure Web App. The flow would look something like this:

The user (or user application) will interact with the web app proxy to authenticate and obtain a token, which is then used to authorize access to the FHIR server. If we suppose that the web app is deployed at the address https://smart-aad-proxy, then an authentication flow can be initiated with:

[plain]
GET https://smart-aad-proxy/{TENANT}/oauth2/authorize?aud=https://fhir-server&client_id=XXXX&scope=patient/Observation.read&....
[/plain]

Or

[plain]
GET https://smart-aad-proxy/{TENANT}/oauth2/v2.0/authorize?aud=https://fhir-server&client_id=XXXX&scope=patient/Observation.read&....
[/plain]

In both cases the request will be redirected to the AAD authorization endpoint and the parameters translated into a form that AAD will accept. In the case of the original endpoint, the scopes will be ignored (assumed statically defined) and in the case of the v2.0 endpoint, the aud and scope parameters will be combined into fully qualified scopes that AAD expects. In addition to this, any slashes (/) will be replaced with hyphens (-) since the slashes in the fully qualified scopes are confusing for AAD. So to make this work, you must name your scopes accordingly when registering the application. The request to the v2.0 endpoint in this case would get translated to something like:

[plain]
GET https://login.microsoftonline.com/{TENANT}/oauth2/v2.0/authorize?client\_id=XXXX\&scope=https://fhir-server/patient-Observation.read&....
[/plain]

The request to the authorization endpoint is redirected to the AAD endpoint. The web app proxy also supports the token endpoint, e.g. a token could be obtained from:

[plain]
POST https://smart-aad-proxy/{TENANT}/oauth2/v2.0/token
[/plain]

Since this is a POST request, it cannot be redirected, it has to be proxied. It is not a requirement to use the token endpoint of the proxy, that request could go directly to the AAD token endpoint. FHIR servers will announce their authorization and token endpoint as part of the conformance (metadata) statement.

And that's it. You now have a set of SMART on FHIR compatible OAuth endpoints backed by Azure Active Directory. Let me know if you have questions/comments/suggestions.