16.11.1SMART on FHIR Inbound Security Module

 

The SMART Inbound Security module is a security module that authorizes client requests by validating OpenID Connect Access Tokens.

Unlike the SMART on FHIR Outbound Security Module, this module is not itself an Authorization Server; rather it assumes the existence of an external Authorization Server and validates that Access Tokens were granted by that server.

16.11.2Requirements

 

This module requires the existence of an external Authorization Server that is compliant with the SMART on FHIR Authorization Guide. Specifically, this external Authorization Server must be a compliant OpenID Connect server.

The following diagram shows the flow when using the SMART Inbound Security module to perform authorization.

SMART Inbound Security Flow

16.11.3Setting Up This Module

 

Setting up a SMART on FHIR architecture with an external Authorization Server requires both an inbound security module and one or more OpenID Connect server definitions.

16.11.3.1Creating the Module

To create a SMART Inbound Security module in the Web Admin Console:

  • Navigate to Module Config Module Config
  • Select the SMART Inbound Security module type and click Add
  • Configure the module and click Save

The SMART Inbound Security module does not require much configuration, as the process of accepting a SMART Access Token is a fairly well-defined process. The module has two properties for defining endpoint URLs: Token Endpoint and Authorization Endpoint.

These properties do not actually affect the behaviour of the inbound security module, but they will affect the CapabilityStatement exported by any FHIR endpoints that use the module.

16.11.4Accepting Tokens

 

The SMART Inbound Security module can authorize tokens in two ways:

Accepting Internal Access Tokens

16.11.5Accepting Internal Access Tokens

 

In a deployment where Smile CDR is acting as the OIDC Provider using the SMART Outbound Security module, the outbound security module is able to act as an OpenID Connect Authentication dependency to other modules (e.g. FHIR Endpoint).

In order to achieve separation of concerns, and to scale different parts of the architecture separately, the SMART Outbound Security module can be placed on a different node from the services being secured (e.g. FHIR Endpoint). In this model, a SMART Inbound Security module should be placed on the same node as the services being secured, and the SMART Inbound Security module should have the trust_intra_cluster_tokens.modules property set with a value pointing to the SMART Outbound Security module on its node.

16.11.6Accepting External Access Tokens

 

When validating an external access token (a token that was issued by a third-party SMART on FHIR compliant Authorization Server), Smile CDR will validate the token using the standard OpenID Connect mechanisms. This involves parsing the signed JWT access token, verifying the signature, granting the user appropriate permissions, and finally applying scopes to the user session.

If possible, the third party server should be configured to support the JSON Web Token (JWT) Profile for OAuth2 2.0 Access Tokens. This profile specifies that Access Tokens must be signed JWTs containing at least the following claims:

  • iss - Issuer
  • sub - Subject
  • exp - Expiry (optional)

The server must support token introspection using a Client ID and Client Secret as defined in the OpenID Connect specification.

The following diagram shows the internal flow used by the SMART on FHIR Inbound Security module to process Access Tokens.

16.11.7Validating Tokens

 

When accepting external tokens, Smile CDR will first validate the token to ensure that it has been digitally signed with a key that is trusted. This involves the following steps inside the SMART Inbound Security module:

  • The access token is decoded as a signed JWT so that it can be inspected.
  • The issuer field is extracted in the token in order to match it with a server definition in Smile CDR.
  • SMART Inbound Security module searches for a server definition that matches the issuer URL. Note that only server definitions for the given SMART Inbound Security module are searched.
  • The server locates the key needed to verify the signature:
    • If the server definition has an explicit key defined, it is used to verify the token.
    • If the server definition does not have an explicit key defined, the SMART Inbound Security module makes a request to the issuer URL discovery document to request the public key of the server. Note that this strategy can be beneficial because it can account for keys that are rotated regularly without needing to manually update configuration. However, it also means that the Smile CDR server must always be able to communicate with the OpenID Connect server. This strategy does not work for signature algorithms that require a shared secret key, such as HS256.

See JWT Signature Algorithms for a list of supported algorithms that can be used to verify a signed token.

16.11.8Creating a Server Definition

 

An OpenID Connect Server definition must be created in order to accept external access tokens. This definition describes the external Authorization Server and tells the SMART Inbound Security module that it is okay to trust the Authorization Server and the tokens it generates in order to authorize access to functions in Smile CDR.

