Integrating FHIR Resources with Backend Services: A Comprehensive Guide

In the healthcare industry, secure access to patient information is paramount. To ensure robust authentication for backend applications, SMART backend services provides a comprehensive framework built upon OAuth 2.0 standards. In this guide, we’ll walk you through the process of building a backend OAuth 2.0 app, completing the required Epic community member setup, and obtaining an access token using a JWT (JSON Web Token) for your backend service.

Building a Backend OAuth 2.0 App

OAuth 2.0 authentication for backend applications allows secure access to patient information without direct user interaction. Here’s how you can set up your backend application:

Client Registration

  • Client ID: Register your backend application with the authorization server to obtain a unique client ID. This ID identifies your application within the Epic community.
  • Public Key: Generate a public-private key pair for JWT signing. The public key will be used to validate your signed JWT and confirm your application’s identity.

Creating a Public Private Key Pair: OpenSSL

You can create a new private key named privatekey.pem using OpenSSL with the following command:

openssl genrsa -out /path_to_key/privatekey.pem 2048

Make sure the key length is at least 2048 bits.

Then you can export the public key to a base64 encoded X.509 certificate named publickey509.pem using this command:

openssl req -new -x509 -key /path_to_key/privatekey.pem -out /path_to_key/publickey509.pem -subj '/CN=myapp'

Where ‘/CN=myapp’ is the subject name (for example the app name) the key pair is for. The subject name does not have a functional impact in this case but is required to create an X.509 certificate.

Complete Required Epic Community Member Setup

Ensure your application is mapped to an Epic user account for auditing purposes. This setup is essential for tracking web service calls made by your backend application and maintaining compliance with security standards.

Using a JWT to Obtain an Access Token

JWTs play a crucial role in obtaining access tokens for Backend Services. Here’s how you can generate a JWT and exchange it for an access token:

Step 1: Creating the JWT

  • Header: Define the JWT header with the appropriate algorithm (e.g., RS256 for RSA signing).
  • Payload: Include necessary claims such as client ID, expiration time, and any additional required parameters.
  • Signing: Sign the JWT using your private key to ensure authenticity and integrity.

Step 2: POSTing the JWT to Token Endpoint

  • HTTP POST Request: Send a POST request to the authorization server’s token endpoint, including the JWT in the request body.
  • Parameters: Set the grant_type to client_credentials and specify the client_assertion_type as urn:ietf:params:oauth:client-assertion-type:jwt-bearer.

Managing App Setup and Resource Selection

After completing the OAuth 2.0 setup, you can manage your app’s configuration and select the specific resources you need data from:

App Setup

  • Navigate to Dashboard: Access your app’s dashboard on the Epic on FHIR website.
  • Manage Settings: Configure your app settings, including the callback URL, scopes, and other relevant parameters.
Fig: Application Setup

Resource Selection

  • Scope Selection: Choose the scopes or permissions required for accessing patient data. Scopes define the level of access your application has to Epic resources.
  • Resource Authorization: Select the specific resources (e.g., Patient, Medication, AllergyIntolerance) your application needs data from. This ensures that your app only accesses the necessary information, enhancing security and compliance.
Fig: Resource Selection

Start Building Your Secure Backend OAuth 2.0 App Today. Hire Our Developers Now!

Configuration for Getting Bearer Token from Oauth2 Backend

import os
import json
import requests
from datetime import datetime, timedelta, timezone
import jwt
import secrets


def get_bearer_token():
   EPIC_ENDPOINT = os.getenv('EPIC_ENDPOINT')
   try:
       client_id = os.getenv("CLIENT_ID")
       message = {
           'iss': client_id,
           'sub': client_id,
           'aud':  EPIC_ENDPOINT + "/oauth2/token",
           'jti': secrets.token_hex(16),
           'iat': int(datetime.now(timezone.utc).timestamp()),
           'exp': int((datetime.now(timezone.utc) + timedelta(minutes=5)).timestamp()),
       }
       private_key = os.getenv("FHIR_PVT_FILE").replace("\\n", "\n")
       compact_jws = jwt.encode(message, private_key, algorithm='RS384')
       headers = {
           'Content-Type': 'application/x-www-form-urlencoded',
       }
       data = {
           'grant_type': 'client_credentials',
           'client_assertion_type':
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
           'client_assertion': compact_jws
       }      
       response = requests.post(EPIC_ENDPOINT + "/oauth2/token", headers=headers, data=data)
       response_data = json.loads(response.text)
       bearer_token = response_data['access_token']
       return bearer_token
   except Exception as e:
       raise e
def get_api_headers():
   try:
       bearer_token = get_bearer_token()
       return {
           'Authorization': f"Bearer {bearer_token}",
           'Accept': 'application/fhir+json'
       }
   except Exception as e:
       raise e

Retrieve Patient Data

In my case, I need to retrieve patient-related information using the Medical Record Number (MRN), and then obtain encounter and observation data, I followed these steps:

  1. Authenticate Your Application: Obtain an access token using OAuth 2.0 authentication.
  2. Retrieve Patient ID: Use the MRN to search for the patient’s ID.
  3. Fetch Encounters: Use the patient’s ID to retrieve encounters associated with the patient.
  4. Retrieve Observation Data: For each encounter, fetch observation data and any other related information.
import requests

# OAuth 2.0 access token obtained earlier
access_token = “YOUR_TOKEN”

# Function to retrieve patient ID using MRN
def get_patient_id(mrn):
    base_url = "https://fhir.epic.com/interconnect-fhir-oauth/"
    search_url = f"{base_url}/Patient?identifier=MRN|{mrn}"

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }

    response = requests.get(search_url, headers=headers)

    if response.status_code == 200:
        data = response.json()
        if "entry" in data:
            patient_id = data["entry"][0]["resource"]["id"]
            return patient_id
    return None

# Function to fetch encounters for a patient
def fetch_encounters(patient_id):
    base_url = "https://fhir.epic.com/interconnect-fhir-oauth/"
    encounter_url = f"{base_url}/Encounter?subject={patient_id}"

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }

    response = requests.get(encounter_url, headers=headers)

    if response.status_code == 200:
       data = response.json()
       if "entry" in data:
           encounters = [entry["resource"] for entry in data["entry"]]
           return encounters
    return None

# Function to fetch observation data for an encounter
def fetch_observations(encounter_id):
     base_url = "https://fhir.epic.com/interconnect-fhir-oauth/"
     observation_url = f"{base_url}/Observation?encounter={encounter_id}"

     headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
     }

     response = requests.get(observation_url, headers=headers)

if response.status_code == 200:
    data = response.json()
    if "entry" in data:
        observations = [entry["resource"] for entry in data["entry"]]
        return observations
    return None

# Example usage
mrn = "123456" # Replace with the patient's MRN
patient_id = get_patient_id(mrn)

if patient_id:
    encounters = fetch_encounters(patient_id)
     if encounters:
        for encounter in encounters:
            encounter_id = encounter["id"]
            observations = fetch_observations(encounter_id)
            # Process observation data as needed
            print(f"Encounter ID: {encounter_id}, Observations: 
{observations}")
     else:
          print("No encounters found for the patient.")
else:
    print("Patient not found.")
coma

Conclusion

Using OAuth 2.0 authentication with SMART backend services ensures that your backend application can securely access patient information. Following our discussed steps, you can create a strong and compliant backend system that keeps sensitive data safe. Watch for more tips and tricks on using SMART backend services and OAuth 2.0 authentication to improve healthcare data security. With these technologies, you can significantly advance healthcare delivery and patient care.

Keep Reading

Keep Reading

  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?