NAV Navbar
Logo cerner
code

HealtheLife Framework SDK

The HealtheLife Framework software development kit (SDK) enables third-party developers to integrate web-based applications, called pagelets, into the HealtheLife patient portal. The HealtheLife Framework SDK includes the tools required to provide a secure and seamless experience for users of the HealtheLife patient portal.

Important! By developing and implementing web applications for use in the HealtheLife framework, organizations agree to take any and all responsibility for properly protecting personal health information (PHI) and IDs as outlined in Health Insurance Portability and Accountability Act (HIPAA) compliance rules when using any sort of analytics or tracking applications such as Google Analytics. Cerner cannot log HIPAA audit events for content embedding using the HealtheLife Framework SDK.

Developing a Pagelet

A pagelet is a web application that is designed to be embedded in an inline frame in the HealtheLife patient portal. Inline frames may add overhead, but they also provide the following benefits:

Browser Context Session Tokens

Example BCS Token:

<iframe src="https://my-app.com/pagelet?bcs_token= eyJraWQiOiIyMDE5LTA1LTIyVDIwOjQwOjM4Ljc4MS5lYyIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJ1cm46Y2VybmVyOmlkZW50aXR5LWZlZGVyYXRpb246cmVhbG06NDlmMTc2NTktODkxYy00MmM5LWFlMjYtZDZhZTc4MjZmNjJkOnByaW5jaXBhbDp0dzVFUDhVRDh3VSIsImF1ZCI6Imh0dHBzOlwvXC9zcHJlcy5wYXRpZW50cG9ydGFsLmhlYWx0aGVpbnRlbnQuY29tIiwiaXNzIjoiaHR0cHM6XC9cL3BsYXlncm91bmQuY29uc3VtZXJwb3J0YWwuaGVhbHRoZWludGVudC5jb20iLCJleHAiOjE1NTg3MDkyMDAsImlhdCI6MTU1ODcwODYwMCwic2lkIjoiNGIwZWRjMmEtMjU5Yi00ODI0LTgyYzYtMTc3MjM4Y2JlZjRjIn0.dDNtHwPpPc2FUmGX_mpWJAA95f1edPttN-6m6uin6dM-P4xRma1yFNNhuBFK9JE-YT39clSPaEjHXnSh6EMnew " scrolling="no" title="Relationships" allow="geolocation *; microphone *; camera *; geolocation; microphone; camera;" style="overflow: hidden; height: 269px;"></iframe>

The HealtheLife framework sends browser context session (BCS) tokens in a query parameter in the URL when the pagelets are loaded in an iframe. The BCS token allows non-Cerner browsing contexts to determine a user’s identity using a verifiable token. The BCS token is a time-bound, signed JSON Web Token (JWT), served by the Cerner authorization server. See Browsing Contexts on the Web Hypertext Application Technology Working Group (WHATWG) website for more information.

Token Fields

Example Decoded BCS Token

{
    "kid": " 2019-05-22T20:40:38.781.ec ",
    "typ": "JWT",
    "alg": "ES256"
}

{
    "sub": " urn:cerner:identity-federation:realm:49f17659-891c-42c9-ae26-d6ae7826f62d:principal:tw5EP8UD8wU ",
    "aud": " https://spres.patientportal.healtheintent.com",
    "iss": " https://playground.consumerportal.healtheintent.com",
    "exp": 1558709200,
    "iat": 1558708600,
    "sid": " 4b0edc2a-259b-4824-82c6-177238cbef4c "
}

When decoded, the content of the token includes a header and payload, like the example to the right. The fields in the decoded token enable applications to identify and authorize users. The following fields are included in the header and are used to verify the token signature:

Name Description
alg The cryptographic algorithm used to sign the BCS token.
kid The ID of the key used to sign the BCS token.
typ The media type of the BCS token.

The payload fields, or token claims, below are used to identify the user, set the content security policy of the application, and verify that the token has not expired. See Registered Claim Names on the Internet Engineering Task Force (IETF) website for more information about token claims.

Name Description
aud The origin domain of the browsing context that receives the token.
exp The expiration time on or after which the BCS token must not be accepted for processing, formatted as a UNIX epoch.
iat The time at which the BCS token was issued, formatted as a UNIX epoch.
iss The root URL of the website, or HealtheLife framework instance, that acts as a container for the pagelet.
sid The proprietary Cerner ID associated with the session. This ID can be used in HIPAA auditing and other security logging.
sub A principal URI that identifies the user.

Validating a Token

Example Token Validation:

