Smile CDR v2024.05.PRE
On this page:

16.2.1Authorization Flows

 

There are a number of different flows that can be used for different purposes. Many of these flows share some similarities, but have nuances and tradeoffs making them useful in different scenarios.

The following list outlines different flows for different purposes. These flows are described in greater detail in the sections below.

16.2.1.1Interactive User Launch Flows

The following flows are intended for launching an application in an interactive way (e.g. the user is logging into the SMART server via an app that is requesting access to clinical data).

  • Authorization Code flow is used for end user interactive applications (i.e. applications where there is a user directly authenticating), as opposed to applications that are purely business-to-business
  • Implicit flow is a previously recommended flow for interactive applications (SPAs), mobile applications, etc.
  • Resource Owner Password Credentials flow is used when the application should have its own username and password UI and should only pass the credentials directly to the authorization server. This is appropriate only for applications that are fully trusted, since the application has access to user credentials (unlike the flows above).

16.2.1.2Non-Interactive User Flows

  • Cross-Organization Data Access Profile allows an entirely separate trusted application stack (such as a system with its own identity provider / user database) to request an access token on behalf of a user to assert the identity of that user without requiring them to log in.

16.2.1.3System Flows

  • Client Credentials flow allows a client (i.e. a system) to authorize directly without having a user (i.e. a person) named or present as a part of the authentication.
  • Refresh flow allows a server to request a new access token using a refresh token.

16.2.2Launch Flow: Authorization Code

 

This flow is the most common authorization flow, and is generally the right one to use if you have a real user (i.e. not a system) who needs to authenticate/authorize themself for access to an application.

This flow can be used by applications that are able to "keep a secret" (confidental client), as well as applications that are not (public clients). The secret referred to here is a password that is specific to the application itself (not the user).

16.2.2.0.1Confidential Clients

Examples of confidential clients include:

  • Web Applications where a secured "backend server" exists, and this backend server is able to handle the code exchange with the authorization server.
  • Mobile Applications where a secured "backend server" exists, and this backend server is able to handle the code exchange with the authorization server.

Confidential clients use a client secret to authenticate with the Authorization Server.

16.2.2.0.2Public Clients

Examples of public clients include:

  • Native Mobile Apps where the app is installed on a user's device and the app does not have a separate secure backend server (aside from the FHIR server). Storing a client secret is inappropriate because the secret would need to be stored in the on the device, and a malicious user could theoretically decompile the application and retrieve it.
  • Single Page WebApps where the entire application consists of HTML+JS code. Using a client secret is inappropriate because the User has access to the source code of these applications.

Public clients are recommended to use Proof Key for Code Exchange (PKCE) as described below. PKCE is an additional layer of security intended to compensate for the lack of a client secret.

16.2.2.0.3Launch Styles

The Launch And Authorization page within the SMART App Launch specification describes several use cases, including launching patient apps standalone ("app launch") and from a portal ("EHR launch"), and launching provider apps standalone and from a portal.

16.2.2.1Authorization Code Flow Steps

The Authorization Code flow works as follows:

Step 1: The SMART Application redirects the user to the Authorization Server (or in the case of a native application, opens a web browser) at a URL similar to the following: http://myserver:8000/oauth/authorize?response_type=code&scope=openid+profile+patient/*.read&client_id=my-client-id&state=af0ifjsldkj&aud=http://myserver:9200&redirect_uri=https://myapp.example.com/cb&launch=ABCDEFG

Note the parameters in the URL:

  • response_type – This must be set to code for the authorization code flow.
  • scope – Indicates the specific scopes that are being requested.
  • client_id – This is a unique identifier for the given client. In the Smile CDR SMART Outbound Security module, this ID corresponds to a Client Definition.
  • state – (optional) This is a random, opaque string generated by the client. This is verified later in order to prevent replay attacks. It may be omitted, but it is highly recommended to use it.
  • aud – (optional) The URL of the resource server from which the app wishes to retrieve FHIR data from. When a Allowed Resource URL list is provided, the authentication server will validate the content of the aud parameter against the allowed list. Validation is not performed when left empty.
  • redirect_uri – This is the URI (belonging to the Client application) where the user will be redirected when the flow completes.
  • prompt – (optional) This is a space-delimited, case-sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for re-authentication and consent. Current support is limited to the login value.
  • If the authorization flow is an EHR Launch (as opposed to a standalone launch), the laynch parameter which was provided to the App when the portal directed the user to the App will be provided back to the authorization server unmodified.
  • If the authorization flow is using PKCE, the url will also contain the code_challenge and code_challenge_method parameters:

