SMART App Launch
1.1.0 - ballot

This page is part of the Smart App Launch Implementation Guide (v1.1.0: STU 2 Ballot 1) based on FHIR R4. The current version which supercedes this version is 2.0.0. For a full list of available versions, see the Directory of published versions

Launch and Authorization

The SMART on FHIR v1 IG has been widely adopted for clinician- and patient-facing app integration into EHRs and other FHIR data systems. Based on community feedback, the Argonaut Project has undertaken a 2020 effort to revise and improve the SMART App Launch IG. A key area of focus in adding support for “granular permissions,” e.g. to provide access to resources at the category level in addition to the type level. This would allow apps to request narrower access, like “all vital signs” rather than “all observations.”

Enhancements and Clarifications to the SMART App Launch specification for this ballot version 1.1.0:

  • Clarification on launch context scopes
  • New scope syntax for granular permissions
  • POST-based authorization
  • Addition of PKCE to authorization requirements
  • Profile token introspection
  • Guidance for communicating permissions to end users
  • Update discovery properties to support these changes

See the Jira tracker FHIR-30578 for a log of changes included in the ballot:

The SMART App Launch Framework connects third-party applications to Electronic Health Record data, allowing apps to launch from inside or outside the user interface of an EHR system. The framework supports apps for use by clinicians, patients, and others via a PHR or Patient Portal or any FHIR system where a user can give permissions to launch an app. It provides a reliable, secure authorization protocol for a variety of app architectures, including apps that run on an end-user’s device as well as apps that run on a secure server. The Launch Framework supports the four uses cases defined for Phase 1 of the Argonaut Project:

  1. Patients apps that launch standalone
  2. Patient apps that launch from a portal
  3. Provider apps that launch standalone
  4. Provider apps that launch from a portal

Profile audience and scope

This profile is intended to be used by developers of apps that need to access FHIR resources by requesting access tokens from OAuth 2.0 compliant authorization servers. It is compatible with FHIR R2 (DSTU2) and later; this publication includes explicit definitions for FHIR R4.

OAuth 2.0 authorization servers are configured to mediate access based on a set of rules configured to enforce institutional policy, which may include requesting end-user authorization. This profile does not dictate the institutional policies that are implemented in the authorization server.

The profile defines a method through which an app requests authorization to access a FHIR resource, and then uses that authorization to retrieve the resource. Synchronization of patient context is not addressed. In other words, if the patient chart is changed during the session, the application will not inherently be updated. Other security mechanisms, such as those mandated by HIPAA in the US (end-user authentication, session time-out, security auditing, and accounting of disclosures) are outside the scope of this profile.

This profile provides a mechanism to delegate an entity’s permissions (e.g., a user’s permissions) to a 3rd-party app. The profile includes mechanisms to delegate a limited subset of an entity’s permissions (e.g., only sharing access to certain data types). However, this profile does not model the permissiosn that the entity has in the first place (e.g., it provides no mechanism to specify that a given entity should or should not be able to access specific records in an EHR). Hence, this profile is designed to work on top of an EHR’s existing user and permissions management system, enabling a standardized mechanism for delegation.

Security and Privacy Considerations

App protection

The app is responsible for protecting itself from potential misbehaving or malicious values passed to its redirect URL (e.g., values injected with executable code, such as SQL) and for protecting authorization codes, access tokens, and refresh tokens from unauthorized access and use. The app developer must be aware of potential threats, such as malicious apps running on the same platform, counterfeit authorization servers, and counterfeit resource servers, and implement countermeasures to help protect both the app itself and any sensitive information it may hold. For background, see the OAuth 2.0 Threat Model and Security Considerations.

  • Apps SHALL ensure that sensitive information (authentication secrets, authorization codes, tokens) is transmitted ONLY to authenticated servers, over TLS-secured channels.

  • Apps SHALL generate an unpredictable state parameter for each user session. An app SHALL validate the state value for any request sent to its redirect URL; include state with all authorization requests; and validate the state value included in access tokens it receives.

  • An app SHALL NOT execute any inputs it receives as code.

  • An app SHALL NOT forward values passed back to its redirect URL to any other arbitrary or user-provided URL (a practice known as an “open redirector”).

  • An app SHALL NOT store bearer tokens in cookies that are transmitted in the clear.

  • Apps should persist tokens and other sensitive data in app-specific storage locations only, not in system-wide-discoverable locations.