require 'net/http'
require 'json'
require 'json/jwt'

JWK_URI = 'https://authorization.cerner.com/jwk'

def decode_token(token)
  keys = Net::HTTP.get(URI(JWK_URI))
  jwk_set = JSON::JWK::Set.new(JSON.parse(keys))
  return decoded_token(JSON::JWT.decode token, jwk_set)
end

Use an open-source library to validate tokens to ensure that they are signed and not tampered with. As a minimum requirement, the library must support validation for Elliptic Curve Digital Signature Algorithm (ECDSA) using a P-256 curve and SHA-256 hash algorithm. Tokens can be validated using a JSON web key, which can be obtained at the following URL: https://authorization.cerner.com/jwk.

See the JSON Web Tokens website for a list of available open-source libraries, and see JSON Web Key on the IETF website for more information about JSON web keys.

Serving Pagelet Requests

Example Server:

require 'sinatra'
require 'net/http'
require 'json'
require 'json/jwt'

# Keys should be cached.
keys = Net::HTTP.get(URI('https://authorization.cerner.com/jwk'))
jwk_set = JSON::JWK::Set.new(JSON.parse(keys))

get '/index.html' do
  bcs_token = params[:bcs_token]
  locale = params[:locale]

  begin
    decoded_token = JSON::JWT.decode bcs_token, jwk_set
  rescue
    return 403
  end

  # Ensure that the token is not expired and that the token was created for this host.
  if !(Time.now.between?(Time.at(decoded_token[:iat]), Time.at(decoded_token[:exp])) &&
    request.host == decoded_token[:aud])
    return 403
  end

  headers['Cache-Control'] = 'no-cache'
  headers['Content-Type'] = 'text/html; charset=utf-8'
  # Allow this pagelet to be embedded as an iframe only on the issuer's domain.
  headers['X-Frame-Options'] = "allow-from #{decoded_token[:iss]}/"
  headers['Content-Security-Policy'] = "frame-ancestors #{decoded_token[:iss]}/;"

  %Q(
    <html lang='{locale}' hidden>
      <head>
        <title>My Pagelet</title>
      </head>
      <body>
        Hello World
        <script src="https://healthelife.healtheintent.com/healthelife_sdk.js"></script>
        <script>
          $HL.App.init({
            acls: ["#{decoded_token[:iss]}"]
          });
        </script>
      </body>
    </html>
  )
end

A pagelet is simply an HTML page that is embedded in the HealtheLife framework. However, to securely embed a pagelet, a server that handles pagelet requests should take additional steps to ensure that the pagelet works in the framework and has a proper security policy. To the right is a simple server written in Ruby that serves pagelet requests.

Verifying the BCS Token Claims

Example Claims Verification:

# Ensure that the token is not expired and that the token was created for this host.
if !(Time.now.between?(Time.at(decoded_token[:iat]), Time.at(decoded_token[:exp])) &&
  request.host == decoded_token[:aud])
  return 403
end

Verify the following claims on BCS tokens before returning a successful response:

Setting a Content Security Policy

Example Content Security Header:

# Allow this pagelet to be embedded as an iframe only on the issuer's domain.
headers['Content-Security-Policy'] = "frame-ancestors #{decoded_token[:iss]}/;"
# Set X-Frame-Options for legacy browser support.
headers['X-Frame-Options'] = "allow-from #{decoded_token[:iss]}/"

Framed applications need to set additional security policies to ensure that they are not vulnerable to user interface (UI) redress attacks. A content security policy header should be sent in the response header to ensure that the pagelet can be framed only in the context of the issuer site. See Clickjacking on the Open Web Application Security Project (OWASP) website for more information about UI redress attacks.

Setting the Locale

locale = params[:locale]

The locale used by the framework is sent to each pagelet in the locale query parameter. Pagelets should recognize this locale if possible.

Pagelet HTML

<html lang='en-us' hidden>
  <head>
    <title>My Pagelet</title>
    <style>
      html[hidden] {
        display: none;
        visibility: hidden;
      }
    </style>
  </head>
  <body>
    Hello World
    <script src="https://healthelife.healtheintent.com/healthelife_sdk.js"></script>
    <script>
      $HL.App.init({
        acls: ["#{decoded_token[:iss]}"]
      });
    </script>
  </body>
</html>

The SDK is initialized by calling $HL.App.init and specifying the access-control list (ACL) in the acls property as equal to the token issuer claim. The ACL ensures that any browser that does not support the Content-Security-Policy or X-Frame-Options directive still hides the pagelet using the hidden attribute on the HTML element. When the origin of the parent site is verified to equal the token issuer, the SDK unhides the pagelet.