Step 2: The Authorization Server presents the user with a login screen, and if necessary asks the user to approve the selected scopes.

Step 3: The Authorization Server redirects the user back to the Callback URL, adding several parameters to the URL. For example: https://myapp.example.com/cb?state=af0ifjsldkj&code=cdcd883gr3g8dggakH

The client should verify that the state code matches the state token it originally provided. This is done in order to prevent replay attacks.

The code parameter is a temporary token (an "Authorization Code") provided by the Authorization Server that the SMART Application can now use to exchange for an Access Token.

Step 4: The SMART Application invokes a REST method on the Authorization Server to exchange the Authorization Code for an Access Token. This is done using a request such as the following:

POST http://myserver:9200/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic [client ID and secret]

client_id=my-client-id&grant_type=authorization_code&code=cdcd883gr3g8dggakH&redirect_uri=https://myapp.example.com/cb&scope=openid&aud=http://myserver:8000

Note the headers in the submission above:

  • Content-Type – This must be set to application/x-www-form-urlencoded
  • Authorization – (optional) If the client is a confidential client (i.e. it has a client secret configured), this header will have the string Basic followed by a space, followed by the Base64 encoding of the string [client ID]:[client secret].

Note the parameters in the submission above:

  • client_id – This is the same Client ID used above.
  • grant_type – This is set to authorization_code.
  • code – This is the code received from the Authorization Server in the step above.
  • redirect_uri – This must match the URI above.
  • scope – This must match the scopes above.
  • If the flow is using PKCE, the code_verifier parameter should also be included.

The Authorization Server will respond with a response similar to the following, which includes the Access Token:

{
   "access_token":"eyJraWQiOiJ1.eyJhenAiOiJteS1jbGllb.Bv42OB0p",
   "id_token":"eyJra33ed8dofh.doh3ohfeisgdOiJteS1jbGllb.u44hdhB0p",
   "token_type":"bearer",
   "expires_in":59,
   "scope":"openid profile patient/*.read"
}

If the client has requested and been approved with the offline_access or online_access scopes, a Refresh Token will also be provided in the response JSON document:

{
   "access_token":"eyJraWQiOiJ1.eyJhenAiOiJteS1jbGllb.Bv42OB0p",
   "id_token":"eyJra33ed8dofh.doh3ohfeisgdOiJteS1jbGllb.u44hdhB0p",
   "token_type":"bearer",
   "expires_in":59,
   "scope":"openid profile patient/*.read",
   "refresh_token":"dd94j4hdsjrbfyf8i4ir9fizJHHfhdg"
}

16.2.3Security Option: Proof Key for Code Exchange (PKCE)

 

When using public clients with the Authorization Code flow, it is highly recommended to use the Proof Key for Code Exchange extension (also known as RFC7636/PKCE and pronounced "Pixy") in order to mitigate potential interception attacks. PKCE allows the client to add an additional token to the initial authorization request (step 1 above) and then requires the client to submit a verifier during the code exchange step (step 4 above) that can be used as additional proof that no "man in the middle" has intercepted the authorization code and is trying to maliciously redeem it.

In order to use PKCE, the client should first generate a random string to use as a challenge. For example: d93i4uggdiuf8p4or0Au (Do not use the same string shown in this example! You must generate a new random string each time PKCE is used)

The client should then generate a SHA-256 hash of this string, and Base64 encode the result. For the example above, this results in: 6VLmPKYqeh3cI/YKXLbeOLfF0SiR3/38pQC6ozldmXs=

The following additions to the steps described above are then performed.

