This page is part of the Smart App Launch Implementation Guide (v2.1.0: STU 2.1) based on FHIR R4. This is the current published version in its permanent home (it will always be available at this URL). For a full list of available versions, see the Directory of published versions
SMART on FHIR’s authorization scheme uses OAuth scopes to communicate (and negotiate) access requirements. Providing apps with access to broad data sets is consistent with current common practices (e.g., interface engines also provide access to broad data sets); access is also limited based on the privileges of the user in context. In general, we use scopes for three kinds of data:
Launch context is a negotiation where a client asks for specific launch context
parameters (e.g., launch/patient
). A server can decide which launch context
parameters to provide, using the client’s request as an input into the decision
process. See “scopes for requesting context data” for details.
Here is a quick overview of the most commonly used scopes. The complete details are provided in the following sections.
Scope | Grants |
---|---|
patient/*.rs |
Permission to read and search any resource for the current patient (see notes on wildcard scopes below). |
user/*.cruds |
Permission to read and write all resources that the current user can access (see notes on wildcard scopes below). |
openid fhirUser |
Permission to retrieve information about the current logged-in user. |
launch |
Permission to obtain launch context when app is launched from an EHR. |
launch/patient |
When launching outside the EHR, ask for a patient to be selected at launch time. |
offline_access |
Request a refresh_token that can be used to obtain a new access token to replace an expired one, even after the end-user no longer is online after the access token expires. |
online_access |
Request a refresh_token that can be used to obtain a new access token to replace an expired one, and that will be usable for as long as the end-user remains online. |
SMART’s scopes allow a client to request the delegation of a specific set of access rights; such rights are always limited by underlying system policies and permissions.
For example:
user/*.cruds
and is granted these scopes by a user, these scopes convey “full access” relative to the user’s underlying permissions. If the underlying user has limited permissions, the client will face these same limitations.system/*.cruds
, these scopes convey “full access” relative to a server’s pre-configured client-specific policy. If the pre-configured policy imposes limited permissions, the client will face these same limitations.Neither SMART on FHIR nor the FHIR Core specification provide a way to model the “underlying” permissions at play here; this is a lower-level responsibility in the access control stack. As such, clients can attempt to perform FHIR operations based on the scopes they are granted — but depending on the details of the underlying permission system (e.g., the permissions of the approving user and/or permissions assigned in a client-specific policy) these requests may be rejected, or results may be omitted from responses.
For instance, a client may receive:
200 OK
response to a search interaction that appears to be allowed by the granted scopes, but where results have been omitted from the response Bundle.403 Forbidden
response to a write interaction that appears to be allowed by the granted scopes.Applications reading may receive results that have been filtered or redacted based on the underlying permissions of the delegating authority, or may be refused access (see guidance at https://hl7.org/fhir/security.html#AccessDenied).
SMART on FHIR defines OAuth2 access scopes that correspond directly to FHIR resource types. These scopes impact the access an application may have to FHIR resources (and actions). We define permissions to support the following FHIR REST API interactions:
c
for create
r
for read
u
for update
d
for delete
s
for search
Valid suffixes are a subset of the in-order string .cruds
. For example, to convey support for creating and updating observations, use scope patient/Observation.cu
. To convey support for reading and searching observations, use scope patient/Observation.rs
. For backwards compatibility with scopes defined in the SMART App Launch 1.0 specification, servers SHOULD advertise the permission-v1
capability in their .well-known/smart-configuration
discovery document, SHOULD return v1 scopes when v1 scopes are requested and granted, and SHOULD process v1 scopes with the following semantics in v2:
.read
⇒ v2 .rs
.write
⇒ v2 .cud
.*
⇒ v2 .cruds
Scope requests with undefined or out of order interactions MAY be ignored, replaced with server default scopes, or rejected. For example, a request of .dus
is not a defined scope request. This policy is to prevent misinterpretation of scopes with other conventions (e.g., interpreting .read
as .rd
and granting extraneous delete permissions).
SMART 2.0 does not define specific scopes for batch or transaction interactions. These system-level interactions are simply convenience wrappers for other interactions. As such, batch and transaction requests should be validated based on the actual requests within them.
Scopes can be combined to represent a union of access. For example, “patient/Condition.rs patient/AllergyIntolerance.rs” expresses access to the Conditions and Allergies associated with the in-context patient. Similarly, “Observation.rs” expresses access equivalent to “Observation.r Observation.s”. In order to reduce token size, it is recommended that scopes be factored to their shortest form.
In SMART 1.0, scopes were based entirely on FHIR Resource types, as in patient/Observation.read
(for Observations) or patient.Immunization.read
(for Immunizations). In SMART 2.0, we provide more detailed constraints based on FHIR REST API search parameter syntax. To apply these constraints, add a query string suffix to existing scopes, starting with ?
and followed by a series of param=value
items separated by &
. For example, to request read and search access to laboratory observations but not other observations, the scope patient/Observation.rs?category=http://terminology.hl7.org/CodeSystem/observation-category|laboratory
.
Because the search parameter based syntax here is quite general, it opens up the possibility of using many features that servers may have trouble supporting in a consistent and performant fashion. Given the current level of implementation experience, the following features should be considered experimental, even if they are supported by a server:
Observation.rs?code:in=http://valueset.example.org/ValueSet/diabetes-codes
Observation.rs?patient.birthdate=1990
_filter
capabilitiesScope strings appear over the wire at several points in an OAuth flow. Implementers should be aware that fine-grained controls can lead to a proliferation of scopes, increasing in the length of the scope
string for app authorizations. As such, implementers should take care to avoid putting arbitrarily large scope strings in places where they might not “fit”. The following considerations apply, presented in the sequential order of a SMART App Launch:
Expressed as a railroad diagram, the scope language is:
Patient-specific scopes allow access to specific data about a single patient.
Which patient is not specified here: FHIR Resource
scopes are all about what and not who which is handled in the next section.
Patient-specific scopes start with patient/
.
Note that some EHRs may not enable access to all related resources (for
example, Practitioners linked to/from Patient-specific resources).
Note that if a FHIR server supports linking one Patient record with another
via Patient.link
, the server documentation SHALL describe its authorization
behavior.
Several examples are shown below:
Goal | Scope | Notes |
---|---|---|
Read and search for all observations about a patient | patient/Observation.rs |
|
Read demographics about a patient | patient/Patient.r |
Note the difference in capitalization between “patient” the permission type and “Patient” the resource. |
Add new blood pressure readings for a patient | patient/Observation.c |
Note that the permission is broader than the goal: with this scope, an app can add not only blood pressures, but other observations as well. Note also that write access does not imply read access. |
Read all available data about a patient | patient/*.cruds |
See notes on wildcard scopes below. |
User-level scopes allow access to specific data that a user can access. Note
that this isn’t just data about the user; it’s data available to that user.
User-level scopes start with user/
.
Several examples are shown below:
Goal | Scope | Notes |
---|---|---|
Read a feed of all new lab observations across a patient population | user/Observation.rs |
|
Manage all appointments to which the authorizing user has access | user/Appointment.cruds |
Individual attributes such as d for delete could be removed if not required. |
Manage all resources on behalf of the authorizing user | user/*.cruds |
|
Select a patient | user/Patient.rs |
Allows the client app to select a patient. |
System-level scopes describe data that a client system is directly authorized
to access; these scopes are useful in cases where there is no user in the loop,
such as a data monitoring or reporting service. System-level scopes start with
system/
.
Several examples are shown below:
Goal | Scope | Notes |
---|---|---|
Alert engine to monitor all lab observations in a health system | system/Observation.rs |
Read-only access to observations. |
Perform bulk data export across all available data within a FHIR server | system/*.rs |
Full read/search for all resources. |
System-level bridge, turning a V2 ADT feed into FHIR Encounter resources | system/Encounter.cud |
Write access to Encounters. |
As noted previously, clients can request FHIR Resource scopes that contain a wildcard (*
) for the FHIR resource. When a wildcard is requested for the FHIR resource, the client is asking for all data for all available FHIR resources, both now and in the future. This is an important distinction to understand, especially for the entity responsible for granting authorization requests from clients.
For instance, imagine a FHIR server that today just exposes the Patient resource. The authorization server asking a patient to authorize a SMART app requesting patient/*.cruds
should inform the user that they are being asked to grant this SMART app access to not just the currently accessible data about them (patient demographics), but also any additional data the FHIR server may be enhanced to expose in the future (e.g., genetics).
As with any requested scope, the scopes ultimately granted by the authorization server may differ from the scopes requested by the client! This is often true when dealing with wildcard FHIR Resource scope requests.
As a best practice, clients should examine the granted scopes by the authorization server and respond accordingly. Failure to do so may lead to situations where the client receives an authorization failure by the FHIR server because it attempted to access FHIR resources beyond the granted scopes.
For example, consider a client with the goal of obtaining read and write access to a patient’s allergies. If this client requests the FHIR Resource scope of patient/AllergyIntolerance.cruds
, the authorization server may respond in a variety of ways with respect to the scopes that are ultimately granted. The following table outlines several, but not an exhaustive list of scenarios for this example:
Granted Scope | Notes |
---|---|
patient/AllergyIntolerance.cruds |
The client was granted exactly what it requested: patient-level read and write access to allergies via the same requested wildcard scope. |
patient/AllergyIntolerance.rs patient/AllergyIntolerance.cud |
The client was granted exactly what it requested: patient-level CRUDS access to allergies. However, note that this was communicated via two explicit scopes rather than a single scope. |
patient/AllergyIntolerance.rs |
The client was granted just patient-level read access to allergies. |
patient/AllergyIntolerance.cud |
The client was granted just patient-level write access to allergies. |
patient/*.rs |
The client was granted read access to all data on the patient. |
patient/*.cruds |
The client was granted its requested scopes as well as read/write access to all other data on the patient. |
patient/Observation.rs |
The client was granted an entirely different scope: patient-level read access to the patient’s observations. While this behavior is unlikely for a production quality authorization server, this scenario is technically possible. |
"" (empty scope string – no scopes granted) |
The authorization server chose to not grant any of the requested scopes. |
As a best practice, clients are encouraged to request only the scopes and permissions they need to function and avoid the use of wildcard scopes purely for the sake of convenience. For instance, if your allergy management app requires patient-level read and write access to allergies, requesting the patient/AllergyIntolerance.cruds
scope is acceptable. However, if your app only requires access to read allergies, requesting a scope of patient/AllergyIntolerance.rs
would be more appropriate.
These scopes affect what context parameters will be provided in the access token response. Many apps rely on contextual data from the EHR to answer questions like:
To request access to such details, an app asks for “launch context” scopes in
addition to whatever FHIR Resource access scopes it needs. Launch context scopes are
easy to tell apart from FHIR Resource scopes, because they always begin with
launch
.
There are two general approaches to asking for launch context data depending on the details of how your app is launched.
Apps that launch from the EHR will be passed an explicit URL parameter called
launch
, whose value must associate the app’s
authorization request with the current EHR session. For example, If an app receives the URL
parameter launch=abc123
, then it requests the scope launch
and provides an
additional URL parameter of launch=abc123
.
The application could choose to also provide launch/patient
,
launch/encounter
, or other launch/
scopes as “hints” regarding which
contexts the app would like the EHR to gather. The EHR MAY ignore these hints
(for example, if the user is in a workflow where these contexts do not exist).
If an application requests a FHIR Resource scope which is restricted to a single patient (e.g., patient/*.rs
), and the authorization results in the EHR is granting that scope, the EHR SHALL establish a patient in context. The EHR MAY refuse authorization requests including patient/
that do not also include a valid launch
, or it MAY infer the launch/patient
scope.
Standalone apps that launch outside the EHR do not have any EHR context at the outset. These apps must explicitly request EHR context. The EHR SHOULD provide the requested context if requested by the following scopes, unless otherwise noted.
Requested Scope | Meaning |
---|---|
launch/patient |
Need patient context at launch time (FHIR Patient resource). See note below. |
launch/encounter |
Need encounter context at launch time (FHIR Encounter resource). |
(Others) | This list can be extended by any SMART EHR to support additional context. When specifying resource types, convert the type names to all lowercase (e.g., launch/diagnosticreport ). |
Note on launch/patient
: If an application requests a scope which is restricted to a single patient (e.g., patient/*.rs
), and the authorization results in the EHR granting that scope, the EHR SHALL establish a patient in context. The EHR MAY refuse authorization requests including patient/
that do not also include a valid launch/patient
scope, or it MAY infer the launch/patient
scope.
access_token
Once an app is authorized, the token response will include any context data the
app requested and any (potentially) unsolicited context data the EHR may
decide to communicate. For example, EHRs may use launch context to communicate
UX and UI expectations to the app (see need_patient_banner
below).
Launch context parameters come alongside the access token. They will appear as JSON parameters:
{
"access_token": "secret-xyz",
"patient": "123",
"fhirContext": [{"reference": "DiagnosticReport/123"}, {"reference": "Organization/789"}],
//...
}
Some common launch context parameters are shown below. The following sections provides further details:
Launch context parameter | Example value | Meaning |
---|---|---|
patient |
"123" |
String value with a patient id, indicating that the app was launched in the context of FHIR Patient 123. If the app has any patient-level scopes, they will be scoped to Patient 123. |
encounter |
"123" |
String value with an encounter id, indicating that the app was launched in the context of FHIR Encounter 123. |
fhirContext |
[{"reference": "Appointment/123"}] |
Array of objects referring to any resource type other than “Patient” or “Encounter”. See details below. |
need_patient_banner |
true or false (boolean) |
Boolean value indicating whether the app was launched in a UX context where a patient banner is required (when true ) or may not be required (when false ). An app receiving a value of false might not need to take up screen real estate displaying a patient banner. |
intent |
"reconcile-medications" |
String value describing the intent of the application launch (see notes below) |
smart_style_url |
"https://ehr/styles/smart_v1.json" |
String URL where the EHR’s style parameters can be retrieved (for apps that support styling) |
tenant |
"2ddd6c3a-8e9a-44c6-a305-52111ad302a2" |
String conveying an opaque identifier for the healthcare organization that is launching the app. This parameter is intended primarily to support EHR Launch scenarios. |
fhirContext
To allow application flexibility, maintain backwards compatibility, and keep a
predictable JSON structure, any contextual resource types that were requested
by a launch scope will appear in the fhirContext
array. The Patient and
Encounter resource types will not be deprecated from top-level parameters,
and they will not be permitted within the fhirContext
array unless they
include a role
other than "launch"
.
Each object in the fhirContext
array SHALL have a reference
property with
a string value containing a relative reference to a FHIR resource. Note that
there MAY be more than one Reference to a given type of resource
Each object in the fhirContext
array MAY have a role
property with a
string value containing a URI identifying the role. The role
property is
OPTIONAL; it MAY be omitted and SHALL NOT be the empty string. Relative URIs
can only be used if they are defined in this specification; other roles require
the use of absolute URIs. The absence of a role property is semantically
equivalent to a role of "launch"
, indicating to a client that the app launch
was performed in the context of the referenced resource. More granular role
URIs can be adopted in use-case-specific ways. Note that role
need not be
unique; multiple entries in fhirContext
may have the same role.
fhirContext
example: EHR Launch with Imaging StudyIf a SMART on FHIR server supports additional launch context during an EHR
Launch, it could communicate the ID of an ImagingStudy
that is open in the
EHR at the time of app launch. The server could return an access token response
where the fhirContext
array includes a value such as {"reference": "ImagingStudy/123"}
.
fhirContext
example: Standalone Launch with Imaging StudyIf a SMART on FHIR server supports additional launch context during a
Standalone Launch, it could provide an ability for the user to select an
ImagingStudy
during the launch. A client could request this behavior by
requesting a launch/imagingstudy
scope (note that launch requests scopes are
always lower case); then after allowing the user to select an ImagingStudy
,
the server could return an access token response where the fhirContext
array
includes a value such as {"reference": "ImagingStudy/123"}
.
fhirContext
example: Medication ReconciliationIf a medication reconciliation app expects distinct contextual inputs
representing an at-home medication list and an in-hospital medication list, the
EHR might supply fhirContext
like:
{
// other properties omitted for brevity
"patient": "123",
"fhirContext": [{
"reference": "List/123",
"role": "https://example.org/med-list-at-home"
}, {
"reference": "List/456",
"role": "https://example.org/med-list-at-hospital"
}]
}
intent
: Some SMART apps might offer more than one context or user interface
that can be accessed during the SMART launch. The optional intent
parameter
in the launch context provides a mechanism for the SMART EHR to communicate to
the client app which specific context should be displayed as the outcome of the
launch. This allows for closer integration between the EHR and client, so that
different launch points in the EHR UI can target specific displays within the
client app.
For example, a patient timeline app might provide three specific UI contexts,
and inform the SMART EHR (out of band, at app configuration time) of the
intent
values that can be used to launch the app directly into one of the
three contexts. The app might respond to intent
values like:
summary-timeline-view
- A default UI context, showing a data summaryrecent-history-timeline
- A history display, showing a list of entriesencounter-focused-timeline
- A timeline focused on the currently in-context encounterIf a SMART EHR provides a value that the client does not recognize, or does not provide a value, the client app SHOULD display a default application UI context.
Note that SMART makes no effort to standardize intent
values. Intents simply
provide a mechanism for tighter custom integration between an app and a SMART
EHR. The meaning of intent values must be negotiated between the app and the EHR.
smart_style_url
: In order to mimic the style of the SMART EHR more closely,
SMART apps can check for the existence of this launch context parameter and, if provided,
download the JSON file referenced by the URL value.
The URL SHOULD serve a “SMART Style” JSON object with one or more of the following properties:
{
color_background: "#edeae3",
color_error: "#9e2d2d",
color_highlight: "#69b5ce",
color_modal_backdrop: "",
color_success: "#498e49",
color_text: "#303030",
dim_border_radius: "6px",
dim_font_size: "13px",
dim_spacing_size: "20px",
font_family_body: "Georgia, Times, 'Times New Roman', serif",
font_family_heading: "'HelveticaNeue-Light', Helvetica, Arial, 'Lucida Grande', sans-serif;"
}
The URL value itself is to be considered a version key for the contents of the SMART Style JSON:
EHRs SHALL return a new URL value in the smart_style_url
launch context parameter if the contents
of this JSON is changed.
Style Property | Description |
---|---|
color_background |
The color used as the background of the app. |
color_error |
The color used when UI elements need to indicate an area or item of concern or dangerous action, such as a button to be used to delete an item, or a display an error message. |
color_highlight |
The color used when UI elements need to indicate an area or item of focus, such as a button used to submit a form, or a loading indicator. |
color_modal_backdrop |
The color used when displaying a backdrop behind a modal dialog or window. |
color_success |
The color used when UI elements need to indicate a positive outcome, such as a notice that an action was completed successfully. |
color_text |
The color used for body text in the app. |
dim_border_radius |
The base corner radius used for UI element borders (0px results in square corners). |
dim_font_size |
The base size of body text displayed in the app. |
dim_spacing_size |
The base dimension used to space UI elements. |
font_family_body |
The list of typefaces to use for body text and elements. |
font_family_heading |
The list of typefaces to use for content heading text and elements. |
SMART client apps that can adjust their styles should incorporate the above property values into their stylesheets, but are not required to do so.
Optionally, if the client app detects a new version of the SMART Style object
(i.e. a new URL is returned the smart_style_url
parameter), the client can
store the new property values and request approval to use the new values from
a client app stakeholder. This allows for safeguarding against poor usability
that might occur from the immediate use of these values in the client app UI.
Some apps need to authenticate the end-user. This can be accomplished by
requesting the scope openid
. When the openid
scope is requested, apps can
also request the fhirUser
scope to obtain a FHIR resource representation of
the current user. Single sign-on support with fhirUser
requires that users
can be represented as FHIR resources. If the EHR cannot represent the user with
a FHIR resource, it cannot support the fhirUser
scope.
When these scopes are requested (and the request is granted), the app will
receive an id_token
that comes alongside the access token.
This token must be validated according to the OIDC specification.
To learn more about the user, the app should treat the fhirUser
claim as the
URL of a FHIR resource representing the current user. This URL MAY be absolute
(e.g., https://ehr.example.org/Practitioner/123
), or it MAY be relative to
the FHIR server base URL associated with the current authorization request
(e.g., Practitioner/123
). This will be a resource of type Patient
,
Practitioner
, PractitionerRole
, RelatedPerson
, or Person
.
Note that the FHIR server base URL is the same as the URL represented in the
aud
parameter passed in to the authorization request.
Note that Person
is only used if the other resource types do not apply to the
current user, for example, the “authorized representative” for >1 patients.
The OpenID Connect Core specification
describes a wide surface area with many optional capabilities. To be considered compatible
with the SMART’s sso-openid-connect
capability, the following requirements apply:
Response types: The EHR SHALL support the Authorization Code Flow, with the request parameters as defined in SMART App Launch. Support is not required for parameters that OIDC lists as optional (e.g., id_token_hint
, acr_value
), but EHRs are encouraged to review these optional parameters.
Public Keys Published as Bare JWK Keys: The EHR SHALL publish public keys as bare JWK keys (which MAY also be accompanied by X.509 representations of those keys).
Claims: The EHR SHALL support the inclusion of SMART’s fhirUser
claim within the id_token
issued for any requests that grant the openid
and fhirUser
scopes.
Signed ID Token: The EHR SHALL support Signing ID Tokens with RSA SHA-256.
A SMART app SHALL NOT pass the auth_time
claim or max_age
parameter to a server that does not support receiving them.
Servers MAY include support for the following features:
claims
parameters on the authorization requestTo request a refresh_token
that can be used to obtain a new access token
after the current access token expires, add one of the following scopes:
Scope | Grants |
---|---|
online_access |
Request a refresh_token that can be used to obtain a new access token to replace an expired one, and that will be usable for as long as the end-user remains online. |
offline_access |
Request a refresh_token that can be used to obtain a new access token to replace an expired token, and that will remain usable for as long as the authorization server and end-user will allow, regardless of whether the end-user is online. |
In addition to conveying FHIR Resource references with the fhirContext
array, additional context parameters and scopes can be used as extensions using the following namespace conventions:
__
(two underscores)fhirContext
for FHIR Resource ReferencesSee Section fhirContext
Examples.
If a SMART on FHIR server wishes to communicate additional context (such
as a custom “dark mode” flag to provide clients a hint about whether they
should render a UI suitable for use in low-light environments), it could
accomplish this by returning an access token response where an extension
property is present. The server could choose an extension property as a full URL
(e.g., {..., "https://ehr.example.org/props/dark-mode": true}
) or by using a
"__"
prefix (e.g., {..., "__darkMode": true}
).
If a SMART on FHIR server supports a custom behavior like allowing users
to choose their own profile photos through a custom non-FHIR API, it
can designate a custom scope using a full URL (e.g.,
https://ehr.example.org/scopes/profilePhoto.manage
) or by using a "__"
prefix (e.g., __profilePhoto.manage
). The server could advertise this scope in its developer-facing
documentation, and also in the scopes_supported
array of its
.well-known/smart-configuration
file. Clients requesting authorization could
include this scope alongside other standardized scopes, so the scope
parameter of the authorization request might look like:
launch/patient patient/*.rs __profilePhoto.manage
. If the user grants these
scopes, the access token response would then include a scope
value that
matches the original request.
GET {issuer}/.well-known/openid-configuration
fhirUser
claim and treat it as the URL of a FHIR resourceIn some circumstances, scopes must be represented as URIs. For example, when exchanging what scopes users are allowed to have, or sharing what scopes a user has chosen. When URI representations are required, the SMART scopes SHALL be prefixed with http://smarthealthit.org/fhir/scopes/
, so that a patient/*.r
scope would be http://smarthealthit.org/fhir/scopes/patient/*.r
.
To represent OpenID scopes as URIs, the prefix http://openid.net/specs/openid-connect-core-1_0#
SHALL be used.
Section is marked as “experimental” to indicate that there may be future backwards-incompatible changes to the style document pointed to by the smart_style_url
. ↩