HealtheLife Framework JavaScript SDK

A production version of the HealtheLife Framework JavaScript SDK is minified and optimized at the following content delivery network (CDN) location: https://healthelife.healtheintent.com/healthelife_sdk.js

Browser Support

The HealtheLife Framework JavaScript SDK supports the following browsers:

Framework JavasScript API

The API methods are in the $HL.App namespace.

$HL.App.init(options)

$HL.App.init Example:

$HL.App.init({
  acls: ["https://yourdomain.com"],
  onReady: function() {
    console.log('Pagelet initialized!')
  },
  targetSelectors: '.modal'
});

Initializes the pagelet. Call $HL.App.init as soon as possible to connect the pagelet to the framework. Initialization uses the following process:

  1. Verify that the pagelet is being framed in a trusted property that is identified by the acls property.
  2. Resize the iframe to fit the height of the content in the pagelet to eliminate vertical scrolling in the iframe.

The following properties can be set in the options object:

Property Type Default Description
acls String[] [] The list of trusted origins that are authorized to be embedded in the pagelet.
onReady function null A callback when initialization is complete.
targetSelectors String null A CSS selector that identifies floated or absolutely positioned (fixed) elements to include in the iframe sizing calculation.

$HL.App.scrollIntoView()

$HL.App.scrollIntoView Example:

$HL.App.scrollIntoView()

Instructs the parent browser window to scroll until the pagelet is in view.

$HL.App.routeTo(options)

// See the following example that uses the path property to navigate to another page in the framework.
$HL.App.routeTo({
  path: '/pages/messaging'
});

// A better approach is to use the alias property to route to a page using its alias.  
// Aliases are assigned to the configured pages in the HealtheLife framework.
// This ensures that if the path to the page changes, the URL does not break.
$HL.App.routeTo({
  alias: 'messaging'
});

// See the following example that routes using the context property:
$HL.App.routeTo({
 alias: 'messages.show',
 context: {
   id: '3298a9adfa98dsa',
 }
});

Navigates the framework to the given path. The following properties can be set in the options object:

Property Type Default Description
path String[] [] The parent application’s path to which to navigate. The path value is used by default if both the alias and path properties are specified.
alias function null An alias that corresponds to a path in the parent application. Aliases for configured pages can be provided by Cerner.
context Object null Extra, contextual information to send to pagelets on the page. The context property is sent to pagelets in query parameters at the end of the pagelet URL. The pagelets that receive the context determine how it is used. Note: The context is not applied to pagelets if a bookmark exists (see the bookmarkLink property below).
bookmarkLink String null The URL loaded into the pagelet on the page that is accessed instead of the original pagelet URL defined for the page. Note: The URL of the original pagelet configured by the parent application and the bookmarkLink URL must have the same origin. For example, if pagelet A is mounted in the iframe on page one and has a button that triggers routeTo with a path of /pagetwo and a bookmarkLink equal to pagelet B’s URL, the parent application opens page two and pagelet B is displayed in the iframe even though page two was originally configured to display pagelet C. This bookmarkLink property is powerful for users because it gives them the ability to bookmark the page in that specific state, view it later with pagelet B displayed in the iframe, and send a link to the page in that specific state to another user.

Styling Considerations

Pagelets embedded in the framework require the special styling considerations below.

Sizing

The SDK determines the height of the iframe using the offset height of the document body. It then monitors the Document Object Model (DOM) and updates the height when changes are made to the HTML. This means that the pagelet cannot set the <html> and <body> elements to a relative height that is determined by the viewport, or, in the case of a pagelet, the iframe. For example, a height of either 100% or 100vh breaks the sizing calculation. See the following recommendations:

Recommended Not Recommended
html, body { height: auto; } or html, body { height: 200px; } html, body { height: 100%; } or html, body { height: 100vh; }

Fluid and Responsive Design

Pagelets in the framework can be configured to be any size and displayed on anything from large screens to small mobile devices. The styles for pagelets should be fluid and responsive to ensure that a pagelet looks acceptable at every screen size. This is especially important because the width of the iframe is used to determine the media query in CSS.

Recommended Not Recommended
Allow content to fill 100 percent of the available screen width, and use media queries to change the style when necessary. Do not fix content to a specific width or assume an ideal viewport size.

Out of Flow Elements

Floated and absolutely positioned elements are taken out of normal flow and not included in the pagelet height calculation. Depending on where the UI is displayed, this may cause the following problems:

When possible, avoid position-fixed elements. If they are required in the UI, use the targetSelectors property to identify those UI elements so they are included in the resizing calculation for the $HL.App.init() method.

Background Colors

Pagelets should set their background color to transparent to ensure that they seamlessly blend with the background color on the HealtheLife framework. Because the background color can be themed, a specific color cannot be guaranteed. Content that needs to be on a white or light background should be placed in a card. Cards and other containers should have no spacing between their borders and the iframe to ensure that they align with other content in the framework.

Recommended Not Recommended
Place content in cards to ensure it is readable with a consistent contrast ratio, and ensure that content is flush with the iframe border. Do not set a background color on your pagelet’s html or body element, and do not set a padding or margin on the body or container elements.

Integrating with Platform APIs

Getting Started

A system account is required to use HealtheIntent APIs, and the system account must be authorized to use the APIs that you use for your pagelet. See Getting Started for more information.

Retrieving the Consumer

Example GET Request to Retrieve a Consumer

GET /consumers?aliasType=USER&aliasSystem=49f17659-891c-42c9-ae26-d6ae7826f62d&aliasValue=tw5EP8UD8wU

The HealtheIntent consumer can be retrieved by sending a GET request to the /consumers endpoint of the HealtheIntent Consumer API and specifying the following query parameters:

The user’s identity realm and principal can be found in the principal URI that is populated in the BCS token sub property. For example, if the BCS token subject is urn:cerner:identity-federation:realm:**49f17659-891c-42c9-ae26-d6ae7826f62d**:principal:**tw5EP8UD8wU**, the user could be retrieved by specifying the query parameters above as follows:

Displaying the Consumer Information in a Pagelet

Example Pagelet That Retrieves a HealtheIntent Consumer

require 'sinatra'
require 'net/http'
require 'json'
require 'json/jwt'

# Keys should be cached.
keys = Net::HTTP.get(URI('https://authorization.cerner.com/jwk'))
jwk_set = JSON::JWK::Set.new(JSON.parse(keys))

get '/index.html' do
  bcs_token = params[:bcs_token]

  begin
    decoded_token = JSON::JWT.decode bcs_token, jwk_set
  rescue
    return 403
  end

  # Ensure that the token is not expired and was created for this host.
  if !(Time.now.between?(Time.at(decoded_token[:iat]), Time.at(decoded_token[:exp])) &&
    request.host == decoded_token[:aud])
    return 403
  end

  subject = decoded_token[:sub]
  principal_uri = subject.match(/realm:(?<realm>[\w+-]+):principal:(?<principal>\w+)/)

  # API changes based on tenant.
  consumer_uri = URI("https://playground.api.us.healtheintent.com/consumer/v1/consumers")
  consumer_uri.query = URI.encode_www_form({
    aliasType: 'USER',
    aliasValue: principal_uri[:principal]
    aliasSystem: principal_uri[:realm]
  })

  http = Net::HTTP.new(consumer_uri.host, consumer_uri.port)
  http.use_ssl = true
  consumer_response = http.get(consumer_uri.request_uri, {
      'Authorization': ENV['BEARER_TOKEN'],
      'Content-Type': 'application/json'
  })

  consumers = JSON.parse(consumer_response.body)["items"]
  consumer = consumers.first

  headers['Cache-Control'] = 'no-cache'
  headers['Content-Type'] = 'text/html; charset=utf-8'
  # Allow this pagelet to be embedded as an iframe only on the issuer's domain.
  headers['Content-Security-Policy'] = "frame-ancestors #{decoded_token[:iss]}/;"
  # Set the X-Frame-Options for legacy browser support.
  headers['X-Frame-Options'] = "allow-from #{decoded_token[:iss]}/"

  %Q(
    <html lang='en-us' hidden>
      <head>
        <title>My Pagelet</title>
        <style>
          [hidden] {
            display: none;
          }
        </style>
      </head>
      <body>
        Hello #{consumer["name"]["formatted"]}
        <script src="https://healthelife.healtheintent.com/healthelife_sdk.js"></script>
        <script>
          $HL.App.init({
            acls: ["#{decoded_token[:iss]}"]
          });
        </script>
      </body>
    </html>
  )
end

After you retrieve the consumer, you can use it to retrieve and display demographic information or to identify any patients that are linked to the consumer. See Consumer API v1 for more information about using the HealtheIntent Consumer API.

Note: Consumer demographic information may be different from the demographic information of patients in the electronic health record (EHR). Patients should be identified using an MRN.

Additional Resources

HealtheIntent Resources

External Resources