To create an OpenID Connect Server definition:

This definition has a few properties:

16.11.8.1Issuer

The most important property when defining the server definition is the Issuer. This is a URL which points to the base URL of the OpenID Connect Authorization Server you wish to trust. Note that the value here must point to a valid OpenID Connect server, and this server must be network accessible by the SMART Inbound Security module. The issuer URL must also match the iss (issuer) claim that is received as a part of any Access Tokens created by the Authorization Server.

Note that trailing slashes in the Issuer URL are ignored in order to avoid incompatibilities. If the Authorization Server iss claim includes a trailing slash, this will not cause any issues.

16.11.8.2Key

If present, the validation key is specified in JWK/JWKS format. Any tokens accepted for this server will be verified against the given key, and authentication will fail if the token can not be verified.

16.11.9Assigning Permissions

 

16.11.9.1Function: onAuthenticateSuccess(...)

In order to accept tokens from an external source, a callback script should generally be supplied. This script will process the verified access token and assign the user an appropriate set of permissions based on this token (and the scopes and claims it provides).

It is important to realize that a token with a set of scopes is not enough on its own to determine what a user may do. For example, a session that has been granted Patient/*.read permission has read access to all resource types for some patient, but which patient(s) that is is not explicitly stated in the token. This is why a script is required. The script is configured in the SMART Inbound Security module configuration, and much contain an onAuthenticateSuccess(...) function that will be called immediately after the OIDC token is validated, but before it is turned into a Smile CDR session.

Several examples of this function can be found in Callback Scripts: onAuthenticateSuccess

16.11.10Function: authenticate(...)

 

If the Authentication Server issues tokens that are not signed JWTs, an authenticate() function must be supplied instead of the onAuthenticateSuccess() function. This function is responsible for validating the token and determining whether it is valid.

16.11.10.1Example: Token Validation Script

The following example simply assigns all permissions to the user. This might be appropriate if all users of an application should have access to all data.

/**
 * This method is called by the SMART Inbound Security module in order to validate the
 * received Access Token and either generate a successful authentication, or
 * to reject it.
 * 
 * @param theRequest The incoming theRequest
 * @param theOutcomeFactory This object is a factory for a successful
 *      response or a failure response
 * @returns {*}
 */
function authenticate(theRequest, theOutcomeFactory) {

	// The received access token is stored in the password field in the 
    // request object
	let token = theRequest.getPassword();

	// In this example, we'll look for a hardcoded token value. In a real scenario, something more
    // advanced will be needed
    let failed = (token === 'ABCDEFG123455');
    
    if (failed) {
        let outcome = theOutcomeFactory.newFailure();
        failure.message = 'Token is invalid';
        return failure;
    }

    // Otherwise, prepare a valid response
	let outcome = theOutcomeFactory.newSuccess();
	outcome.username = theRequest.getUsername();
	outcome.addAuthority('FHIR_READ_ALL_IN_COMPARTMENT', 'Patient/123');
	outcome.addApprovedScope('patient/*.read');

	return outcome;
}

16.11.11Function: getUserName(...)

 

By default, issuer and subject access token claims are used to create Smile CDR username for the external user. This function allows to override this behaviour.

16.11.11.1Example: Username Mapping Script

/**
 * This is a sample username mapping callback script
 *
 * @param theOidcUserInfoMap OIDC claims from the Access token as a map
 * 
 * @param theServerInfo JSON mapping of the OAuth server definition (backed by ca.cdr.api.model.json.OAuth2ServerJson)
 * 
 * @returns Local unique Smile CDR username for the external user.  
 */
function getUserName(theOidcUserInfoMap, theServerInfo) {
   return "EXT_USER:" + theOidcUserInfoMap['preferred_username'];
}

16.11.12Function: onSmartScopeAuthorityNarrowing(...)

 

Called after the session's authorities have been narrowed based on the approved scopes. This function can be used to restore any removed authorities, or add new ones based on approved scopes or permissions. If Enforce Approved Scopes to Restrict Permissions is not enabled, this function can be used to manually apply scope-based authority narrowing logic in place of the built-in logic.

Further documentation and an example can be found in Callback Scripts: onSmartScopeAuthorityNarrowing