Step 1: When the SMART Application redirects the user to the Authorization Server, two new parameters are added as shown in the following example. http://myserver:9200/oauth/authorize?response_type=code&scope=openid+profile+patient/*.read&client_id=my-client-id&state=af0ifjsldkj&redirect_uri=https://myapp.example.com/cb&code_challenge=6VLmPKYqeh3cI/YKXLbeOLfF0SiR3/38pQC6ozldmXs=&code_challenge_method=S256

Note the two extra parameters in the URL:

  • code_challenge – This is the Base64 encoded SHA256 hash of the challenge string.
  • code_challenge_method – This parameter should have a value of S256 to indicate the hashing method. Note that a value of PLAIN is also allowed, but is not recommended for use as it is less secure.

Step 4: When performing the code exchange, one new parameter is added as shown in the following example.

POST http://myserver:9200/oauth/token
Content-Type: application/x-www-form-urlencoded

client_id=my-client-id&grant_type=authorization_code&code=cdcd883gr3g8dggakH&redirect_uri=https://myapp.example.com/cb&scope=openid&code_verifier=d93i4uggdiuf8p4or0Au

Note the additional parameter:

  • code_verifier – This is the original (unhashed) challenge string.

When using the Smile CDR Outbound Security module, consider making the use of PKCE mandatory.

16.2.4Launch Flow: Implicit Grant

 
The Implicit Grant flow is fully supported in Smile CDR, but it is generally not recommended for use by many security experts. We recommend using the Authorization Code flow without a Client Secret for applications that are unable to keep a secret.

This flow is used for applications that are unable to "keep a secret". It should only be used in these cases.

Step 1: The SMART Application redirects the user to the Authorization Server (or in the case of a native application, opens a web browser) at a URL similar to the following: http://myserver:9200/oauth/authorize?response_type=id_token%20token&scope=openid+profile+patient/*.read&client_id=my-client-id&state=af0ifjsldkj&redirect_uri=https://myapp.example.com/cb

Step 2: The Authorization Server presents the user with a login screen, and if necessary asks the user to approve the selected scopes.

Step 3: The Authorization Server redirects the user back to the Callback URL, adding several parameters to the URL. For example: https://myapp.example.com/cb?state=af0ifjsldkj&access_token=cdcd883gr3g8d.ggakHeuedhd&token_type=bearer&expires_in=60

In this response the access_token parameter contains the Access Token that has been granted.

16.2.5Launch Flow: Resource Owner Password Credentials

 
This flow is dangerous to use with an app that isn't completely trusted. This is because this flow allows the Application to see the raw password that the user enters. It should generally only be used for applications that were developed by the owner of the Resource Server.

This flow relies on credentials being collected directly in the source application and then transferred to the Authorization Server for validation.

To perform the Resource Owner Password Credentials flow, the Application collects the user's username and password directly in the Application UI. It then uses an HTTP POST to the token endpoint in order to request an access token.

The following example shows a SMART Application making a request to the Authorization Server to grant a new Access Token using the Resource Owner Password Credentials flow (for readability each parameter is shown on a new line but in a real request these would be joined together in one long line):

POST /oauth/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

grant_type=password
&client_id=growth_chart
&client_secret=someclientsecret
&username=someuser
&password=someuserspassword
&scope=openid+profile+patient.%2A%2Fread

This client request must contain the client secret if the client definition has one.

The Authorization Server will respond with a response similar to the following, which includes the Access Token:

{
   "access_token":"eyJraWQiOiJ1.eyJhenAiOiJteS1jbGllb.Bv42OB0p",
   "id_token":"eyJra33ed8dofh.doh3ohfeisgdOiJteS1jbGllb.u44hdhB0p",
   "token_type":"bearer",
   "expires_in":59,
   "scope":"openid profile patient/*.read"
}

16.2.5.1Client Secrets for Resource Owner Password Credentials

If the Client definition contains a client secret, this secret must be provided during the grant process. This should only be done if the communication between the Client application and the Authorization Server is being performed using a trusted and secured backend server.