Support for “public” and “confidential” apps

Within this profile we differentiate between the two types of apps defined in the OAuth 2.0 specification: confidential and public. The differentiation is based upon whether the execution environment within which the app runs enables the app to protect secrets. Pure client-side apps (for example, HTML5/JS browser-based apps, iOS mobile apps, or Windows desktop apps) can provide adequate security, but they may be unable to “keep a secret” in the OAuth2 sense. In other words, any “secret” key, code, or string that is statically embedded in the app can potentially be extracted by an end-user or attacker. Hence security for these apps cannot depend on secrets embedded at install-time.

For strategies and best practices to protecting a client secret refer to:

Use the confidential app profile if your app is able to protect a secret

for example:

  • App runs on a trusted server with only server-side access to the secret
  • App is a native app that uses additional technology (such as dynamic client registration and universal redirect_uris) to protect the secret
Use the public app profile if your app is unable to protect a secret

for example:

  • App is an HTML5 or JS in-browser app that would expose the secret in user space
  • App is a native app that can only distribute a secret statically

Considerations for PKCE Support

All SMART apps SHOULD support Proof Key for Code Exchange (PKCE), and public client SMART apps SHALL support PKCE. PKCE is a standardized, cross-platform technique for public clients to mitigate the threat of authorization code interception. PKCE is described in IETF RFC 7636. SMART requires the S256 code_challenge_method. The plain method is not supported.

Implementers can review the OAuth Security Topics guidance from IETF as a collection of Best Current Practices.

Some resources shared with apps following this IG may be considered Patient Sensitive; implementers should review the Core FHIR Specification’s Security Page for additional security and privacy considerations.

Registering a SMART App with an EHR

Before a SMART app can run against an EHR, the app must be registered with that EHR’s authorization service. SMART does not specify a standards-based registration process, but we encourage EHR implementers to consider the OAuth 2.0 Dynamic Client Registration Protocol for an out-of-the-box solution.

No matter how an app registers with an EHR’s authorization service, at registration time every SMART app must:

  • Register zero or more fixed, fully-specified launch URL with the EHR’s authorization server
  • Register one or more fixed, fully-specified redirect_uris with the EHR’s authorization server. Note: In the case of native clients following the OAuth 2.0 for Native Apps specification (RFC 8252), it may be appropriate to leave the port as a dynamic variable in an otherwise fixed redirect URI.

SMART authorization & FHIR access: overview

An app can launch from within an existing EHR or Patient Portal session; this is known as an EHR launch. Alternatively, it can launch as a standalone app.

In an EHR launch, an opaque handle to the EHR context is passed along to the app as part of the launch URL. The app later will include this context handle as a request parameter when it requests authorization to access resources. Note that the complete URLs of all apps approved for use by users of this EHR will have been registered with the EHR authorization server.

Alternatively, in a standalone launch, when the app launches from outside an EHR session, the app can request context from the EHR authorization server during the authorization process described below.

Once an app receives a launch request, it requests authorization to access a FHIR resource by causing the browser to navigate to the EHR’s authorization endpoint. Based on pre-defined rules and possibly end-user authorization, the EHR authorization server either grants the request by returning an authorization code to the app’s redirect URL, or denies the request. The app then exchanges the authorization code for an access token, which the app presents to the EHR’s resource server to access requested FHIR resources. If a refresh token is returned along with the access token, the app may use this to request a new access token, with the same scope, once the access token expires.

SMART “launch sequence”

The two alternative launch sequences are described below.

EHR launch sequence

In SMART’s EHR launch flow (shown above), a user has established an EHR session, and then decides to launch an app. This could be a single-patient app (which runs in the context of a patient record), or a user-level app (like an appointment manager or a population dashboard). The EHR initiates a “launch sequence” by opening a new browser instance (or iframe) pointing to the app’s registered launch URL and passing some context.

