Bearer token in a HealthConnect Production world - Looking for best practise

Primary tabs

Hi all,

a HealthConnect customer of ours came across with a question to use an external service via REST and OpenID within one of his HealthConnect  (2020.1) productions. The overal idea is to send data to the external system after receiving a baerer token to use for the communication between HealthConnect and this system.

Since I´ve never done such thing before I have an idea to solve this task but looking for a best practise way to do so. Using the RESt-Api of the external system is not the question here. More interesting is the mechanismn to receive a new bearer token and use it in the communication with the external system. From my perspective HealthConnect acts as a client for the remote system. That´s why I thought it would be the correct way to configure a OAuth2 client for authentication and to receive the bearer token fropm there. From within the customers production I would create some kind of a REST Operation and use code to authenticate/receive the bearer token via OAuth2 and use the token in further communication with the external system. Would this be the way here for my scenario? I´ve had a look in the documentation but couldn´t find anything fitting for my problem.

Thats why my question is - are there any best practises to accomplish my scenario? Or is my approach to do so completly wrong? Any ideas are highly appreciated.

Best regards,

Sebastian

Replies

Good morning Sebastian -

I read your post yesterday and was hopeful someone from InterSystems might respond with the best practices here as I tackled this situation myself a few months ago and had similar questions - the documentation isn't explicitly clear how this should be handled but certainly there are tools and classes available within HealthConnect to rig this up. As there has been no other response, I'll share how I handled this. Maybe others will chime in if they know of a better approach.

For my need, I am working with a vendor that requires we call their OAuth API with some initial parameters sent in a JSON body to receive back a response containing the Bearer token to be used in other API calls.

To achieve this, I created a custom outbound operation for this vendor that extends EnsLib.REST.Operation and using an XData MessageMap, defined a method that would execute the API call to get the Bearer token from the vendor (with the JSON body attributes passed in as a custom message class) and then another method that would execute the other API call that would utilize the Bearer token and pass along the healthcare data defined for this implementation (using a separate custom message class.) XData MessageMap looks similar to this:

XData MessageMap
{
<MapItems>
  <MapItem MessageType="MyCustomMessageClass.VendorName.Request.GetBearerToken">
    <Method>GetBearerToken</Method>
  </MapItem>
  <MapItem MessageType="MyCustomMessageClass.VendorName.Request.SubmitResult">
    <Method>SubmitResult</Method>
  </MapItem>
</MapItems>
}

Within that GetBearerToken method, I define the %Net.HttpRequest parameters, including the JSON body that I extract from the custom message class using %JSON.Adaptor's %JSONExportToString function, and execute the call. On successful status, I take the response coming back from the vendor and convert it into another custom message class (e.g. MyCustomMessageClass.VendorName.Response.GetBearerToken.)

From here, I simply need a way to use my custom outbound operation, define the values in the Request message class and utilize the values coming back to me in the  response message class. For that, I created a BPL that controls the flow of this process. In my case, the Bearer token defined by the vendor has a lifespan that is defined in the response message so I can also store off the Bearer token for a period of time to reduce the amount of API calls I make on a go-forward basis.

Here is an example BPL flow showing the call-outs to get the Bearer token:

So you can see at the top I'm executing a code block to check my current token for expiration based on a date/time I have stored with it. If expired, I call out to my custom operation with the appropriate message class (see the Xdata block I showed) to get a new token and if successful, I execute another code block to read the custom response message class to store off the Bearer token and its expiration date. 

From there it's just a matter of stepping into the next flow of the BPL to send a result that utilizes that Bearer token for its API calls. Each time this BPL is executed, the Bearer token is checked for validity (i.e. not expired) - If expired, it gets a new one, if not, it utilizes the one that was saved off and then the next part of the BPL (not shown), crafts a message for the SubmitResult part of the custom operation and inside that operation, SubmitResult utilizes the stored Bearer token as appropriate to execute its API call.

Hopefully I explained enough to get you going or basically echo what you were thinking of doing.

But I'd certainly be interested in hearing a better approach if there is one.

Regards,

Craig

Hi Craig,

thank you for your response. So first of all good to hear that I am not the only one with this kind of questions. I assume you don´t configured an OAuth Client via SMP directly but instead do the  invocation of the API endpoint to get the token directly in the operation - therefore you should not use any %OAuth... framework-functionalities from within the operation - am I correct?

It would be very interesting to here from InterSystems what would be the best practise here. Anyway this should work. Could you supply me with an example of how you implemented the GetBearerToken() method?

best regards,

sebastian

Hi Sebastian - You are correct in that I implement it all directly in the Operation and the BPL handler with respect to getting the token (Handled in the Operation) and the storing of it (handled in the BPL.) I am not currently using any %OAuth framework functionalities though I have started to peek at them. They don't seem to match my use-case and perhaps it has something to do with how the vendor I'm working with has implemented their OAuth messaging.

My GetBearerToken function ends up looking something like this - below is any early working version. I have since cleaned things up and encapsulated a lot of the build-up of the %Net.HttpRequest object using a custom framework I developed but this will give you a general idea.

My custom message classes extends Ens.Request (or Ens.Response as appropriate) and %JSON.Adaptor. The properties defined within are aligned with what the vendor expects to receive or send back. For instance, when sending this request to the vendor, they typically expect a Client Id, Client Secret, and the Audience and Grant Type being requested for the token. My BPL defines all those properties dynamically before sending the call to the operation.

/// REST WS Method to fetch an environment specific OAuth Bearer Token.
Method GetBearerToken(pRequest As MyCustomMessageClass.Vendor.Request.GetBearerToken, Output pResponse As MyCustomMessageClass.Vendor.Response.GetBearerToken) As %Status
{

// Endpoint for retrieval of Bearer Token from Vendor
Set tURL = "https://vendor.com/oauth/token" 

Try {

Set sc = pRequest.%JSONExportToString(.jsonPayload)
THROW:$$$ISERR(sc) $$$ERROR($$$GeneralError, "Couldn't execute object to json conversion") 

// ..%HttpRequest is a reference to a class defined variable of type %Net.HttpRequest.
// Set HTTP Request Content-Type to JSON
Set tSC=..%HttpRequest.SetHeader("content-type","application/json") 

// Write the JSON Payload to the HttpRequest Body
Do ..%HttpRequest.EntityBody.Write()
tSC = ..%HttpRequest.EntityBody.Write(jsonPayload) 

// Call SendFormDataArray method in the adapter to execute POST. Response contained in tHttpResponse
Set tSC=..Adapter.SendFormDataArray(.tHttpResponse,"POST", ..%HttpRequest, "", "", tURL) 

// Validate that the call succeeded and returned a response. If not, throw error.
If $$$ISERR(tSC)&&$IsObject(tHttpResponse)&&$IsObject(tHttpResponse.Data)&&tHttpResponse.Data.Size 
{
Set tSC = $$$ERROR($$$EnsErrGeneral,$$$StatusDisplayString(tSC)_":"_tHttpResponse.Data.Read())
}
Quit:$$$ISERR(tSC) If $IsObject(tHttpResponse)
{

// Instantiate the response object
pResponse = ##class(MyCustomMessageClass.Vendor.Response.GetBearerToken).%New()
// Convert JSON Response Payload into a Response Object
tSC = pResponse.%JSONImport(tHttpResponse.Data)
}
Catch {
// If error anywhere in the process not caught previously, throw error here.
Set tSC = $$$SystemError
}
Quit tSC
}

Hi Craig,

sry for the late response. Thank you this is what I was already thinking about. I´ve did some tests and the approach works for me. I´ve marked your as accepted.

Anyway I´am still highly interessted in InterSystems point of view on this.

best regards,

sebastian