If the Client definition does not contain a client secret, this flow will be permitted without the client secret as a request parameter. Use this mode with caution as it has security implications which must be considered!

16.2.6Non-Interactive User Flow: Cross-Organization Data Access Profile

 

The SMART Cross-Organization Data Access Profile (CODAP) is useful in scenarios where data is being requested from a trusted third-party application that maintains its own identity provider and does not wish to federate at the directory level.

This flow is fundamentally different from the interactive flows in that it allows the third-party application to securely assert the identity of the requesting system and user to Smile CDR. Smile CDR will in turn issue a valid Access Token (and will create entries in its own user database in order to track audit events) but will not attempt to verify the identity of the calling user at all.

16.2.6.1Authorization and Authentication Token

The Cross-Organization Data Access Profile uses two separate tokens which are generated and signed by the third-party application. The technology for this signature involves JSON Web Tokens. For this exchange to be secure, the tokens are signed using a private key that only the third-party application has access to, and are verified by Smile CDR using a corresponding public key.

These tokens are:

  • The Authentication Token contains an assertion about the system (application) that is making the request on behalf of a user
  • The Authorization Token contains an assertion about the user (person) making the request

Note that although the specification treats these as separate tokens in order to facilitate flows where these tokens are generated in separate steps, it is also possible to generate a single token containing all relevant assertions. This can reduce the burden on implementors.

16.2.6.2CODAP Data Flow

The following diagram illustrates the Cross-Organization Data Access Profile.

CODAP Flow

This flow consists of the following steps:

  1. The user logs in

The user authenticates with the Third-Party system. This could be an EMR, HIS, web portal, etc., using whatever credentials that system supports. This flow assumes that the EHR is able to identify the user and make strong assertions about their identity. This fact is key to understanding the difference between this flow and the other SMART auth flows: for all other flows, the user/system is authenticating with Smile CDR. For this flow, the Third-Party System is simply asserting the identity of the user/system that will be making requests.

  1. The EHR Generates an Authorization Token

Per the CODAP specification, the authorization token will be a signed JWT with claims resembling the following example. Note that the claims include information about who is the user, whose data are they requesting, and why are they requesting it.

{
  "iss": "http://third-party-ehr/issuer",
  "sub": "someuser",
  "aud": "https://my-smile-server/issuer",
  "acr": "http://nist.gov/id-proofing/level/3",
  "jti": "uuid-representing-token",
  "iat": 1418698788,
  "exp": 1422568860,
  "requested_record": {
    "resourceType": "Patient",
    "name": {
      "text": "Pauline Smith"
    },
    "identifier": [{
      "system": "http://example.org/patients",
      "Identifier": "0123456789"
    }],
    "gender": "female",
    "birthDate": "1970-05-18"
  },
  "reason_for_request": "treatment",
  "requested_scopes": "patient/*.read patient/*.write",
  "requesting_practitioner": {
    "resourceType": "Practitioner",
    "identifier": [{
      "system": "http://example.org/providers",
      "value": "983736235"
    }],
    "name": {
      "text": "Juri van Gelder"
    },
    "practitionerRole": [{
      "role": {
        "coding": [{
          "system": "http://snomed.info/sct",
          "code": "36682004",
          "display": "Physical therapist"
        }]
      }
    }]
  }
}

The "requested record", which is used by the requesting EHR to identify the patient whose record is being accessed, is a simple FHIR Patient resource. The level of identification required is not a requirement of Smile CDR. For example, you may choose to require only an identifier, or a name and birth date, etc.

Similarly, the "requesting practitioner" is a FHIR Practitioner resource. The level of identification required is not a requirement of Smile CDR.

  1. Generate Authentication Token

Per the CODAP specification, the authentication token will be a signed JWT containing claims resembling the example below. Note that where the authorization token contained claims about the user and what they wanted to accomplish, this token contains information about the system actually performing the request.

{
  "iss": "http://third-party-ehr/issuer",
  "sub": "http://third-party-ehr/issuer",
  "aud": "https://my-smile-server/issuer",
  "iat": 1422568860,
  "exp": 1438698788,
  "jki": "uuid-representing-token",
  "kid": "key_id"
}

  1. Third-Party System issues Token Request

