Stream: javascript
Topic: Using SMART JS Client in express-based CDS Hooks Service
Chris Moesel (May 06 2021 at 20:48):
I fear that this is a silly question and the answer is obvious, but... I have a Node.js Express based application that acts as a CDS Hooks service. I'd like to use the SMART JS Client Library to query the FHIR server using the fhirServer
and fhirAuthorization
details (particularly the access_token
) passed in the CDS Hooks service call. But it seems that the SMART library wants either no auth at all or the full SMART auth workflow. I don't see a way to say "just use this token". Am I missing something? Or am I misguided in what I am trying to do?
Josh Mandel (May 06 2021 at 20:56):
@Vladimir Ignatov is there a mechanism to just provide an access token to a client instance?
Vladimir Ignatov (May 06 2021 at 21:37):
There is no official mechanism for that but it might be possible (I have not tried it). Don't use the SMART API (authorize, ready or init). Just create a client instance directly and pass it's desired state. This is a complete example but may want to delete most of these properties (only serverUrl
is required).
const fhirclient = require("fhirclient");
app.get("/whatever, (req, res) => {
const client = fhirclient(req, res).client({
"clientId": "3ab1d54f-b004-48fd-a1b6-eeae73d8858e",
"scope": "launch openid fhirUser patient/*.read offline_access",
"redirectUri": "https://35u09.codesandbox.io/browser/ehr_launch/index.html",
"serverUrl": "https://launch.smarthealthit.org/v/r3/fhir",
"tokenResponse": {
"need_patient_banner": true,
"smart_style_url": "https://launch.smarthealthit.org/smart-style.json",
"patient": "67eba314-7595-431c-9d62-8d30e0bd76ac",
"encounter": "e13a28cc-17ca-46ca-834e-917e4cd0a411",
"refresh_token": "eyJ0eXAiOi...",
"token_type": "bearer",
"scope": "launch openid fhirUser patient/*.read offline_access",
"client_id": "3ab1d54f-b004-48fd-a1b6-eeae73d8858e",
"expires_in": 3600,
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
},
"key": "46xzy8p3niNqtAwl",
"registrationUri": "",
"authorizeUri": "https://launch.smarthealthit.org/v/r3/auth/authorize",
"tokenUri": "https://launch.smarthealthit.org/v/r3/auth/token",
"expiresAt": 1620338304
});
// use client as you wish...
})```
Josh Mandel (May 06 2021 at 21:43):
I haven't used this in Node -- How are (req, res)
used in the fhirclient(req, res)
creation function?
Vladimir Ignatov (May 06 2021 at 22:37):
They provide some context (https://github.com/smart-on-fhir/client-js/blob/master/src/entry/node.ts). An adapter for the current environment needs to be created first. In that case https://github.com/smart-on-fhir/client-js/blob/master/src/adapters/NodeAdapter.ts.
In browsers we have things like location
, sessionStorage
, btoa
, redirects... In node most of these are available through the current request and response objects.
Finally, adapters are mostly just collections of environment-specific methods implementing the interface at https://github.com/smart-on-fhir/client-js/blob/master/src/types.d.ts#L119-L175.
Chris Moesel (May 07 2021 at 12:28):
Thank you @Josh Mandel and @Vladimir Ignatov. I've now tested this out with my CDS Service and the Logica Sandbox -- and it works! Here's the relevant code snippet, trimmed down to just those properties that a CDS Hooks client provides. In this cas req
is the incoming CDS Hooks call from the CDS Hooks client:
if (req.body.fhirServer) {
const state = {
serverUrl: req.body.fhirServer,
};
if (req.body.fhirAuthorization) {
const auth = req.body.fhirAuthorization;
Object.assign(state, {
clientId: auth.subject,
scope: auth.scope,
tokenResponse: {
token_type: 'bearer',
scope: auth.scope,
client_id: auth.subject,
expires_in: auth.expires_in,
access_token: auth.access_token
}
});
}
const client = fhirclient(req, res).client(state);
// do stuff with the client
}
I also had the same question as Josh, so thank you, Valdimir, for the explanation regarding why (req, res)
is needed. That makes sense!
Josh Mandel (May 07 2021 at 12:38):
I'll say that API-contract-wise, it's surprising to have to give a FHIR client access to an incoming API request and response. Does this imply that I couldn't use the client library from a "plain old" node CLI app that wasn't itself an http server?
Vladimir Ignatov (May 19 2021 at 13:43):
Yes, it is a little weird, but so would be trying to do oAuth in CLI. That said, it is possible to use it as a FHIR client only. I'm doing that a lot for testing but I don't consider it a viable use case otherwise, thus it is not that user friendly. Under the hood fhirclient(req, res).client(options)
does something like: new Client(new NodeAdapter(req, res), options)
so the customized version may look like:
import Client from "fhirclient/lib/client"
import CLIAdapter from "./somewhere/MyAdapter"
const client = new Client(new CLIAdapter(), options)
client.request("Patient").then(console.log, console.error)
@Josh Mandel Do you think that now (thanks to bulk data) FHIR servers are going to support backend services auth for SMART on FHIR. If that is the case it would be worth considering further updates like backend services support, out of the box CLI adapter and probably better API for non-server node environments.
Chris Moesel (May 19 2021 at 13:48):
FWIW, I find the features like auto-paging, flattening, etc, useful -- which is why I wanted to use it in a non-SMART app. I just didn't want to roll my own page aggregation, etc, when someone else has already implemented it well. Would you recommend some other library for that purpose?
Josh Mandel (May 19 2021 at 13:48):
Thanks for this snippet! In general yes I think we will see more use of backend Services which will be associated with command line applications; I think we will also see continued interest in flexibility here, so being able to simply pass in an access token and an fhir endpoint it is a pretty nice pattern. We might also want to think about something like a "header generation callback", so you could instantiate a client that sends out requests but before each request the registered header generation function has a chance to inspect the request and generate additional headers; this is the useful for other authorizations scenarios (e.g., experiments with DPOP).
Josh Mandel (May 19 2021 at 13:50):
To Chris's point: indeed, I would think about making this library as useful as possible for developers connecting to any FHIR servers (SMART and otherwise).
Last updated: Apr 12 2022 at 19:14 UTC