Use FHIR in open EMR V7

Hi, I am trying to use FHIR in openEMR V7. I am interested in making FHIR API call to get patient health information (encounter, care plan, medications, etc…) stored in open EMR V7. my question is how to obtain JWT (Jason web token) for offline access.

I went through registration for multiple patient’s access and was authorized by data provider. I have the app client ID and secret.

Any documentation and guidance is greatly appreciated. Thanks

Zhiling

1 Like

Zhilling - I was also exploring FHIR APIs and took a while for me understand the integration architecture. I was able to finally understand how JWT works with respect to FHIR servers. Here is some information which might help you to get started:

Client Credentials Grant using JWT for making FHIE API Calls

Client credentials flow is typically used in machine-to-machine (M2M) applications, where the user’s interaction is not suitable or required for authenticating the services. Examples of machine-to-machine applications are CLI scripts, daemons, or backend services. For such applications, the usual authentication mechanisms such as user/password or social logins are not suitable, instead these applications will use Client Credential Flow to authenticate themselves with Client Id and Secret to get a token and use that token to access the resource.

Here are the typical steps in Client Credentials Flow:

  1. Register an app in the application server. When the app is registered, the application server will provide client Id and client secret. This is a one time activity.
  2. Obtain a token from the server using client id and client secret
  3. Access the resource from the server using the token

image
As depicted in the above diagram, client credentials authentication type uses client id and secret while requesting a token from the server.

However when it comes to authentication with FHIR servers like OpenEMR, it doesn’t require client id and secret, but a JWT assertion with client id and other information as explained below.

There is a lot of information about JWT on the web. But it is very confusing for many people to understand the concept and relate that to how FHIR servers use JWTs for authentication.

I will try to explain the concept behind JWT with respect to FHIR server and the process of generating JWT assertions to use in API calls to request a token. There is also a good amount of information on HL7 website: https://hl7.org/fhir/uv/bulkdata/authorization/index

JWT stands for Json Web Token which is a very modern and secure approach used for authenticating the client with the servers. JWT provides a stateless mechanism to share the information without storing session information in cookies which makes it suitable for REST APIs such as FHIR APIs.

Following diagram depicts client credential authentication flow using JWT:

Above steps are explained in detail below:

Step 1: Obtain JWKS (Jason Web Key Set)
JWKS stands for Json Web Key Set which consists of a set of public keys which can be used for verifying Java Web Token (JWT) by the authorization server and signed by RS384 signing algorithm. It is important to note that public key should be provided to the authorization server at the time of registration. This can be done in two ways: provide the public key itself or provide the URL where public can be found.

In our example below, we will use former approach where we will provide the public key at the time of registration.

The easiest way to generate JWK is using the web based application provided by https://mkjwk.org. This website provides an easier way to create JWK.

• Use the following parameters for creating JWK:
• Key Size: 2048
• Key Use: Signature
• Algorithm: RS384:RSA
• Key ID: SHA-256
• Show X.509 : Yes


When you click on Generate button, it will show public and private key set as below. It will show X 5.09 certificate as well just below the key sets.

Copy this information and keep it safely as this will be needed in next steps.

TIP: Note that mkjwk.org claims that they don’t store any of this information on their server. It is safe to use the keys generated by their app, however for added security and confirmation they also provide libraries which can be downloaded locally to generate the keys from your local server.

Step 2: Register the client in Open EMR
After the JWK keys are generated, next step is to register the client in OpenEMR. This is required and stipulated by HL7 guidelines so that the client is preauthorized before requesting a token,

While register the client, following parameters need to be sent in the request:
• Application_type
• redirect URIs
• post_logout_redirect_uris
• Initiate_login_url
• token_endpoint_method
• contacts
• scope
• jwk

