Build a SMART App With REST API Calls
This document shows you how to build a more advanced SMART App with server-side logic and REST API Calls.
You should first read the Main Page and HOWTO Build a SMART App before reading this document.
Server to Server Authentication
When your app was making JavaScript API calls, authentication and authorization were entirely transparent to you, the app builder, because the SMART Container was able to take care of it all: the JavaScript API call simply notified the outer frame of the data request, and the outer frame knows who the logged-in user is and what medical record they're currently considering.
Now, we consider the case where you want the backend of your app to obtain data directly from the SMART Container, e.g. the SMART Reference EMR, using the REST API. In that case, the SMART Container doesn't know a-priori who is making the call and whether they're authorized to do so. We need to authenticate the call somehow, and you, the app builder, need to know how to ensure that your calls are properly authenticated.
Choosing the Right Tools
Since you can write a SMART REST app in any language using any toolkit, you have a lot of flexibility. In general, you'll want to look for a language with existing OAuth libraries to handle the details of signing requests to the SMART container. Here we'll illustrate the highlights with a simple Python-based SMART REST demo app called unimaginatively called smart_rest_minimal. This app is written using the Flask web microframework. This won't be a tutorial on Flask, but to get started all you'll need to understand is that Flask provides a simple mechanism to map HTTP URLs to Python functions.
Getting to know OAuth
To authenticate your apps' calls to the SMART Container, SMART uses OAuth, an open standard for access delegation.
OAuth bundles two important features:
a way to label and sign HTTP requests using an identifier token and a secret string
a dance involving the user's browser, the data server, and the server that wishes to consume the data, which, when the user approves the exchange, provides the data consumer with the token and secret needed to perform the authenticated API calls as per (1).
SMART employs (1) and, as of SMART v0.6, (2) the full OAuth authentication "dance".
Change Your App's OAuth Consumer Secret in Production (Important!)
Each SMART container your app runs against must first "install" your app
by inserting your app's manifest into it's database. This data includes
your app's OAuth consumer-secret which is the shared secret between
your app and the container that ALL SECURITY of your app's
communication with the server relies on and is a basic requirement of
OAuth-signed REST API calls.
YOU MUST CHANGE THE
consumer-secret IN PRODUCTION!
This secret is provisioned when your app is loaded into the smart container with the following command:
$ manage.py load_app <manifest-location> <secret>
OAuth
SMART REST apps now perform the standard OAuth 1.0a dance including providing authorization callback URLs instead of the previous "simplified" dance. This allows developers to use more standard libraries and methods write native apps (including mobile apps).
Here is a diagram of the OAuth 1.0 flow for you visual learners: http://developer.yahoo.com/oauth/guide/oauth-auth-flow.html
Authentication Flow
Initializing the SMARTClient
First, initialize the SMARTClient with the URL of the SMART container
you are attempting to access (the api_base) and your app's
consumer_key and consumer_secret which you registered with the
container previously so the container can authenticate your app's
requests. This may be on you app's users first hit of your app's index
page. You may or may not have a patient's record_id at this point.
Getting the record_id
If you don't have a record_id, you will be able to redirect to the
container's record selection page (the smart.launch_url) which will
redirect back to your app's index page with the user selected
record_id in the URL parameters for you to read.
Requesting the Request Token
The next step in the OAuth dance is for your app to request the
request_token to allow access to a specific patient record. This is done
simply by initializing the SMARTClient with the api_base and desired
record_id. Assuming your initialized SMARTClient is stored in a
variable named smart:
smart.fetch_request_token()
Authorizing the request
If this call was successful, the next step in the dance is to have the user signal to the container that they approve this request for access. This is done by having your app redirect the user's browser to the container's "access authorization page":
flask.redirect(smart.auth_redirect_url)
Exchange the Request Token for the Access Token
Once the user authorizes your app's request with the container, the
container will redirect the users' browser to the oauth_callback URL
(typically /authorized) that you defined in the manifest that you
installed with the container passing an oauth_verifer as a HTTP
parameter. Your app's handler for this URL should then "exchange" the
request_token and the oauth_verifer with the container to receive
the access_token which your app will use to make requests for
protected data from the container.
acc_token = smart.exchange_token(verifier)
Accessing Protected Data With the acc_token
A few final steps are required before accessing data: your app will need to store the access token in a web session (or other means) so it can be reused across multiple requests. And the smart client's internal token should be "updated" e.g.
flask.session['acc_token'] = acc_token
smart.update_token(acc_token)
Now accessing data using the SMART REST API is simply a matter of making calls such as:
# Now we're ready to get data!
# Let's fetch demographics and display the name
demo = smart.get_demographics()
The result is an SMARTResponse object containing an RDF graph of data, which we can query for just the fields we want which you can query with SPARQL e.g.:
sparql = """
PREFIX vc: <http://www.w3.org/2006/vcard/ns#>
SELECT ?given ?family
WHERE {
[] vc:n ?vcard .
OPTIONAL { ?vcard vc:given-name ?given . }
OPTIONAL { ?vcard vc:family-name ?family . }
}
"""
results = demo.graph.query(sparql)
record_name = 'Unknown'
if len(results) > 0:
res = list(results)[0]
record_name = '%s %s' % (res[0], res[1])
What Next?
To see how this all works, carefully read over the code for the smart_rest_minimal app.