This is a Rust-based SMART-on-FHIR application that recreates Cerner's SMART-on-FHIR tutorial using Actix and Maud.
This repository is licensed Apache 2.0, with several exceptions:
- One CSS file is copied in verbatim from Cerner's tutorial repo,
this file is under
resources
The current application is a straightforward translation of Cerner's SMART-on-FHIR tutorial from hardcoded HTML into HTML macros that are defined using Maud and served using Actix. We then package the app into a Docker container, which is deployed via AWS Fargate.
Our application exposes three primary endpoints:
/
, defined insrc/index.rs
/launch.html
, defined insrc/launch.rs
/healthcheck.html
, defined insrc/health.rs
It also exposes endpoints to serve the contents of the /resources
directory,
using the actix_files crate.
The /healthcheck.html
endpoint provides a simple mechanism to check if the server is running.
The /launch.html
endpoint is the endpoint that a FHIR application would call to launch your
SMART-on-FHIR application. This endpoint is responsible for starting the SMART authorization
sequence, and requesting the necessary Oauth scopes
for your application.
The /
endpoint is the endpoint that a FHIR application would redirect to, after launching your
application. At this point, your application will have the necessary credentials to access data
using FHIR.
The app is packaged into a simple Docker container, using the Dockerfile
in the root directory.
Once the container is built, it is pushed to AWS ECR;
example instructions can be found here.
We manage our deployment using Terraform,
which can be found in the iac
directory. We make a few assumptions:
- You are deploying this application under a subdomain (e.g.,
fhir-example.translating.science
, vstranslating.science
) of a root domain - You have a multiple Account organization structure in AWS, where your DNS for your root domain
is managed in an
Infrastructure
account that is separate from the account where you plan to deploy thefhir-example
application (an example of this can be found here)
We have three deployment steps:
iac/ecr
, which is used initially to set up an ECR repository that we will push our docker container to.iac/dns
, which is used initially to set up DNS records and certificates. This allows us to deploy the app behind HTTPS in Fargate, with AWS managing the certificats via AWS ACM.iac/fargate
, which sets up Fargate and its accompanying resources.
We use several Terraform variables:
aws_profile
: Used byecr
,dns
, andfargate
. This is the name of the AWS profile used to deploy into the AWS account for thefhir-example
application.aws_profile_main
: Used only bydns
. This is the name of the AWS profile used to deploy into the AWSInfrastructure
account.domain
: Used only bydns
. This is the name of the root domain.subdomain
: Used by bothdns
andfargate
. This is the name of the subdomain.image
: Used only byfargate
. This is the repository/tag of the Docker image to use.
There are additional optional variables defined in the IaC.
We have an example deployment running at https://fhir-example.translating.science. If you'd like to run your own deployment, you can follow the steps below.
As of June 12th, 2024, the cost of running a Fargate service in AWS' us-west-2
region are:
Price | |
---|---|
per vCPU per hour | $0.04048 |
per GB per hour | $0.004445 |
Our IaC scripts configure a service running 0.25 vCPU and 0.5 GB of memory. Thus, the computing costs of running the service are approximately:
Timespan | Price |
---|---|
1 hour | $0.01 |
1 day | $0.30 |
1 month (31 days) | $9.18 |
In addition to these costs, you will also need to pay for AWS' networking services.
Assuming you have Docker installed, you can
build a Docker container by running docker build .
in the repository root.
Once you have built your Docker container, you need to create a private repository in the
AWS account you plan to deploy the application in. This step needs to happen exactly once.
To do this, move into the iac/ecr
directory, and run terraform init
to initialize the
terraform providers.
To create the terraform plan for this step, run:
terraform plan -var="aws_profile=<application_profile>" -out=plan
This is a good time to check the generated plan to make sure it looks correct. Assuming that your plan creates successfully, you can then create the resources by running:
terraform apply plan
This step is nearly instantaneous.
The terraform apply
will output the URL of your ECR repository. With this, you can push your container to ECR by running
Step 4 and onwards in this AWS tutorial.
Move into the iac/dns
directory, and run terraform init
to initialize the terraform
providers.
To create the terraform plan for this step, run:
terraform plan -var="domain=<mydomain>" -var="subdomain=fhir-example.<mydomain>" -var="aws_profile=<application_profile>" -var="aws_profile_main=<infrastructure_profile>" -out=plan
Check your plan, and if it looks correct, deploy by running terraform apply plan
.
This step should take approximately 5 minutes.
Move into the iac/fargate
directory, and run terraform init
. To generate a plan, you
will need to run:
terraform plan -var="subdomain=fhir-example.<mydomain>" -var="aws_profile=<application_profile>" -var="image=<application_account>.dkr.ecr.us-west-2.amazonaws.com/<repository_name>:<tag>" -out=plan
Check your plan, and if it looks correct, deploy by running terraform apply plan
.
This step takes approximately 1-3 minutes to run. Once the resources are created, it takes
several minutes for Fargate to launch your instances and start your container.
You can launch your application by going to the SMART Sandbox Launcher. You will want to use the R4
FHIR version, and either the Provider EHR
launch type or one of the Standalone
launch types.
For the SMART Sandbox Launcher's validation settings (on the "Client Registration & Validation" tab of the interface), you will want to configure the app as a confidential symmetric app. For this, you will need to provide the following info:
- FHIR scopes: This is a whitespace delimited string that explains what FHIR scopes our app wants to access.
Our app uses the
patient/Patient.read patient/Observation.read launch launch/patient online_access openid profile
scopes. - Client ID and secret: These are used to perform basic authentication as part of the
confidential symmetric flow. We set these values in our app using the environment variables
FHIR_EXAMPLE_CLIENT_ID
andFHIR_EXAMPLE_CLIENT_SECRET
. The default values areFHIR_EXAMPLE_CLIENT_ID=rust-smart-fhir
andFHIR_EXAMPLE_CLIENT_SECRET=rust-smart-fhir-secret
. - Allowed redirect URL: When we try to obtain an authentication code, we provide a
URL that the SMART-on-FHIR server will redirect the user to. The SMART-on-FHIR server will validate this URL against this list of allowed redirect URLs (which we have provided offline).
For our app, the URL should point to our
/callback
endpoint. If you are running the app locally, that URL will behttp://127.0.0.1:8080/callback
. Otherwise, you should configure the URL by setting theFHIR_EXAMPLE_DOMAIN
environment variable.
When you launch the app through the SMART sandbox launcher, you will need to pick a patient. If you do not, the SMART sandbox launcher will display a patient picker as part of the launch flow. Note that not all of the patients in the SMART sandbox launcher will have observation data for the codes we request.
In addition to the EHR launch flow, we support standalone launches.
When you select a Standalone
launch in the SMART Sandbox Launcher, it will provide you with a Server's FHIR Base URL
that
we will launch our app against. Copy this URL from the SMART Sandbox Launcher site, and navigate to the /standalone.html
endpoint of your server. You will provide
this URL as the URL of EHR endpoint:
in the form on that page.