Here is as an example of a sample registration request:

  {
   "application_type": "private",
   "redirect_uris": ["https://localhost:8080/redirect"],
   "post_logout_redirect_uris": ["https://localhost:8080/logout"],
   "client_name": "New JWT App 13",
   "initiate_login_url": "https://localhost:8081/launch",
   "token_endpoint_auth_method": "client_secret_post",
   "contacts": [ "me@example.org","them@example.org"],
   "scope": "openid offline_access api:oemr api:fhir api:port patient/Patient.read  patient/Procedure.read   system/Patient.read system/AllergyIntolerance.read system/CarePlan.read system/CareTeam.read system/Condition.read system/Coverage.read system/Device.read system/DiagnosticReport.read system/Document.read system/DocumentReference.read system/Encounter.read system/Goal.read system/Group.read system/Immunization.read system/Location.read system/Medication.read system/MedicationRequest.read system/Observation.read system/Organization.read system/Person.read system/Practitioner.read system/PractitionerRole.read system/Procedure.read system/Provenance.read",
  "jwks": {
    "keys": [{{"kty":"RSA","e":"AQAB","kid":"5c17409c-87f0-4713-814f-c864bfe876bc",
.......
      }
    ]
  }

Tip: Make sure system scope is enabled in Open EMR in Global Settings, otherwise client registration will throw error if you are using system scope.

The registration API will return the client Id and client secret, but we are interested only in client Id. We need this for creating a JWT assertion in next step.

Step 3: Create JWT client assertion
JWT stands for Json Web Token which is a modern and secure way to share client session information in a stateless manner, The easiest way to create a JWT assertion manually by going to jwt.io and entering client id, and other information as mentioned below.

JWT has three parts – header, payload and signature.

Header requires following parameters:

Parameter Description
alg Algorithm used for signing the authentication JWT e.g RS384, ES384
Kid The identifier of the key-pair used to sign this JWT. This identifier should be unique within the client’s JWK Set.
Type Fixed Value: JWT
Jku Optional - The TLS-protected URL to the JWK Set containing the public key(s) accessible without authentication or authorization. When present, this should match a value that the client supplied to the FHIR server at client registration time.

Payload requires following parameters:

Parameter Description
iss Issuer of the JWT – the client’s client_id, as determined during registration with the FHIR authorization server
sub The service’s client_id, as determined during registration with the FHIR authorization server (note that this is the same as the value for the iss claim)
aud The FHIR authorization server’s “token URL” (the same URL to which this authentication JWT will be posted) Example: https://xxyx.xx/oauth2/default/token
jti A string value that uniquely identifies this authentication JWT.

To get JWT, go to https://jwt.io and enter header and payload information as explained above. For signature, use the public and private key which were obtained in Step 1 while creating JWK.

The UI in jwt.io looks as below:


Tip: Signature Verified check will appear only if the signature key is valid. If you see Invalid Signature, ensure that public and private keys are correct.

Tip: This step can be automated programmatically in Java, Python or any other languages. Jwt.io provides a means for quickly creating the JWT assertion for PoC or testing purpose, but it is recommended to automate this process in your application.

Step 4: Request a token using JWT assertion
In this step, you will use the JWT assertion created in the previous step. As mentioned earlier, token is needed for making a FHIR API call.

To get a token, make a HTTP POST request to token endpoint URL using content type application/x-www-form-urlencoded with following parameters:

Parameter Description
scope It should match the scope which was provided during client registration process. Example: openid offline_access api:oemr api:fhir api:port patient/Patient.read patient/Procedure.read system/Patient.read system/AllergyIntolerance.read system/CarePlan.read system/CareTeam.read system/Condition.read system/Coverage.read system/Device.read system/DiagnosticReport.read system/Document.read system/DocumentReference.read system/Encounter.read system/Goal.read system/Group.read system/Immunization.read system/Location.read system/Medication.read system/MedicationRequest.read system/Observation.read system/Organization.read system/Person.read system/Practitioner.read system/PractitionerRole.read system/Procedure.read system/Provenance.read",
grant_type Fixed value: client_credentials
client_assertion_type Fixed value: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion Signed authentication JWT value (from step 3)

Postman Request:

Use the access_token returned by the response in the next step to make a FHIR API call

Step 5 Issue FHIR API Call
We are almost at the end. We have a new access token. This needs to be sent in FHIR API call with Authorization header of Bearer token.

If everything goes well, you will be able to see the response from the FHIR server as shown above.

4 Likes

Great post Mandrake, only thing I would mention is that applications that are patient facing (meaning it is a patient that is using the app in their browser or mobile device) should use the Authorization Grant flow and not the client credentials grant. If you are needing offline_access you must request the offline_access scope in your app registration as well as in your authorization request.

Client Credentials grant is intended to be used for backend server to server communication. While you can register a mobile device or even a browser with a JWKS right now in OpenEMR, at some point we plan to restrict the JWKS registration to the URL option only in order to improve the security and ‘authenticity’ element of the credentials grant. The OpenEMR server will request the JWKS from the backend and so the URL must be publicly accessible on the internet secured via SSL/TLS.

Overall though great explanation and walkthrough @mandrake, thanks for putting this together.

2 Likes

Thanks for the feedback Stephen! I totally agree with you that Client Credentials grant is for backend servers while Authorization Grant flow is for patient facing applications where there would be some kind of interaction by the user for authentication. I am in a process of creating a writeup on Authorization Code grant. I will post that here once that is ready.

I also agree with you about JWK URL as this is a better option as this will allow the client to recycle or change the JWK anytime. I didn’t mention that in this post because, I wanted to keep it very simple and easy to follow because I felt JWK URL option would be confusing for many beginners as it happened to me.

1 Like

Agreed! Very helpful where I’m sure many will be grateful…

Thank you Mandrake for such elaborated response. I am following along, but stuck at step 3 - create JWT client assertion. One of Payload parameters is “jti” (value uniquely identifies this JWT), where do I get this value? Thanks again

Zhiling

It could be any unique combination of alphanumeric characters. I was just making up the values using system timestamp.

The JTI must be unique as the system tracks the JTI values to prevent replay attacks.

Thank you both @Mandrake and @adunsulag for quick response. will provide more updates as I am moving forward.

Hi, @Mandrake. I couldn’t pass the step of acquiring access token. I received “invalid client” in postman. In the step 3, I have signature successfully verified. See postman screen shot. any suggestions would be great.

Thank you

During client app registration step, wonder if I filled following two fields right:

App Redirect URI (also is used for aud claim): https://something.com/oauth2/default/token
App Launch URI: https://somthing.com/apis/default/fhir

Thanks

Have you enabled the logging? Can you post the message what you see in the error log? Generally log files are located in /var/log/apache2/error.log

Thank you so much @Mandrake. I successfully went through the whole process - made the call “…/apis/default/fhir/patient” and results were returned using the access token. Your detailed document is the key for me to understand the process.

I have a follow up question. The access token is only good for 300s. If this is the case, do I repeat the process programmatically from step 3 (Create JWT client assertion) to get access token every time when I need to make the call?

Thanks again

Zhiling

Yes, you repeat the process each time to renew the token using client credentials grant. If you were doing the authorization_code grant you would request the offline_scope and use the refresh_token to get a new token.

1 Like

Thank you Stephen, that’s what I plan to do as I am using client credential grant for now. would like to explore authorization grant after I finish credential grant. I actually also watched your youtube video - OpenEMR FHIR Lecture Series: Lecture 1) and found that’s very helpful. Thank you both (@adunsulag and @Mandrake) for such detailed and helpful material.

Hi, @Mandrake. I couldn’t pass the step of acquiring access token. same of @Zhiling
i also enabled openemr FHIR system scopes

Thank you

Hi mandrake,
I followed by your steps but i faced error . i used demo openemr site

Hi @Param_CapMinds ,
Regarding the issue , you can try this alternative method to fetch the access token using the grant type as password with some additional parameters.If you have further queries, do drop a mail to openemr.support@visolve.com
-Visolve-AI-Team

Thank you Visolve-AI Team.I resolved my issue. :blush: