Implementing OAuth2 Authentication with SMART on FHIR in Node.js
Technology Blogs

Implementing OAuth2 Authentication with SMART on FHIR in Node.js

Bhuvnesh Sharma
Software Engineer
Table of Content

In modern healthcare applications, secure integration with Electronic Health Record (EHR) systems is crucial for accessing patient data while maintaining compliance and security. OAuth2 Authentication with SMART on FHIR in Node.js provides an industry-standard solution using OAuth2 authentication with healthcare-specific extensions. SMART on FHIR enables healthcare applications to integrate with various EHR systems using a standardized approach, reducing development complexity and ensuring compliance with healthcare security standards. When integrated with Node.js, it allows developers to build scalable healthcare applications that securely connect to multiple FHIR-enabled systems.

SMART on FHIR enables healthcare applications to integrate with various EHR systems using a standardized approach, reducing development complexity and ensuring compliance with healthcare security standards. When integrated with Node.js, it allows developers to build scalable healthcare applications that securely connect to multiple FHIR-enabled systems.

This guide covers implementing OAuth2 authentication using SMART on FHIR protocol in Node.js, including endpoint discovery, OAuth2 flow implementation, authenticated FHIR requests, and security best practices.

Understanding SMART on FHIR

What is SMART on FHIR?

SMART on FHIR combines HL7 FHIR standard for healthcare data exchange with OAuth2 for secure authentication and authorization. It provides a standardized way for healthcare applications to access patient data from EHR systems.

Why Use SMART on FHIR?

SMART on FHIR offers several key benefits for healthcare applications:

  • Enhanced Security – OAuth2 with healthcare-specific scopes ensures authorized access to patient data
  • Interoperability – Works across different EHR systems using standardized protocols
  • Patient Context – Automatically provides patient context during app launch
  • Granular Permissions – Supports fine-grained access control with specific FHIR resource scopes
  • Compliance Ready – Designed to meet healthcare regulatory requirements

SMART on FHIR is particularly useful for clinical decision support tools, patient engagement applications, and any healthcare app requiring secure access to patient data.

Prerequisites

Before starting, ensure you have:

  • Node.js (latest LTS version)
  • npm (Node Package Manager)
  • Access to a SMART on FHIR-enabled server
  • Basic understanding of OAuth2 concepts

Looking for Seamless EHR Integration?

Step 1: Setting Up the Project

Create a new Node.js project and install required dependencies:

mkdir smart-fhir-oauth-app
cd smart-fhir-oauth-app
npm init -y
npm install express axios express-session querystring dotenv

Package descriptions:

  • express – Web framework for building APIs and handling HTTP requests
  • axios – HTTP client for making requests to FHIR servers and OAuth endpoints
  • express-session – Session middleware for securely storing authentication tokens
  • dotenv – Loads environment variables for configuration

Step 2: Environment Configuration

Create a .env file to store SMART on FHIR configuration:

CLIENT_ID=your-smart-client-id
REDIRECT_URI=http://localhost:3000/callback
FHIR_BASE_URL=https://fhir-server.example.com
SESSION_SECRET=your-random-session-secret
PORT=3000

This configuration defines your app’s identity and connection details for the FHIR server.

Step 3: Basic Server Setup

Create app.js with Express server configuration:

const express = require('express');
const session = require('express-session');
require('dotenv').config();

const app = express();

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 3600000 } // 1 hour
}));

app.use(express.json());

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`SMART on FHIR app running on http://localhost:${PORT}`);
});

This sets up Express with secure session management for storing OAuth tokens.

Step 4: SMART Configuration Discovery

Create smartConfig.js to handle endpoint discovery:

const axios = require('axios');

async function discoverSmartConfig(fhirBaseUrl) {
  try {
    const configUrl = `${fhirBaseUrl}/.well-known/smart_configuration`;
    const response = await axios.get(configUrl);
   
    console.log('Discovered endpoints:', {
      authorization: response.data.authorization_endpoint,
      token: response.data.token_endpoint
    });
   
    return response.data;
  } catch (error) {
    console.error('SMART discovery failed:', error.message);
    throw new Error('Unable to discover SMART configuration');
  }
}

module.exports = { discoverSmartConfig };

This automatically discovers OAuth endpoints from the FHIR server’s well-known configuration.

Step 5: Launch Authentication Flow

Create the launch endpoint to initiate OAuth2 flow:

const { discoverSmartConfig } = require('./smartConfig');
const querystring = require('querystring');

