Epic FHIR Authentication: EHR Launch and Access Token - How to build an Epic Provider App with SMART on FHIR
Learn FHIR for FREE! Enroll Now!

Epic FHIR Authentication: EHR Launch and Access Token

In the last lesson we registered VitalCheck on fhir.epic.com and got a non-production client ID. In this lesson we use that client ID in Postman to run the EHR launch against Epic’s sandbox and exchange it for an access token.

By the end you’ll have a token that already knows which patient the clinician launched from, and you’ll use it to read that patient’s record.

Launch the app from Epic’s test launcher

On fhir.epic.com, open the Documentation menu and go to the page called Launching your app from Epic. On the right side of that page there’s a Try It button. This is Epic’s test launcher, and it simulates a clinician opening your app from inside a patient’s chart.

Click it and fill in the dialog:

  • App: select VitalCheck.
  • Patient: pick any of the test patients.
  • Launch URL: the endpoint URI you registered in the last lesson. This is where Epic sends the clinician.
  • Tokens: optional extra details about the launch, like the IP address of the machine or the patient’s date of birth. Ignore these for now.
The Try It launcher on the Launching your app from Epic page.
The Try It launcher on the Launching your app from Epic page.

If you click Launch, Epic sends you to your endpoint URL with a few parameters attached. This is exactly what happens when a practitioner opens your app from inside Epic.

Click Generate URL only instead, so you can see what that URL looks like before anything happens. Pasting it into a browser does the same thing as clicking Launch.

Build Your First Real Fhir App in 15 Days Copy

Build your first real FHIR app in 15 days

500+ builders have done it. Live kick-off, structured modules, Demo Day. $27.

Join the challenge

What the launch URL carries

The generated URL is your endpoint URL plus two parameters:

http://localhost:3000
  ?iss=https%3A%2F%2Ffhir.epic.com%2Finterconnect-fhir-oauth%2Fapi%2FFHIR%2FR4
  &launch=eyJhbGciOi...
The generated launch URL with iss and launch highlighted
The launch URL, with the iss and launch parameters.

iss is the issuer. In OAuth 2.0, the issuer is whoever issues the tokens. Here it’s the FHIR server of the practice your app was launched from. That makes it the base URL for every request you make after this. If your app is installed at several hospitals, iss is also how you tell which one just launched your app.

launch is the launch token. It encodes the context of the launch: which patient is open, which encounter, and other details Epic needs.

Why the launch token skips the login screen

In the patient flow, the authorization step sent the user to a MyChart login page and then a consent page. In the EHR launch, you pass the launch token along with the same authorization request, and Epic skips both. The clinician is already logged in to Epic and already on the patient, so there’s nothing to ask. The flow goes to the authorization endpoint and straight back to your redirect.

It’s still the same OAuth flow underneath.

Find the authorization and token endpoints

Copy the iss value. It’s URL-encoded, so run it through a URL decoder first to get the FHIR base URL:

https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4

Append /.well-known/smart-configuration and send a GET request:

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/.well-known/smart-configuration

The response includes a lot of things, but the two you need are:

  • authorization_endpoint
  • token_endpoint
The SMART discovery response
The SMART discovery response.

In production you’d do this against whatever iss the hospital’s Epic instance sends you, because the endpoints differ from site to site.

Set up OAuth 2.0 in Postman

In your Postman collection, open the folder for VitalCheck. Under Authorization, change the type from No Auth to OAuth 2.0 and fill in the fields.

FieldValue
Grant typeAuthorization Code (With PKCE)
Callback URLYour endpoint URI (http://localhost:3000)
Auth URLThe authorization_endpoint from the discovery response
Access Token URLThe token_endpoint from the discovery response
Client IDYour non-production client ID
Client SecretLeave empty
Scopelaunch openid fhirUser user/Patient.read user/Observation.read user/Observation.write
StateAny random value, about 8 characters (12345678 works)
Client AuthenticationSend client credentials in body

A few notes on the fields.

The callback URL is the endpoint URI you registered. It can be the same URL as your app’s base URL, if your app is a single-page app that handles the redirect itself, or a dedicated callback route. In Postman it doesn’t matter, because Postman intercepts the redirect.

The scope must include launch. Without it, Epic won’t accept the launch parameter and you won’t get the patient context back in the token. The rest of the scopes depend on what your app needs. Try different ones and see what Epic returns.

Client authentication has to be set to send credentials in the body.

Add two extra parameters

Scroll down to Advanced and add two parameters to the authorization request.

ParameterValue
launchThe launch token from the generated URL
audThe FHIR base URL (the decoded iss value)

The launch parameter is the one that carries the patient context. The aud parameter (audience) tells Epic which FHIR server you’re asking for access to. Some Epic instances reject the request without it, so add it every time, even though the sandbox may let it through.

Get the token

Click Get New Access Token. Postman redirects to Epic’s authorization page and comes straight back. No login, no consent. Postman takes the authorization code and exchanges it for a token.

If you leave out the launch token, Epic will instead show a provider login page and ask the clinician to sign in. That’s a standalone provider launch. It works, but your app won’t open from inside the EHR and it won’t know which patient is open. You’d be asking the clinician to log in to Epic again from your app.

Read the token response

Click Proceed and scroll down.

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "launch openid fhirUser user/Patient.read user/Observation.read ...",
  "state": "12345678",
  "patient": "erXuFYUfucBZaryVksYEcMg3",
  "encounter": "eBnj6YvXfJ8vN1NzO8Zj2Kw3"
}

Most of this is the same as Module 2. The access token, the token type, the expiry and the scopes you were granted.

The new fields are the launch context:

patient is the FHIR ID of the patient the clinician launched from. This is the only way your app can know which patient is open.

encounter is the ID of the current visit. It appears when the launch happens inside an encounter. You’ll need it in the next lesson, because Epic won’t accept a vitals write without one.

Access Token with Patient and Encounter ID
Access Token with Patient and Encounter ID

This is what makes a provider app different. The clinician is already on a patient in Epic. If your app opens and shows them a patient list to pick from, you’ve broken the workflow. Use the patient ID from the token and show only that patient.

Make your first FHIR request

Click Use Token. Then save the folder with Ctrl+S or Cmd+S.

Postman doesn’t save the authorization change automatically. If you skip the save, the requests inside the folder keep using the old settings and you’ll get an authentication error that looks like a problem with Epic.

Create a GET request in the folder, name it Get Patient, and use the FHIR base URL plus the patient ID from the token:

GET https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Patient/erXuFYUfucBZaryVksYEcMg3

Epic sometimes returns XML by default, so set the Accept header to application/json.

Send it. The response is the Patient resource for the patient you selected in the launcher.

Postman GET Patient request with the Accept header and the Patient resource in the response
The patient the app was launched from.

Every other resource works the same way. You can fetch the patient’s vitals with an Observation search right here in Postman, but we’ll do that in the working app in the next lesson so you can see the request and the response together.

Should your app show the patient’s details?

If your app is embedded inside Epic, the patient’s name and details are already on screen in Epic’s own header. So you don’t strictly need to repeat them.

But it’s a good practice to show them anyway. Put the patient’s name and date of birth somewhere small, off to the side, so the clinician can always confirm your app is on the same patient they are. It’s a safety check, and it costs nothing.

What’s next

You’ve run the EHR launch, exchanged the launch token for an access token, and read the patient data.

In the next lesson we build VitalCheck, read the patient’s vitals through the Observation API, and write a new reading back into Epic. If Postman is giving you an error at any step, post it in the comments.

Comments