Short answer: SMART App Launch (SMART on FHIR) is HL7's specification for how an app gets authorised access to a FHIR server on behalf of a user. It is OAuth 2.0 and OpenID Connect with FHIR-specific conventions: a discovery document at .well-known/smart-configuration; two launch modes (EHR launch, where the EHR opens the app in context of the current patient, and standalone launch, where the user opens the app and picks a patient); scopes in a FHIR-aware syntax (patient/Observation.rs means read and search Observations of the patient in context); launch context returned with the token (which patient, which encounter); and backend services for system-to-system access with signed JWT client authentication. SMART v2 (2021 onwards) made PKCE mandatory, added granular scopes and asymmetric client authentication, and is the version national programmes require.
Step 1: discovery
SMART App Launch discovery starts with the app fetching the FHIR server's .well-known/smart-configuration document, which lists the authorisation and token endpoints, the capabilities supported (EHR launch, standalone launch, public and confidential clients, OpenID Connect, patient and user permissions), the scopes supported, the PKCE code challenge methods and the grant types, so the app knows where to send the user.
GET https://fhir.example-hospital.org/.well-known/smart-configuration
{
"issuer": "https://auth.example-hospital.org",
"authorization_endpoint": "https://auth.example-hospital.org/authorize",
"token_endpoint": "https://auth.example-hospital.org/token",
"introspection_endpoint": "https://auth.example-hospital.org/introspect",
"capabilities": ["launch-ehr", "launch-standalone", "client-public", "client-confidential-asymmetric", "sso-openid-connect", "context-ehr-patient", "context-standalone-patient", "permission-patient", "permission-user", "permission-v2", "permission-offline"],
"scopes_supported": ["openid", "fhirUser", "launch", "launch/patient", "patient/*.rs", "user/*.rs", "offline_access"],
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "client_credentials"]
}
The app learns where to send the user and what the server supports.
Step 2: launch
SMART App Launch has two launch modes. In an EHR launch, the clinician opens the app inside the EHR, which passes an iss parameter (the FHIR server) and an opaque launch token encoding the current patient and encounter. In a standalone launch, the user opens the app directly and the authorisation server asks them to pick or confirm a patient.
EHR launch. The clinician clicks the app inside the EHR; the EHR opens the app's launch URL with two parameters: iss (the FHIR server) and launch (an opaque token that encodes the current patient and encounter). The app must echo launch in its authorisation request with the launch scope.
Standalone launch. The user opens the app directly; the app knows the FHIR server (chosen or configured) and asks for context with launch/patient; the authorisation server asks the user to pick or confirm a patient.
Step 3: authorisation code with PKCE
The SMART authorisation step is the OAuth 2.0 authorisation code flow with PKCE: the app sends the user to the authorisation endpoint with its scopes, the aud of the FHIR server and a code challenge; the user authenticates and approves; the app exchanges the returned code and code verifier for an access token that arrives together with the launch context.
GET https://auth.example-hospital.org/authorize?
response_type=code&client_id=my-app&redirect_uri=https://app.example.org/cb
&scope=openid%20fhirUser%20launch%20patient/Observation.rs%20patient/Condition.rs%20offline_access
&state=af0ifjsldkj&aud=https://fhir.example-hospital.org
&launch=xyz123&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256
The user authenticates (with the hospital's identity provider) and approves the scopes; the server redirects back with a code; the app exchanges it:
POST https://auth.example-hospital.org/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=...&redirect_uri=https://app.example.org/cb&client_id=my-app&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid fhirUser launch patient/Observation.rs patient/Condition.rs offline_access",
"refresh_token": "a8c3...",
"id_token": "eyJhbGciOi...",
"patient": "Patient/123",
"encounter": "Encounter/456",
"fhirUser": "Practitioner/42",
"need_patient_banner": true,
"smart_style_url": "https://ehr.example.org/styles/smart.json"
}
The launch context (patient, encounter, fhirUser) arrives alongside the token. Public clients (mobile and browser apps) use PKCE and no client secret; confidential clients authenticate with a secret or, preferably, with a signed JWT (private_key_jwt) under the asymmetric method.
Scopes
SMART on FHIR v2 scopes use the syntax context/resource.permissions, where context is patient, user or system and permissions combine c, r, u, d and s for create, read, update, delete and search, as in patient/Observation.rs. Granular scopes add a filter, openid and fhirUser give the user's identity, launch scopes request context, and offline_access requests a refresh token.
SMART v2 syntax: <context>/<resource>.<permissions> where context is patient (the patient in context), user (whatever the user may see) or system (backend), and permissions are any of c create, r read, u update, d delete, s search, in that order: patient/Observation.rs, user/Patient.rs, system/*.cruds. Granular scopes add a filter: patient/Observation.rs?category=http://terminology.hl7.org/CodeSystem/observation-category|vital-signs. openid fhirUser give an ID token and the user's FHIR identity; launch, launch/patient, launch/encounter request context; offline_access a refresh token for use when the user is away, online_access one valid only during the session. The v1 syntax (patient/Observation.read) is still accepted by many servers.
Backend services
For a system that acts without a user (a nightly export, an integration engine), SMART Backend Services uses client_credentials with a signed JWT assertion (the client's public keys published at a JWKS URL) and system/ scopes. No user, no launch context, strict registration.
Running it safely
Running SMART App Launch safely means registering exact redirect URIs, never using the implicit flow, storing and rotating refresh tokens securely, validating state and the aud of ID tokens, verifying tokens on every API call, enforcing both scopes and the patient context at the data layer so a token for one patient never reads another, and logging every access.
Register exact redirect URIs; never use the implicit flow; store refresh tokens securely and rotate them; validate state; check the aud of ID tokens; verify tokens on every API call (introspection or JWT validation) and enforce scopes and the patient context at the data layer, because a token for Patient/123 must never read Patient/124; log every access (Security and identity: the map). SMART also defines Patient Access Brands, a way for apps to show users which organisations they can connect to, and interoperates with SMART Health Links for patient-initiated sharing.
Where it is required
The US patient-access rules require certified EHRs to support SMART App Launch and Backend Services; national programmes in Europe adopt it for app access (the Netherlands' MedMij uses OAuth 2.0 with equivalent conventions); the IPS and many implementation guides assume it. See TEFCA and National electronic health records.
In Anpheros
Anpheros Platform uses OAuth 2.0 with PKCE for apps and signed-JWT client authentication for backends, with scopes derived from the person's consent and the patient context enforced on every request. The developer site documents authentication and the SMART on FHIR backend pattern for building on it.
Related pages
- Security and identity in health apps: the map
- Patient consent platform
- SMART Health Links explained
- CDS Hooks explained
- FHIR search explained
- SMART on FHIR backend (developer site)
- Authentication (developer site)
Sources
- HL7 SMART App Launch (STU 2.2)
- HL7 SMART App Launch: scopes and launch context
- HL7 SMART App Launch: Backend Services
- IETF RFC 7636: PKCE
- IETF RFC 6749: OAuth 2.0
- OpenID Connect Core 1.0
Frequently asked questions
Is SMART on FHIR the same as OAuth 2.0?
It is OAuth 2.0 and OpenID Connect with FHIR-specific additions: discovery, launch context, the scope syntax and backend services. Any OAuth library works; the SMART conventions are what make an app portable across FHIR servers.
Do I need PKCE for a server-side app?
In SMART v2, yes, for all clients using the authorisation code flow. Confidential clients add client authentication (a secret or a signed JWT) on top.
What is the difference between patient/ and user/ scopes?
patient/ limits access to the single patient in the launch context; user/ grants whatever the authenticated user is allowed to see, across patients. Patient-facing apps use patient/; clinician apps often use user/ with context.
Can a SMART app write to the record?
If granted create or update permissions (c, u) in its scopes and the server allows it. Many deployments grant patient apps read and search only, and route writes through specific resources such as QuestionnaireResponse.
Try it free in the sandbox
Your first FHIR call in 5 minutes. An HL7 FHIR R4 API with the patient's consent built in: try it in the sandbox, then connect your own app or clinic system.
Free · 30 synthetic patients · no card

The more you log, the clearer your record
Your health history in one place, under your control.
Free · Works in your browser · No card
