---
title: "SMART on FHIR Backend Services: Why Asymmetric JWTs Matter for Healthcare"
date: 2026-04-19
category: vitalchain
source: VitalChain Health Research
draft: true
---

# SMART on FHIR Backend Services: Why Asymmetric JWTs Matter for Healthcare

Healthcare interoperability has a quiet authentication problem. Most teams evaluating a new platform ask about FHIR support, data models, and coverage across the major EHR vendors. Far fewer ask the question that actually determines whether the integration will ever go live: how does your server prove it is who it says it is?

We've been living inside that question for the past several weeks. On April 17 we verified our SMART on FHIR Backend Services implementation end-to-end against the SMART App Launcher, the reference harness that runs the same authentication flow Epic, Cerner, and Athena use in production. This post is a walk through what that flow actually is, why the major EHR vendors have all settled on it, and what changes when a health IT platform gets this piece right versus wrong.

## The Short Version

SMART on FHIR Backend Services is the standard that lets one system (say, a population health platform, an analytics tool, or in our case an emergency records blockchain) authenticate to an EHR without a human logged in. No browser, no redirect, no user clicking "allow." It is server to server, and every major US EHR vendor now requires it for any app that pulls data in the background.

The mechanism looks simple on paper. Your server signs a short-lived JSON Web Token with a private key, sends it to the EHR's token endpoint, the EHR verifies the signature against your public key, and hands back an access token. Your server then uses that access token to call the FHIR API.

What's interesting is what this flow is not. It is not a shared secret. It is not an API key. It is not a client ID plus a client secret in a Basic auth header. Epic, Cerner, and Athena have all deprecated or outright rejected those older patterns for Backend Services apps. The reason why is worth understanding.

## What a Shared Secret Gets Wrong

If you've built any HTTP API in the past decade, you've used shared-secret authentication. The client and server both hold a copy of the same secret string. The client sends it in a header, the server checks it matches, the request proceeds. It's easy to implement, easy to rotate, easy to explain.

The trouble is that every party who can verify the secret can also impersonate the holder. The EHR vendor has your secret on their side, their database administrator has it, their backup system has it, and anyone who gets a copy of any of those stores can turn around and pretend to be you. In a consumer API that risk is acceptable. In healthcare, where a leaked secret means an attacker can query protected health information under your app's identity, it is not.

Asymmetric keys solve this by splitting the responsibility. You generate a key pair. The private key never leaves your infrastructure. The public key is what you register with the EHR. The EHR can verify that a token was signed by the holder of your private key, but the EHR itself cannot produce a valid token. Even a total compromise of the EHR's credential store does not let an attacker impersonate your platform.

In regulatory terms, this is the difference between a secret you share and a secret you hold. HIPAA's Technical Safeguards care deeply about that distinction, because the audit trail for "who accessed this record" is only meaningful if the identity behind the access is actually hard to forge.

## Why Epic, Cerner, and Athena Rejected client_secret_basic

When we probed Epic's sandbox capability statement on April 17, it advertised OAuth2 endpoints but explicitly constrains Backend Services apps to asymmetric client authentication. Epic App Orchard apps must register a JWKS URL or a static public key. Cerner's Code Console follows the same pattern. Athena does as well.

The HL7 SMART App Launch specification actually permits `client_secret_basic` for some flows, but every major vendor we've evaluated has removed it as an option for the system-to-system grant type. The reasoning tracks with what we just walked through. Healthcare data moves in bulk, APIs are queried by automated systems running without human oversight, and an app holding `system/*.read` scope against a large EHR can pull years of records in minutes. The blast radius of a shared-secret compromise is too large to accept.

So when a platform tells you it "integrates with Epic" but its auth is a client ID and a client secret in a header, one of two things is true: either the integration runs against a sandbox that does not enforce production constraints, or the platform has not actually completed a production App Orchard submission. There is no path to Epic production with shared-secret auth for Backend Services.

## What We Actually Built

Our SMART token endpoint accepts the RFC 7523 client assertion grant. A partner system signs a short-lived JWT with their private key, posts it to our `/auth/token` endpoint along with `grant_type=client_credentials` and `client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer`, and we hand back a bearer access token scoped to whatever SMART scopes they requested and we support.