This step consists of an HTTP Post from the Third-Party Application to the CODAP Auth Server. The request will resemble the following:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&
assertion={signed authorization JWT}&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion={signed authentication JWT}

In the above example, the {signed authorization JWT} and {signed authentication JWT} should be replaced with signed versions of the JWTs described above, using a key that belongs to the Third-Party Application and known to the SMART Outbound Security Module.

  1. Invoke Callback Scripts and generate Access Token

In this step, a custom callback script is executed that validates the information supplied by the authorization token and performs any neccesary processing for the specific login workflow being implemented. This step is very flexible from implementation to implementation. For example, one implementation of Smile CDR might decide that only minimal (or no) details about the patient are required, where another implementation might decide that a complete set of demographics are required to be supplied by the Third-Party Application. This is completely implementation dependent, and is specified in the callback script.

  1. Return Access Token to Third-Party Application

Once the validation step has completed, an Access Token is generated and returned to the user. This takes the form of a standard OAuth2 token response, such as the one below.

200 OK
Content-Type: application/json

{"access_token": "iaslfhsfauel3fgterioioeg0o.dfoifsiou.ururhi"}

  1. Use Access Token

Once the Third-Party Application has an Access Token, it may be used to perform any REST calls that are authorized. A simple FHIR read operation is shown as an example below.

GET /Patient/123 HTTP/1.1
Authorization: Bearer iaslfhsfauel3fgterioioeg0o.dfoifsiou.ururhi
Accept: application/fhir+json

See the CODAP Grant section of the SMART Outbound Security module documentation for information on how to use this grant type with the built-in Authorization server in Smile CDR.

16.2.7System Flow: Client Credentials

 

The Client Credentials flow can be used to authenticate a client directly, without the involvement of any user account.

This is useful in situations where a system needs to be authorized for actions without needing to involve or authorize a user. For example, this grant type could be used for authenticating a system for system-to-system data flow.

The following example shows a SMART Application making a request to the Authorization Server to grant a new Access Token using the Client Credentials Grant (for readability each parameter is shown on a new line but in a real request these would be joined together in one long line):

POST /oauth/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=growth_chart
&client_secret=someclientsecret

The server will then respond with a response similar to the following, which includes a new Access Token (note that the Authorization Server may or may not reuse the same Refresh Token at its own discretion).

{
   "access_token":"eyJraWQiOiJ1bml0dG.eyJhenAiOiJteS1j.THbNnfuX7Ly4g",
   "token_type":"bearer",
   "expires_in":59,
   "scope":"patient/*.read"
}

See the Client Credentials Grant section of the SMART Outbound Security module documentation for information on how to use this grant type with the built-in Authorization server in Smile CDR.

When a client requests a token using the client credentials flow and does not specify any scopes, all the allowed scopes listed in the OIDC client configuration are granted to the client.

16.2.7.1Client Credentials with Basic Auth

Instead of including client credentials in the request body, SMART Application may use HTTP Basic authentication scheme to provide client_id and client_secret parameters in the Client Credentials flow. Below is an example of such request.

POST /oauth/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic [base64 of "client-id:client-secret"]

grant_type=client_credentials

16.2.8System Flow: Client Credentials with JWT Credential

 

This variant of the Client Credentials flow described above uses a cryptographically signed JWT as the credential that the client presents to the authorization server, instead of a client secret. This type of authentication is specified as the mechanism for SMART Backend Services and is described in [RFC 7523](JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants) JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants.

In this flow, the client first creates a JWT resembling the following:

{
  "jti":"cc7a3074-5c59-4e3f-b493-bb5115049344",
  "sub":"my-client-id",
  "iss":"my-client-id",
  "aud":"http:\/\/example.com\/issuer\/oauth\/token",
  "kid":"some-key-id",
  "exp":1623019405,
  "iat":1623019345
}

Note the following claims in the JWT document:

  • The sub and iss claims contain the Client ID for the client that is authenticating (this value should be identical for both claims).
  • The aud claim contains the token endpoint for the Authorization Server.

