SMART on FHIR defines a standard way for applications to launch from inside an EHR, obtain an OAuth 2.0 access token scoped to the current patient and encounter, and then call the EHR's FHIR API on the patient's behalf.
/launch endpoint with a launch token → your app discovers the EHR's auth server → PKCE authorization code exchange → access token with patient/encounter context.Your GET /launch controller receives two query parameters from the EHR: iss (the FHIR server base URL) and launch (an opaque context token).
@GetMapping("/launch")
public RedirectView launch(@RequestParam String iss,
@RequestParam String launch) {
SmartConfig cfg = discoveryService.discover(iss);
String verifier = pkce.generateVerifier(); // 96 bytes, base64url
String challenge = pkce.challenge(verifier); // SHA-256
String state = stateStore.save(verifier, iss);
return new RedirectView(cfg.authorizationEndpoint()
+ "?response_type=code"
+ "&client_id=" + clientId
+ "&redirect_uri=" + redirectUri
+ "&scope=launch+openid+fhirUser+patient/*.read"
+ "&launch=" + launch
+ "&aud=" + iss
+ "&state=" + state
+ "&code_challenge=" + challenge
+ "&code_challenge_method=S256");
}
Before redirecting, your app must fetch the EHR's SMART configuration from {iss}/.well-known/smart-configuration. This returns the authorization and token endpoints, supported scopes, and PKCE requirements.
After the user authorises, the EHR redirects back to your /callback with a code. Exchange it for tokens:
POST {token_endpoint}
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code={code}
&redirect_uri={redirect_uri}
&client_id={client_id}
&code_verifier={verifier}
The token response includes access_token, patient, encounter, and need_patient_banner at the top level.
aud={iss} in the authorisation request — Epic requires this.client_id from your Epic App Orchard registration, not a self-assigned value.code_challenge_method.Epic's App Orchard review checks that your app handles token expiry, uses HTTPS everywhere, stores tokens securely (not in localStorage), and implements a proper logout. Budget 4–8 weeks for the review cycle and test thoroughly on the Epic sandbox first.
Have questions about implementing this in your healthcare platform? Get in touch with the Akhester team.