The following parameters are included:

Parameters
iss required Identifies the EHR's FHIR endpoint, which the app can use to obtain additional details about the EHR, including its authorization URL.
launch required Opaque identifier for this specific launch, and any EHR context associated with it. This parameter must be communicated back to the EHR at authorization time by passing along a launch parameter (see example below).
For example

A launch might cause the browser to navigate to:

Location: https://app/launch?iss=https%3A%2F%2Fehr%2Ffhir&launch=xyz123

On receiving the launch notification, the app would query the issuer’s /metadata/ endpoint or .well-known/smart-configuration endpoint which contains (among other details) the EHR’s identifying the OAuth authorize and token endpoint URLs for use in requesting authorization to access FHIR resources.

Later, when the app prepares a list of access scopes to request from the EHR authorization server, it will be associated with the existing EHR context by including the launch notification in the scope.

Standalone launch sequence

Alternatively, in SMART’s standalone launch flow (shown above), a user selects an app from outside the EHR, for example by tapping an app icon on a mobile phone home screen. This app will launch from its registered URL without a launch id.

In order to obtain launch context and request authorization to access FHIR resources, the app discovers the EHR authorization server’s OAuth authorize and token endpoint URLs by querying their .well-known/smart-configuration file.

The app then can declare its launch context requirements by adding specific scopes to the request it sends to the EHR’s authorization server. The authorize endpoint will acquire the context the app needs and make it available.

For example:

If the app needs patient context, the EHR’s authorization server may provide the end-user with a patient selection widget. For full details, see SMART launch context parameters.

  • launch/patient - to indicate that the app needs to know a patient ID
  • launch/encounter - to indicate the app needs an encounter

SMART authorization and resource retrieval

SMART authorization sequence

Step 1: App asks for authorization

At launch time, the app constructs a request for authorization by supplying the following parameters to the EHR’s “authorize” endpoint.

Note on PKCE Support: if an app supplies PKCE parameters in the authorization request (code_challenge and code_challenge_method, see table below for details) the EHR SHALL ensure that the code_verifier is present and valid in Step 3 (“App exchanges authorization code for access token”), at the completion of the OAuth flow.

