Sandstorm apps may wish to respond to HTTP requests from Javascript code, native mobile apps, or command-line tools, outside of the grain-frame. These clients sometimes need a permanent URL to reach the app; the Sandstorm HTTP APIs feature allows apps to make specific parts of themselves available this way.

Overview

Sandstorm allows apps to expose their HTTP APIs at a permanent URL, as opposed to ephemeral domains used within the grain-frame. Sandstorm does access control on each inbound HTTP API request to the app. Briefly:

  • When Sandstorm receives a request for an app's HTTP API via a Sandstorm API subdomain, it verifies that the request has a valid API token, then passes it on to the app.

  • When the app receives the HTTP request, the token has been removed and typical Sandstorm permission headers like X-Sandstorm-User-Id have been added instead.

  • When the app responds, Sandstorm modifies some HTTP headers in the response, then passes the response to the client. This is flexible to any MIME type as well as to WebSockets.

Try it now. You can try making a request to a Sandstorm app's JSON API right now via curl:

curl -H "Authorization: Bearer 49Np9sqkYV4g_FpOQk1p0j1yJlvoHrZm9SVhQt7H2-9" https://alpha-api.sandstorm.io/

Configuring an app to permit requests via the API subdomain

The handling of inbound HTTP API requests is configured in sandstorm-pkgdef.capnp. Look for this line.

    # apiPath = "/api",

Before your app will accept requests on an API subdomain, you need to uncomment this line and specify a string here. All inbound requests to the Sandstorm API subdomain for your app will have their path prefixed by this string. The empty string ("") indicates that your app disallows API requests; a single slash ("/") indicates that your entire app should be available over the Sandstorm API subdomain.

For apps not using sandstorm-http-bridge, read the relevant Cap'n Proto files.

How to generate an API token

Sandstorm uses API tokens to determine the which grain the user is requesting, the identity of the user, and the permission level to apply to this request.

There are various ways to obtain an API token:

  • Recommended: Client-side Javascript embedded within your app, aka offer templates. The app specifies some text which Sandstorm shows to the user, doing string substitution to add an API token to the text.

  • The user can click the key icon in the top bar when they have an app open.

  • Cap'n Proto RPC: Sandstorm's HackSessionContext exports a Cap'n Proto RPC method called HackSessionContext.generateApiToken(). That method is deprecated in favor of offer templates. If you need to use it, read the web publishing guide for more about how to access HackSessionContext.

In the future, we will implement an OAuth flow allowing a third party to initiate a request for access to the user's apps.

Creating an offer template

An offer template is a way for an app to create an element that appears like a DIV, containing text controlled by the app, which also has an API token inside. Because it is implemented with an IFRAME, the app cannot read the token out of the offer template, which is good for app isolation.

You can see an example by launching a GitWeb demo.

To create an offer template:

  • Create full-width, 55-pixel-tall IFRAME element within your page with an ID of offer-iframe, with no margins so as to blend in seamlessly into your app. (The ID can be any valid ID, so long as you use the same one in a later step.) For example:
<iframe style="width: 100%; height: 55px; margin: 0; border: 0;" id="offer-iframe">
</iframe>
  • Modify your page so that when it loads, the page will ask Sandstorm to generate a URL with the text of the offer template. (The rpcId parameter can be any string, so long as you use the same one in this step and the next step. Sandstorm will echo it back with the response.)
<script>
  function requestIframeURL() {
    var template = "You can use the $API_TOKEN key to reach me at $API_HOST.";
    window.parent.postMessage({renderTemplate: {
      rpcId: "0",
      template: template,
      clipboardButton: 'left'
    }}, "*");
  }

  document.addEventListener("DOMContentLoaded", requestIframeURL);
</script>
  • Modify your page so that when Sandstorm provides the unique URL for this offer template, your page will place that URL into the src element of the IFRAME.
<script>
  var copyIframeURLToElement = function(event) {
    if (event.data.rpcId === "0") {
      if (event.data.error) {
        console.log("ERROR: " + event.data.error);
      } else {
        var el = document.getElementById("offer-iframe");
        el.setAttribute("src", event.data.uri);
      }
    }
  };

  window.addEventListener("message", copyIframeURLToElement);
</script>
  • Your offer template will now contain text such as this.
You can use the DT5hkM18CejvQomjIM1AVT4zqQdOdoFCid898bP2hQS key to reach me at https://api-d9bc3de0bed9cb9b321d3c491c10dbca.alpha.sandstorm.io/.

Note: API tokens created this way must be used within 5 minutes, or else they automatically expire. To prevent this from becoming a serious problem, the Sandstorm shell automatically refreshes the IFRAME every 5 minutes.

Parameters to renderTemplate()