app.get('/launch', async (req, res) => {
  try {
    const smartConfig = await discoverSmartConfig(process.env.FHIR_BASE_URL);
   
    const state = Math.random().toString(36).substring(2, 15);
    req.session.oauthState = state;
   
    const scopes = 'launch patient/Patient.read patient/Observation.read';
   
    const authParams = {
      response_type: 'code',
      client_id: process.env.CLIENT_ID,
      redirect_uri: process.env.REDIRECT_URI,
      scope: scopes,
      state: state,
      aud: process.env.FHIR_BASE_URL
    };
   
    const authUrl = `${smartConfig.authorization_endpoint}?${querystring.stringify(authParams)}`;
    res.redirect(authUrl);
   
  } catch (error) {
    res.status(500).json({ error: 'Launch failed' });
  }
});

This builds the authorization URL with SMART-specific parameters and redirects users to authenticate.

Step 6: Handle OAuth2 Callback

Process the authorization callback and exchange code for tokens:

app.get('/callback', async (req, res) => {
  const { code, state, error } = req.query;
 
  if (error) {
    return res.status(400).json({ error: 'Authorization failed' });
  }
 
  if (state !== req.session.oauthState) {
    return res.status(400).json({ error: 'Invalid state parameter' });
  }
 
  try {
    const smartConfig = await discoverSmartConfig(process.env.FHIR_BASE_URL);
   
    const tokenParams = {
      grant_type: 'authorization_code',
      code: code,
      redirect_uri: process.env.REDIRECT_URI,
      client_id: process.env.CLIENT_ID
    };
   
    const tokenResponse = await axios.post(
      smartConfig.token_endpoint,
      querystring.stringify(tokenParams),
      { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
    );
   
    req.session.accessToken = tokenResponse.data.access_token;
    req.session.patientId = tokenResponse.data.patient;
   
    res.json({ message: 'Authentication successful!', patient: tokenResponse.data.patient });
   
  } catch (error) {
    res.status(500).json({ error: 'Token exchange failed' });
  }
});

This handles the callback, validates the state parameter, and exchanges the authorization code for access tokens.

Step 7: Making Authenticated FHIR Requests

Use access tokens to fetch FHIR resources:

app.get('/patient', async (req, res) => {
  const { accessToken, patientId } = req.session;
 
  if (!accessToken) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
 
  try {
    const response = await axios.get(
      `${process.env.FHIR_BASE_URL}/Patient/${patientId}`,
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Accept': 'application/fhir+json'
        }
      }
    );
   
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch patient data' });
  }
});

app.get('/observations', async (req, res) => {
  const { accessToken, patientId } = req.session;
 
  if (!accessToken) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
 
  try {
    const response = await axios.get(
      `${process.env.FHIR_BASE_URL}/Observation?patient=${patientId}`,
      {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Accept': 'application/fhir+json'
        }
      }
    );
   
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch observations' });
  }
});

These endpoints demonstrate how to use stored access tokens for authenticated requests to different FHIR resources.

Step 8: Running the Application

Start your SMART on FHIR OAuth2 application:

node app.js

Visit http://localhost:3000/launch to initiate authentication, then access patient data at /patient and /observations endpoints.

Security Best Practices

To ensure secure implementation, follow these strategies:

State Parameter Validation – Always validate state parameter to prevent CSRF attacks Secure Token Storage – Use encrypted sessions with appropriate expiration times
HTTPS in Production – Enable secure cookies and HTTPS for production deployments Scope Validation – Request only necessary permissions and validate received scopes Error Handling – Implement proper error handling for failed authentication attempts

Example of secure session configuration for production:

app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: { secure: true, httpOnly: true },
  maxAge: 3600000
}));
coma

Conclusion

Integrating OAuth2 Authentication with SMART on FHIR in Node.js provides secure, standardized access to healthcare data across different EHR systems. This implementation ensures compliance with healthcare security standards while enabling seamless patient data access.

The modular approach demonstrated allows for easy maintenance and extension of functionality. Key benefits include standardized authentication, automatic patient context management, and fine-grained access control through FHIR-specific scopes.

SMART on FHIR is essential for modern healthcare applications, and its Node.js integration makes it excellent for building scalable, interoperable healthcare solutions. Start implementing OAuth2 Authentication with SMART on FHIR in Node.js today to unlock secure access to healthcare data!

Bhuvnesh Sharma

Bhuvnesh Sharma

Software Engineer

Bhuvnesh is a proficient Full-Stack developer with 4+ years of expertise in the MERN stack. He excels in creating sustainable, scalable web applications and RESTful APIs with optimized code. Specializing in dynamic user interfaces and robust server-side applications, he is dedicated to staying current with the latest tech trends. His passion for innovation drives him to deliver high-quality solutions that exceed client expectations.

Share This Blog

Read More Similar Blogs

Let’s Transform
Healthcare,
Together.

Partner with us to design, build, and scale digital solutions that drive better outcomes.

Location

5900 Balcones Dr, Ste 100-7286, Austin, TX 78731, United States

Contact form