Parameters
response_type required Fixed value: code.
client_id required The client's identifier.
redirect_uri required Must match one of the client's pre-registered redirect URIs.
launch optional When using the EHR launch flow, this must match the launch value received from the EHR.
scope required Must describe the access that the app needs, including clinical data scopes like patient/*.read, openid and fhirUser (if app needs authenticated patient identity) and either:
  • a launch value indicating that the app wants to receive already-established launch context details from the EHR
  • a set of launch context requirements in the form launch/patient, which asks the EHR to establish context on your behalf.
See SMART on FHIR Access Scopes details.
state required An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHALL be used for preventing cross-site request forgery or session fixation attacks.
aud required URL of the EHR resource server from which the app wishes to retrieve FHIR data. This parameter prevents leaking a genuine bearer token to a counterfeit resource server. (Note: in the case of an EHR launch flow, this aud value is the same as the launch's iss value.)
code_challenge conditional This parameter is generated by the app and used for PKCE. This is the S256 hashed version of the code_verifier parameter, which will be used in the token request. All apps SHOULD implement PKCE support; see Considerations for PKCE Support above for additional requirements.
code_challenge_method conditional This parameter is required if an app is using PKCE and indicates the method used for the code_challenge parameter. Fixed value: S256.

The app SHALL use an unpredictable value for the state parameter with at least 122 bits of entropy (e.g., a properly configured random uuid is suitable). The app SHALL validate the value of the state parameter upon return to the redirect URL and SHALL ensure that the state value is securely tied to the user’s current session (e.g., by relating the state value to a session identifier issued by the app). The app SHOULD limit the grants, scope, and period of time requested to the minimum necessary.

If the app needs to authenticate the identity of the end-user, it should include two OpenID Connect scopes: openid and fhirUser. When these scopes are requested, and the request is granted, the app will receive an id_token along with the access token. For full details, see SMART launch context parameters.

The following requirements are adopted from OpenID Connect Core 1.0 Specification section 3.1.2.1:

  • Authorization Servers SHALL support the use of the HTTP GET and POST methods at the Authorization Endpoint.
  • Clients MAY use the HTTP GET or POST methods to send the Authorization Request to the Authorization Server. If using the HTTP GET method, the request parameters are serialized using URI Query String Serialization. If using the HTTP POST method, the request parameters are serialized using Form Serialization and the application/x-www-form-urlencoded content type.
For example

If an app needs demographics and observations for a single patient, and also wants information about the current logged-in user, the app can request:

  • patient/Patient.read
  • patient/Observation.read
  • openid fhirUser

If the app was launched from an EHR, the app adds a launch scope and a launch={launch id} URL parameter, echoing the value it received from the EHR to be associated with the EHR context of this launch notification.

Apps using the standalone launch flow won’t have a launch id at this point. These apps can declare launch context requirements by adding specific scopes to the authorization request: for example, launch/patient to indicate that the app needs a patient ID, or launch/encounter to indicate it needs an encounter. The EHR’s “authorize” endpoint will take care of acquiring the context it needs (making it available to the app).
For example, if your app needs patient context, the EHR may provide the end-user with a patient selection widget. For full details, see SMART launch context parameters.

The app then causes the browser to navigate the browser to the EHR’s authorization URL as determined above. For example to cause the browser to issue a GET:

Location: https://ehr/authorize?
            response_type=code&
            client_id=app-client-id&
            redirect_uri=https%3A%2F%2Fapp%2Fafter-auth&
            launch=xyz123&
            scope=launch+patient%2FObservation.read+patient%2FPatient.read+openid+fhirUser&
            state=98wrghuwuogerg97&
            aud=https://ehr/fhir

Alternatively, the following example shows one way for a client app to cause the browser to issue a POST, using HTML and javascript:

<html>
  <body onload="javascript:document.forms[0].submit()">
    <form method="post" action="https://ehr/authorize">
      <input type="hidden" name="response_type" value="code"/>
      <input type="hidden" name="client_id" value="app-client-id"/>
      <input type="hidden" name="redirect_uri" value="https://app/after-auth"/>
      <input type="hidden" name="launch" value="xyz123"/>
      <input type="hidden" name="scope" value="launch patient/Observation.read patient/Patient.read openid fhirUser"/>
      <input type="hidden" name="state" value="98wrghuwuogerg97"/>
      <input type="hidden" name="aud" value="https://ehr/fhir"/>
    </form>
  </body>
</html>

Step-2: EHR evaluates authorization request, asking for end-user input

The authorization decision is up to the EHR authorization server, which may request authorization from the end-user. The EHR authorization server will enforce access rules based on local policies and optionally direct end-user input.

The EHR decides whether to grant or deny access. This decision is communicated to the app when the EHR authorization server returns an authorization code (or, if denying access, an error response). Authorization codes are short-lived, usually expiring within around one minute. The code is sent when the EHR authorization server causes the browser to navigate to the app’s redirect_uri, with the following URL parameters:

Parameters
code required The authorization code generated by the authorization server. The authorization code *must* expire shortly after it is issued to mitigate the risk of leaks.
state required The exact value received from the client.
For example

Based on the client_id, current EHR user, configured policy, and perhaps direct user input, the EHR makes a decision to approve or deny access. This decision is communicated to the app by causing the browser to navigate to the app’s registered redirect_uri. For example:

Location: https://app/after-auth?
  code=123abc&
  state=98wrghuwuogerg97

Step-3: App exchanges authorization code for access token

After obtaining an authorization code, the app trades the code for an access token via HTTP POST to the EHR authorization server’s token endpoint URL, using content-type application/x-www-form-urlencoded, as described in section 4.1.3 of RFC6749.

For public apps, authentication is not possible (and thus not required), since a client with no secret cannot prove its identity when it issues a call. (The end-to-end system can still be secure because the client comes from a known, https protected endpoint specified and enforced by the redirect uri.) For confidential apps, authentication is required.

(Note that conformance requirements determining support for these two methods of client authentication are still under consideration; tentative guidance is that clients SHOULD register for JWT assertion authentication and MAY instead register for Client Password authentication.)

  • If a client has registered for Client Password authentication (i.e., it possesses a client_secret that is also known to the EHR), the client authenticates using an Authorization header with HTTP Basic authentication, where the username is the app’s client_id and the password is the app’s client_secret (see example).

  • If a client has registered for JWT assertion-based authentication (i.e., it possesses a public/private keypairn whose public key is known to the EHR), the client authenticates using two parameters: client_assertion_type and client_assertion, as profiled in SMART Backend Services Protocol Details (see example).

Parameters
grant_type required Fixed value: authorization_code
code required Code that the app received from the authorization server
redirect_uri required The same redirect_uri used in the initial authorization request
client_id conditional Required for public apps. Omit for confidential apps.
code_verifier conditional Required for PKCE. This parameter is used to verify against the code_challenge parameter previously provided in the authorize request.

The EHR authorization server SHALL return a JSON object that includes an access token or a message indicating that the authorization request has been denied. The JSON structure includes the following parameters:

Parameters
access_token required The access token issued by the authorization server
token_type required Fixed value: Bearer
expires_in recommended Lifetime in seconds of the access token, after which the token SHALL NOT be accepted by the resource server
scope required Scope of access authorized. Note that this can be different from the scopes requested by the app.
id_token optional Authenticated patient identity and user details, if requested
refresh_token optional Token that can be used to obtain a new access token, using the same or a subset of the original authorization grants

In addition, if the app was launched from within a patient context, parameters to communicate the context values MAY BE included. For example, a parameter like "patient": "123" would indicate the FHIR resource https://[fhir-base]/Patient/123. Other context parameters may also be available. For full details see SMART launch context parameters.

The parameters are included in the entity-body of the HTTP response, as described in section 5.1 of RFC6749.

The access token is a string of characters as defined in RFC6749 and RFC6750. The token is essentially a private message that the authorization server passes to the FHIR Resource Server, telling the FHIR server that the “message bearer” has been authorized to access the specified resources.
Defining the format and content of the access token is left up to the organization that issues the access token and holds the requested resource.

The authorization server’s response SHALL include the HTTP “Cache-Control” response header field with a value of “no-store,” as well as the “Pragma” response header field with a value of “no-cache.”

The EHR authorization server decides what expires_in value to assign to an access token and whether to issue a refresh token, as defined in section 1.5 of RFC6749, along with the access token. If the app receives a refresh token along with the access token, it can exchange this refresh token for a new access token when the current access token expires (see step 5 below). A refresh token SHALL BE bound to the same client_id and SHALL contain the same, or a subset of, the set of claims authorized for the access token with which it is associated.

Apps SHOULD store tokens in app-specific storage locations only, not in system-wide-discoverable locations. Access tokens SHOULD have a valid lifetime no greater than one hour. Confidential clients may be issued longer-lived tokens than public clients.

A large range of threats to access tokens can be mitigated by digitally signing the token as specified in RFC7515 or by using a Message Authentication Code (MAC) instead. Alternatively, an access token can contain a reference to authorization information, rather than encoding the information directly into the token itself.
To be effective, such references must be infeasible for an attacker to guess. Using a reference may require an extra interaction between the resource server and the authorization server; the mechanics of such an interaction are not defined by this specification.

For example

Given an authorization code, the app trades it for an access token via HTTP POST.

Request for

POST /token HTTP/1.1
Host: ehr
Authorization: Basic bXktYXBwOm15LWFwcC1zZWNyZXQtMTIz
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=123abc&
redirect_uri=https%3A%2F%2Fapp%2Fafter-auth

Response

{
  "access_token": "i8hweunweunweofiwweoijewiwe",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "patient/Observation.read patient/Patient.read",
  "intent": "client-ui-name",
  "patient":  "123",
  "encounter": "456"
}

See full payload example.

At this point, the authorization flow is complete. Follow steps below to work with data and refresh access tokens, as shown in the following sequence diagram.

SMART retrieval and refresh sequence

Step 4: App accesses clinical data via FHIR API

With a valid access token, the app can access protected EHR data by issuing a FHIR API call to the FHIR endpoint on the EHR’s resource server. The request includes an Authorization header that presents the access_token as a “Bearer” token:

Authorization: Bearer {{access_token}}

(Note that in a real request, {{access_token}} is replaced with the actual token value.)

For example

With this response, the app knows which patient is in-context, and has an OAuth2 bearer-type access token that can be used to fetch clinical data:

Request

GET https://ehr/fhir/Patient/123
Authorization: Bearer i8hweunweunweofiwweoijewiwe

Response

{
  "resourceType": "Patient",
  "birthTime": ...
}

See full payload example.

The resource server SHALL validate the access token and ensure that it has not expired and that its scope covers the requested resource. The resource server also validates that the aud parameter associated with the authorization (see above) matches the resource server’s own FHIR endpoint. The method used by the EHR to validate the access token is beyond the scope of this specification but generally involves an interaction or coordination between the EHR’s resource server and the authorization server.

On occasion, an app may receive a FHIR resource that contains a “reference” to a resource hosted on a different resource server. The app SHOULD NOT blindly follow such references and send along its access_token, as the token may be subject to potential theft. The app SHOULD either ignore the reference, or initiate a new request for access to that resource.

Step 5: (Later…) App uses a refresh token to obtain a new access token

Refresh tokens are issued to enable sessions to last longer than the validity period of an access token. The app can use the expires_in field from the token response (see step 3) to determine when its access token will expire. EHR implementers are also encouraged to consider using the OAuth 2.0 Token Introspection Protocol to provide an introspection endpoint that clients can use to examine the validity and meaning of tokens. An app with “online access” can continue to get new access tokens as long as the end-user remains online. Apps with “offline access” can continue to get new access tokens without the user being interactively engaged for cases where an application should have long-term access extending beyond the time when a user is still interacting with the client.

The app requests a refresh token in its authorization request via the online_access or offline_access scope (see SMART on FHIR Access Scopes for details). A server can decide which client types (public or confidential) are eligible for offline access and able to receive a refresh token. If granted, the EHR supplies a refresh_token in the token response. After an access token expires, the app requests a new access token by providing its refresh token to the EHR’s token endpoint. An HTTP POST transaction is made to the EHR authorization server’s token URL, with content-type application/x-www-form-urlencoded. The decision about how long the refresh token lasts is determined by a mechanism that the server chooses. For clients with online access, the goal is to ensure that the user is still online.

  • For public apps, authentication is not possible (and thus not required). For confidential apps, see authentication considerations in step 3.

The following request parameters are defined:

Parameters
grant_type required Fixed value: refresh_token.
refresh_token required The refresh token from a prior authorization response
scope optional The scopes of access requested. If present, this value must be a strict sub-set of the scopes granted in the original launch (no new permissions can be obtained at refresh time). A missing value indicates a request for the same scopes granted in the original launch.

The response is a JSON object containing a new access token, with the following claims:

JSON Object property name
access_token required New access token issued by the authorization server.
token_type required Fixed value: bearer
expires_in required The lifetime in seconds of the access token. For example, the value 3600 denotes that the access token will expire in one hour from the time the response was generated.
scope required Scope of access authorized. Note that this will be the same as the scope of the original access token, and it can be different from the scopes requested by the app.
refresh_token optional The refresh token issued by the authorization server. If present, the app should discard any previous refresh_token associated with this launch, replacing it with this new value.

In addition, if the app was launched from within a patient context, parameters to communicate the context values MAY BE included. For example, a parameter like "patient": "123" would indicate the FHIR resource https://[fhir-base]/Patient/123. Other context parameters may also be available. For full details see SMART launch context parameters.

For example

If the EHR supports refresh tokens, an app may be able to replace an expired access token programatically, without user interaction:

Request

POST /token HTTP/1.1
Host: ehr
Authorization: Basic bXktYXBwOm15LWFwcC1zZWNyZXQtMTIz
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=a47txjiipgxkvohibvsm

Response

{
  "access_token": "m7rt6i7s9nuxkjvi8vsx",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "patient/Observation.read patient/Patient.read",
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}

See full payload example.