renderTemplate() accepts the following parameters:

  • rpcId: String of a message ID that will be passed back to your code.

  • template: String to display to the user, where $API_HOST, $API_TOKEN, and $GRAIN_TITLE_SLUG will be replaced.

  • petname: String (optional) of a name that this API token will have, when the user lists the API tokens and sharing links they have generated.

  • roleAssignment: roleAssignmentPattern (optional) of permissions to apply to inbound requests. Use this to create API tokens with limited permissions, such as a read-only view.

  • forSharing: Boolean (optional) true if this token should represent the anonymous user. You can use this to detach the token from the user who created it. Note that this also allows users to redeem it as a sharing link of the form https://sandstorm.example.com/shared/$API_TOKEN.

  • clipboardButton: String (optional) to display a copy-to-clipboard button in either the top left or top right corner of the IFRAME. Valid values are left and right. Left unspecified, no button is shown.

WebKeys

Sandstorm users can directly create an API token by clicking on the key icon within the Sandstorm top bar. This creates a webkey, which is a combination of an endpoint URL and an API token separated by a #. An example is:

https://alpha-api.sandstorm.io#49Np9sqkYV4g_FpOQk1p0j1yJlvoHrZm9SVhQt7H2-9

This format is intentionally chosen to look like a valid URL that could be opened in a browser. Eventually, when such a URL is loaded directly in a browser, Sandstorm will show the user information about the API and possibly offer the ability to explore the API and initiate requests for debugging purposes. As of this writing, these features are not yet implemented.

The part of the webkey before the # is the API endpoint for the server (in this case, for alpha.sandstorm.io). After the # is the API token.

How to provide the API token with a request

There are two main ways to provide the API token to Sandstorm.

  • Recommended: OAuth 2.0-style Bearer header. You can pass an Authorization: Bearer foo header with the HTTP request, replacing foo with the API token. For example:
curl -H "Authorization: Bearer 49Np9sqkYV4g_FpOQk1p0j1yJlvoHrZm9SVhQt7H2-9" https://alpha-api.sandstorm.io/
  • HTTP Basic auth. You can use any username so long as you provide the API token as the password. For example, https://anything:DT5hkM18CejvQomjIM1AVT4zqQdOdoFCid898bP2hQS@api-d9bc3de0bed9cb9b321d3c491c10dbca.alpha.sandstorm.io/.

We recommend the Bearer header option because web browsers will not cache the API token (unlike Basic auth), and because one cannot accidentally use the token in a web browser.

WebSockets

Unfortunately, WebSockets have no way to set headers in most languages (specifically Javascript). To work around this, you must pass the token as part of the URL path. It must be at the beginning of the path and of the form:

/.sandstorm-token/<token>

For example:

wss://api-qxJ58hKANkbmJLQdSDk4.oasis.sandstorm.io/.sandstorm-token/RfNqni4FEHXkWC5B8v6t/some/path

The "/.sandstorm-token/<token>" part of the path will be stripped, and the remaining segment of the path will be passed onto your app.

API hostnames

Each API token has a unique subdomain of the server where it is allowed. This ensures that in the unlikely case where if a web browser connects to the API endpoint, and if the browser ignores the Content-Security-Policy header provided by Sandstorm, grains cannot communicate by storing data in the browser that the other can read.

There is also a generic API hostname that allows all API tokens. However, if you make a request to the generic API hostname using HTTP Basic auth, then those requests are subject to a whitelist of non-web-browser User-Agent strings. Therefore, it is vastly easier to configure HTTP clients to use the token-specific hostname.

Header modification by Sandstorm

Sandstorm sanitizes HTTP request and response headers it does not recognize, and adds a few response headers.

  • Sandstorm applies a CORS header of Access-Control-Allow-Origin: * to allow Javascript on any domain to interact with the app's API. This is safe because the API token serves as the access control.

  • Sandstorm applies a Content-Security-Policy: default-src "none"; sandbox header to ensure that if you visit an API host within a web browser, the browser will prevent the API host from reaching other domains. This helps keeps the app confined.

  • Sandstorm removes cookie-related headers because cookies are not allowed within the Sandstorm HTTP API system. The API token should be used for access control.

  • Sandstorm applies a HTTP request and response header whitelist because some HTTP headers modify the meaning of the request, and we do not have a full list of safe headers. If your app needs HTTP headers that Sandstorm does not support, please file an issue similar to this one and accept our apologies.

If you run into trouble with Sandstorm's header modification, please email the sandstorm-dev Google Group.

Getting the user IP address

One special request header modifies Sandstorm's privacy defaults. By default, Sandstorm removes the user's IP address from API requests. The API client code can request that Sandstorm pass the IP address to the grain by setting a request header on the API call:

X-Sandstorm-Passthrough: address

When the app receives the request from Sandstorm, it will be enriched by a X-Real-IP request header containing the API user's IP address, assuming it is using sandstorm-http-bridge.

If you want to capture IP address by default for your app, you can distribute sample Javascript code with the app that sets the X-Sandstorm-Passthrough header on XMLHttpRequest calls. One app that does this is Piwik.