CORS in FHIR
August 8, 2014 4 Comments
The following is a copy of an email sent to the FHIR list by Peter Bernhardt (and copied with permission). I’ve not read through it in detail, but it’s something that I’m grappling with myself as I prepare for the SMART track in the up-coming Connectathon.
I suspect that it’s something that a lot of folk are going to have ‘fun’ with, so I suggested to Peter that we copy it here so it can be found later!
(btw – all the servers that Peter refers to are described in the spec)
Hey Folks,
I’m following up conversations I’ve had with a few people on the implementers chat about Cross-Origin Resource Sharing (CORS). I’ve been writing a FHIR client application using JavaScript, and I’ve had limited success interacting with various servers.
I’ve tested against the following:
- SPARK
- Health Intersections
- HAPI
- Blaze
- SMRT
While I’ve had no problem issuing GET requests to all of these servers, only SPARK is accepting other methods. I’ve summarized my findings below.
Per CORS, here’s the “pre-flight” request sent to the remote servers:
OPTIONS /fhir/Organization/b27ed191-f62d-4128-d99d-40b5e84f2bf2 HTTP/1.1
Host: spark.furore.com
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Access-Control-Request-Method: POST
Origin: http://www.fhir-starter.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36
Access-Control-Request-Headers: accept, x-fhir-starter, content-type
Accept: */*
DNT: 1
Referer: http://www.fhir-starter.com/organization/edit/0B7
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Aside from the resource URL and Host, the request is always the same, so I won’t repeat it. From a CORS perspective, the most relevant information in that request are these headers:
Origin: http://www.fhir-starter.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: accept, x-fhir-starter, content-type
Essentially, this is the browser or user agent letting the server know who it is that is calling (Origin), what they intend to do (POST), and what headers they will include.
For the request headers, a particular browser injects different headers (I used Chrome, so you see the accept and content-type headers). I’ve written the client to also include the X-FHIR-Starter header.
The SPARK server responds as follows.
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 07 Aug 2014 16:15:20 GMT
Content-Length: 0
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: x-fhir-starter,content-type
Connection: Keep-Alive
Again, focusing on CORS-related headers, we have these:
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: x-fhir-starter,content-type
The Origin value tells me there is no whitelist restriction. And the Allow-Headers value lets me know the server is expecting that I include X-FHIR-Starter and Accept headers in my POST request.
The transaction continues with the POST (or PUT or DELETE, as the case may be), with the following headers.
POST /fhir/Organization/b27ed191-f62d-4128-d99d-40b5e84f2bf2 HTTP/1.1
Host: spark.furore.com
Connection: keep-alive
Content-Length: 1177
Cache-Control: no-cache
Pragma: no-cache
X-FHIR-Starter: urn:fhir.starter
Origin: http://www.fhir-starter.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36
Content-Type: application/json+fhir
Accept: application/json+fhir, application/json, text/plain, */*
DNT: 1
Referer: http://www.fhir-starter.com/organization/edit/0B7
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Note that this includes the X-FHIR-Starter header the browser promised to include.
My client can successfully execute PUT, POST and DELETE against Spark. For the other FHIR servers I tested, the transaction fails after the server’s response to the OPTIONS request. Here’s a sampling.
HAPI (http://fhirtest.uhn.ca/base):
HTTP/1.1 200 OK
Date: Thu, 07 Aug 2014 16:34:50 GMT
Server: GlassFish Server Open Source Edition 4.0
X-Powered-By: HAPI FHIR 0.6-SNAPSHOT RESTful Server
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Expose-Headers: Content-Location
Content-Type: application/xml;charset=UTF-8
Transfer-Encoding: chunked
Connection: Keep-Alive
Content-Encoding: gzip
Health Intersections (http://fhir.healthintersections.com.au/open:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 2
Date: Thu, 07 Aug 2014 16:25:13 GMT
Expires: Wed, 06 Aug 2014 16:25:13 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Expose-Headers: Content-Location
Access-Control-Request-Method: GET, POST, PUT, DELETE
Server: Health Intersections FHIR Server
Connection: Keep-Alive
SMART (https://fhir-open-api.smartplatforms.org):
HTTP/1.1 200 OK
Server: nginx/1.4.6 (Ubuntu)
Date: Thu, 07 Aug 2014 16:57:13 GMT
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: http://www.fhir-starter.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: origin, authorization, accept, content-type, x-requested-with
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS
Access-Control-Max-Age: 3600
Same results for HAPI, Health Intersections and SMART: While the server returns a 200 response, the XMLHttpRequest object throws a fatal error: “Request header field X-FHIR-Starter is not allowed by Access-Control-Allow-Headers”. According to the W3C CORS spec (Section 6.2, step 4 [http://www.w3.org/TR/cors/#resource-preflight-requests]), I should get back my X-FHIR-Starter header in the list of Allow-Headers.
I get a different response from Blaze.
Blaze (https://fhir.orionhealth.com/blaze/fhir):
HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Allow: DELETE
Content-Type: application/xml+fhir; charset=utf-8
Content-Encoding: gzip
Expires: -1
Vary: Accept,Accept-Encoding
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: *
Date: Thu, 07 Aug 2014 16:30:59 GMT
Content-Length: 208
The same Invalid HTTP status code 405 is returned for all but the GET method (which is successful).
Some references I found helpful:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
Regards,
Peter Bernhardt
I am noticing renewed interest in “good security guidance”, that does not use CORS… This seems like something the general http REST community would have already addressed, so doesn’t feel to me like something Healthcare, especially HL7, should need to address… Comment?
You mean the people are thinking that CORS is not good practice? Certainly clinFHIR (or any other browser based app) would have issues without CORS (though I guess I could set up a server based proxy…)
“X-FHIR-Starter” why this header used for?
It’s not a standard header – where are you seeing it?