On the outbound side, when VitalChain pulls data from a partner EHR, we reverse the flow. Our platform holds an RSA-2048 private key, generated automatically at container startup. We publish the matching public key at `https://pilot.vitalchainhealth.com/.well-known/jwks.json` in standard JWKS format. When we need to authenticate to a partner's token endpoint, we build a client assertion with `iss` and `sub` set to our client ID, `aud` set to the partner's token endpoint, a five-minute expiry, and a unique `jti` to prevent replay. We sign it with RS256 and include a `kid` header so the partner knows which key to use for verification.

That last detail, the `kid`, is where we spent more time than we'd like to admit. The SMART App Launcher rejected our first live attempt with `invalid_client: Missing token 'kid' header`. Epic, Cerner, and Athena all return a similar error. The spec allows a client to use multiple signing keys, and partners cannot be expected to guess which one signed a given request. We now compute the `kid` as the first 16 hex characters of SHA-256 of the public key's modulus, both in our JWKS endpoint and in the JWT header we emit. They match, and the verification succeeds.

## The Discovery Step No One Talks About

SMART on FHIR has a discovery mechanism that makes Backend Services genuinely portable. When our platform connects to a new partner EHR, we don't need a configuration file listing their token endpoint. We fetch their `/.well-known/smart-configuration` document, read `token_endpoint` from the JSON, and use that. Partners discover our endpoint the same way.

This matters because it means a new partnership is, in the happy case, a matter of two parties registering each other's client IDs and public keys. No bespoke integration work, no shared credential exchange, no "send us your secret over email." The trust boundary is clean: our private key, their private key, and two public JWKS documents that either side can fetch and cache.

We have to be honest that the happy case is rarely the actual case. Real EHR vendors have registration workflows that take days or weeks, require legal review, and gate production access behind conformance testing. None of that is a SMART problem, it's a vendor policy problem. But the underlying protocol is the thing that makes the eventual integration straightforward once the paperwork clears.

## What We Verified, and What's Still Ahead

On April 17 we ran the full Backend Services flow end-to-end against the SMART App Launcher at launch.smarthealthit.org. Our platform built a signed client assertion, posted it to the Launcher's token endpoint, the Launcher fetched our public JWKS, verified the signature, and returned a bearer token carrying `system/*.read system/*.write` scope. We then used that bearer on a FHIR Patient search and got back a valid Bundle. To our knowledge, this was the first time our platform has completed a real external SMART Backend Services exchange.

We also probed the Cerner open sandbox directly and captured real MedicationRequest, AllergyIntolerance, and Patient bundles that our FHIR bridge now handles end-to-end. Cerner's open sandbox doesn't require auth, so it tested our resource mapping rather than our token flow. The SMART Launcher exercise closed that second loop.

We want to be precise about what this means. We have not integrated with Epic, Cerner, or Athena. Integration with those vendors requires app registration, conformance testing, and in Epic's case App Orchard review. What we've done is verify that our implementation is correct against the reference standard those vendors use. When we go through their registration processes, the technical implementation will hold up. That's a different claim than a partnership, and the distinction matters.

## Why This Should Matter to You

If you're a CTO or health IT decision maker looking at interop platforms, the Backend Services question is a fast way to separate real implementations from vaporware. Ask how the platform authenticates outbound to EHR partners. If the answer involves shared secrets, client secrets in headers, or any form of symmetric credential, the platform has not actually been engineered for production EHR integration. If the answer involves asymmetric keys, JWKS endpoints, and RS256 or ES384 signing, you're looking at something that can plausibly go live.

For security engineers, the takeaway is that Backend Services is one of the few places where healthcare's regulatory pressure has produced a genuinely good authentication pattern. Asymmetric client authentication is the right design for any high-trust, high-volume, server-to-server API, not just healthcare. Watching the EHR vendors converge on it has been a rare case of the industry moving in the right direction without a breach forcing the hand.

For us, it's a milestone. Verified against the standard, ready for registration, and built the long-term way from the start. The next step is submitting for App Orchard, Code Console, and the Athena developer portal, and the engineering is no longer the bottleneck.