The JWT is then digitally signed, and passed to the Authorization Server in a request similar to the following:

POST /oauth/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion=[serialized JWT]

The server will then respond with a response similar to the following, which includes a new Access Token (note that the Authorization Server may or may not reuse the same Refresh Token at its own discretion).

{
   "access_token":"eyJraWQiOiJ1bml0dG.eyJhenAiOiJteS1j.THbNnfuX7Ly4g",
   "token_type":"bearer",
   "expires_in":59,
   "scope":"patient/*.read"
}

See the Client Credentials Grant section of the SMART Outbound Security module documentation for information on how to use this grant type with the built-in Authorization server in Smile CDR.

16.2.9System Flow: Refresh Token

 

The OpenID Connect flow allows for an Authorization Server to issue Access Tokens that are then consumed by Resource Servers in order to authenticate access to APIs. The design of the protocol means that this flow can generally work with minimal direct interaction needed between the Resource Server and the Authorization Server. Tokens leverage digital signatures, meaning that the Resource Server can verify that a token was issued by the Authorization Server without actually needing to communicate with it.

This is a useful property of the process as it means that the two different logical servers have less (or no) need to communicate directly with each other in order to validate individual client requests. This is good for scalability (indeed this is a common design principle in the popular Microservice Architecture application design).

However, this property does have one important drawback: it means that the Authorization Server is not able to notify the Resource Server if it wishes to revoke an access token. Because of this, the Authorization Server will often specify an access token with a short expiry time (often as short as 60 seconds or less), and will issue a second token called a Refresh Token while it is issuing an access token to the SMART Application.

This Refresh Token may be used by the SMART Application to request a new access token when the current one has expired (or perhaps beforehand). By issuing Access Tokens with a short expiry time and regularly requiring the SMART Application to request a new Access Token, the Authorization Server is able to ensure that the SMART Application only has a valid Access Token for an appropriate amount of time (in other words, the Authorization Server can effectively revoke tokens within a short amount of time).

Refresh tokens may also be used by the application to perform background tasks, such as periodic fetches of data on behalf of the user, even when the user is not actively using the client application.

16.2.9.1Obtaining a Refresh Token

A client can request that a Refresh Token be issued by requesting the offline_access or online_access scopes (see Supported SMART Scopes).

If one of these scopes is approved for the client session, the refresh token will be returned to the client during the Authorization Code flow. See the description of this flow above for an example.

16.2.9.2Using the Refresh Token

Once a client has a Refresh Token, the token can be exchanged for a new access token using the OAuth2 Refresh Flow.

The following example shows a SMART Application making a request to the Authorization Server to grant a new Access Token using a Refresh Token (for readability each parameter is shown on a new line but in a real request these would be joined together in one long line):

POST /oauth/token
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic [client_id:client_secret]

grant_type=refresh_token
&refresh_token=37d53025-964c-4ab4-afcb-54ea5d892ebb
&client_id=growth_chart

The server will then respond with a response similar to the following, which includes a new Access Token and a Refresh Token that may be used to request the next Access Token (note that the Authorization Server may or may not reuse the same Refresh Token at its own discretion).

{
   "access_token":"eyJraWQiOiJ1bml0dG.eyJhenAiOiJteS1j.THbNnfuX7Ly4g",
   "token_type":"bearer",
   "refresh_token":"c5bbdcab-2e02-4c80-bb2c-3022c66b2a7e",
   "expires_in":59,
   "scope":"patient/*.read online_access openid"
}

16.2.9.2.1Refresh Flow without Client Secret

Note that if the client definition is not marked as Client Secret Required to Authenticate, it will be possible for the client to request a new access token without requiring the use of the client secret.

In this case, the client must present a valid Access Token instead. The following example shows such a request. Note that the Client ID is not presented.

POST /oauth/token
Accept: application/json
Authorization: Bearer zddefffpof4wopwjfsf.sfsef9ef9oesjKLKLhldhdg
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=37d53025-964c-4ab4-afcb-54ea5d892ebb