SMART on FHIR compatibility

698 views
Skip to first unread message

Erik Glimpse - CrystalPM

unread,
Nov 2, 2021, 2:01:44 PM11/2/21
to HAPI FHIR
Is the HAPI FHIR server compatible with SMART on FHIR client connections with the current code?

If not, are there any plugins/extensions available for this functionality?

David Conlan

unread,
Nov 25, 2021, 2:20:54 AM11/25/21
to HAPI FHIR
Hi Erik,

 In a number of ways, it all depends what you are trying to do with smart. With smart, you launch an app from the EMR, which is then authorised and can access the fhir data. So beside the smart app, there are two other parts that are not part of the hapi base, an EMR and and authorisation server.
These two are often seen as bundled together, as some behind the scenes has to happen for the context in the EMR to be known by the authorisation server. But having said that, a smart app can also be launched stand alone with no associated context. In this case we lose the EMR and just
have an authorisation server and a fhir server.

I have implemented something resembling this with hapi, though it was for a very early spec of smart.

On the hapi side of things you need to :
  1. give it an Authorization Interceptor which checks the bearer token and applies the scopes. I used a jwt based authorisation token, so all the information needed was in the token.
  2. give a Search Narrowing Interceptor which uses the scopes to narrow the data to only what is available based on the scopes.
  3. Change the capability statement of the fhir server (the /metadata) to include the information about where to find the authorisation and token end points (this has since been replaced by a .well-known)
  4. Implement the .well-known endpoint either using an interceptor or some other mechanism
You may be able to get away with only doing 1,2 & 4. In theory 1 and 2 seem straight forward, but what a scope of user/Patient.* actually means is not strictly defined. If the user is a practitioner, then which patients are available to be viewed is not defined by fhir. The closest you will
get is using the practitioner compartment, but my reading of it is that practitioners and patients only direct link is the GP field.

The .well-known endpoint is pretty simple. something like

@Interceptor
class SmartWellKnownInterceptor {

  private final Logger LOGGER = LoggerFactory.getLogger(SmartWellKnownInterceptor.class);

  private final String fhirContextPath;
  private final String authorizationEndpointUri;
  private final String tokenEndpointUri;

  SmartWellKnownInterceptor(String fhirContextPath, String authorizationEndpointUri, String tokenEndpointUri) {
    this.fhirContextPath = fhirContextPath;
    this.authorizationEndpointUri = authorizationEndpointUri;
    this.tokenEndpointUri = tokenEndpointUri;
  }

  @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_PROCESSED)
  public boolean processIncomingRequest(HttpServletRequest request, HttpServletResponse response) {
    String context = request.getContextPath();
    String uri = request.getRequestURI();
    LOGGER.trace("Checking uri '{}'", uri);
    if (uri.endsWith(context + fhirContextPath + "/.well-known/smart-configuration")) {
      StringBuilder sb = new StringBuilder("{");
      sb.append("\"authorization_endpoint\": \"")
          .append(authorizationEndpointUri).append("\", ");
      sb.append("\"token_endpoint\": \"")
          .append(tokenEndpointUri).append("\", ");
      sb.append("\"token_endpoint_auth_methods\": [\"client_secret_post\", \"client_secret_basic\"], ");
      sb.append("\"capabilities\": [\"launch-ehr\", \"client-confidential-symmetric\", \"sso-openid-connect\", \"context-ehr-patient\", \"context-ehr-encounter\", \"permission-patient\"]");
      sb.append("}");
      response.setContentType("application/json");
      response.setStatus(200);
      try {
        response.getWriter().write(sb.toString());
        response.getWriter().flush();
      } catch (IOException e) {
        LOGGER.error("Failed to write to response", e);
      }
      LOGGER.trace("Well known - returning '{}'", sb);
      return false;
    }
    return true;
  }
}

Then you just need the EMR and authorisation server parts of the equation.
I wrote an authorisation server that included some special end points for adding launch context, and then I built a smart on fhir app to act as an EMR which knew how to set the context in the authorisation server before launching a smart app.

You could use something like keycloak to act as the authorisation server, but again this will need custom configuration to deal with the special scopes and authorisation parameters which are part of the spec.
Off the shelf oauth2 servers won't magically know that if  "launch=abcd" is included in the authorisation request, that extra context needs to be added to the token response. Or that a fhirUser scope means it needs to add the fhir resource
associated with the user in the context information. There is some info about configuring keycloak here https://github.com/Alvearie/keycloak-extensions-for-fhir but I have no experience with using it.

Hopefully I provided something late but useful.

David Conlan

Reply all
Reply to author
Forward
0 new messages