Reaching Outside Your App

While Cloud Apps are meant to exist as stand-alone apps which interact with Alma, there are times where an app has to reach outside and interact with external services. For example, your app may retrieve data from a webservice or show content from an external website. In order to provide a secure, trusted environment, Cloud Apps are “sandboxed,” meaning they cannot by default reach outside without specifying the app’s requirements explicitly in the manifest.

In this tutorial, we’ll walk through the configuration required to call the Hathitrust Bibliographic API to retrieve information about volumes in their repository. This tutorial contains the following sections:

First, we’ll follow the instructions in the “Adding additional routes” tutorial to add a new component and route called “external”.

Connect Source

Our component template includes a simple web form which includes a select box of the identifier types supported by the Hathitrust API and a text box for the identifier. The search button is hooked up to a search method.

external.component.htmlView on Github

The second part of our template includes the display for the data we retrieve from the webservice.

external.component.htmlView on Github

Our search method calls the Hathitrust API and maps the response into an object that we store in a record property of the component. This is what the template uses to display the response from Hathitrust.

external.component.tsView on Github

 

The interesting part of this example, though, is the manifest file. Without adding the appropriate configuration in the manifest, we will not be able to make the outbound call to Hathitrust. We would see an error message similar to the following in the browser console:

Refused to connect to 'https://catalog.hathitrust.org/api/volumes/brief/isbn/0394530934.json' because it violates the following Content Security Policy directive: "connect-src 'self'".

To resolve this error, we’ll add the following to our manifest.json file:

"contentSecurity": {
   "connectSrc": [
      "https://catalog.hathitrust.org/"
   ]
}

Now our app is able to make calls to endpoints which begin with https://catalog.hathitrust.org/.

Browser Sandbox

In our template we include a link to the record view in the Hathitrust catalog which will open in a new browser tab. By default, Cloud Apps are sandboxed and cannot open popup windows without explicitly setting the configuration in the manifest. So within the contentSecurity section we add a sandbox attribute as follows:

"sandbox": {
   "popups": true
}

Now clicking on the link will open the page in a new tab.

Other properties

Other properties of the contentSecurity section of the manifest allow us to reach outside of our app in different ways, including:

  • Displaying content from other websites in an iframe with the frameSrc property
  • Displaying modals (confirm, prompt) or pop-up windows with the sandbox property.

See the manifest documentation for the full details.

Authentication Token

To communicate with external services that require authentication, the Cloud Apps API provides an authentication token as a signed JSON Web Token (JWT). For technical details on the token and how to verify it, see this documentation. Your app can use the authentication with a custom service that you have written. If you’re using an existing service that requires credentials, you can create a proxy that validates the token to ensure the request came from your Cloud App, and then forward the request to the third-party service with the required credentials.

In this part of the tutorial, we will be using the Hathitrust Data API. Unlike the Hathitrust Bibliographic API, the data API requires authentication with credentials that can be obtained at the Hathitrust key service. We’ve created a proxy which accepts requests and adds OAuth 1.0a signature parameters before forwarding the request to the Hathitrust API. We’ve also created an authorizer function which validates a Cloud App token. With this combination, we can now make the authenticated API available to our app to retrieve metadata about a record including the number of pages, and display the scanned pages from the Hathitrust repository.

We’ve added a call to the getAuthToken method of the Events Service in our init function. We then display a field for the Hathitrust identifier and a button to call the data API. In the dataApi method, we then create an Authorization header with the keyword Bearer and our token. We send that token along with our request to the Data API proxy. We parse the response and use it to populate a list of page scan images to display. We also display a button which opens the images in a lightbox. The images are retrieved using the same Data API proxy and authentication header.

external.component.tsView on Github

Of course, we need to add the URL of our service to the connectSrc property of the manifest, as we outline above.

We can use this pattern to communicate with any external service. You can reuse the Hathitrust proxy for your own institution using your credentials. If you’re using the AWS API Gateway to host your proxy, you can reuse the authorizer function. See the readme for more details on how to deploy the service to your environment.

Generic Proxy

Browser security prevents an API endpoint to be called from one domain to another unless the API presents the proper CORS headers. In many cases, even publicly available APIs don’t provide the required headers and therefore can’t be used as-is from Cloud Apps. Such APIs can be called via a proxy which adds the CORS headers to the response from the API. The code in this Github repository can be deployed to AWS and used to call any API from a Cloud App. In addition, we have deployed the proxy to our own AWS account and it’s available for use at the URL https://api.exldevnetwork.net/proxy/. The README file includes instructions for how to call the proxy from your app, including the need to add the Cloud App authentication token as described above.

Post Message from Popups

You may have a requirement to communicate with windows from other applications. For example, you may wish to link out to a payment service or other flow and receive a message when the flow has been completed. Windows of different origins can speak to each other via the postMessage strategy. In this example, we’ll open a popup window in another domain. The user will click a button which in turn will call a function which posts a message back to our Cloud App. Our Cloud App is listening for messages from the popup, and responds when it receives one.

First, we’ve built a simple popup which includes the following HTML:

<html>
  <head>
    <script>
      function process(val) {
        window.opener.postMessage({ tutorialReponse: { val: val }}, "*");
        window.close();
      }
    </script>
  </head>
  <body>
    <h1>Cloud App Tutorial Pop Up</h1>
    <p>This page represents an external app which can interact with your Cloud App.</p>
    <p>Click a button below and see the results in the Cloud App.</p>
    <div>
      <button onclick="process(false)">Disagree</button>
      <button onclick="process(true)">Agree</button>
    </div>
  </body>
</html>

In our Cloud App, we add a method in the component.ts file which listens for messages. If we get a message we recognize, we set a local property which in turn updates the view.

external.component.tsView on Github

A note about origins: Since Cloud Apps run in sandboxed iframes, we cannot limit messages to a particular target origin. Hence our code above calls postMessage with "*" as the second parameter. And in the listener we need to check that the message is in a form we expect.