Using an API Key to read data

This guide will show you how to secure an API Key which will be used by your application to make authenticated requests. We will be using Mailchimp in the role of the external provider.

The application we will build during the course of this guide will show if the ticket owner's subscriber status from Mailchimp and will add a sidebar where this data will be displayed.

Obtaining a Mailchimp API key

In order to make requests to the Mailchimp API we need to authenticate using an API key. Mailchimp makes it very easy to create new API keys. If you do not have a Mailchimp account, sign-up for a free account here: https://login.mailchimp.com/signup/ .

Once you sign-in, head over to the Account section in Mailchimp and create an API key. Here is a good Knowledge Base article explaining how to find or generate an API key: https://kb.mailchimp.com/integrations/api-integrations/about-api-keys#Find-or-Generate-Your-API-Key

It is not a good idea to hardcode any credentials in your source code, instead we recommend using the Settings API so the person installing the application can provide them. This guide will show you how to create an installer which will prompt the administrator for the Mailchimp API key when installing your app

Configuring the application

First clone the boilerplate repository:

    git clone https://github.com/deskpro/apps-boilerplate dev-guide-api-key
    cd dev-guide-api-key
    rm -rf .git && git init .

Open the file package.json and edit the following properties to replace references to the boilerplate application

{
  "name": "dev-guide-api-key",
  "version": "0.1.0",
  "description": "My first app",
  "deskpro": {
    "title": "dev-guide-api-key"
  }
}

Now install the dependencies:

  npm install

Now it is time to add our settings to the application manifest. Open again the file package.json if you don't have it open already, and modify the following properties

{
  "scripts": {
    "package": "dpat clean . && dpat compile . && dpat bundle-installer . && dpat verify ./dist && dpat package ."
  },
  "deskpro": {
    "settings" : [
      {
        "name": "apiKey",
        "defaultValue": "",
        "title": "Mailchimp API Key",
        "required": true,
        "type": "text"
      }
    ],
    "storage": [
      {
        "name": "apiKey",
        "isBackendOnly": true,
        "permRead": "EVERYBODY",
        "permWrite": "OWNER"
      }
    ],
    "externalApis": [
      "/^https?://([^.]+\\.)*mailchimp.com/?.*$/"
    ]    
  }
}

What we did here is declare a required setting named apiKey, displayed as a text field. We also configured the storage item for this setting's value (notice both entries in the settings and storage lists have the same name) since we need this value to make request to the Mailchimp API

Since the API Key a credential which should not be made public, in the storage item declaration we made the value a protected value, by setting "isBackendOnly": true. This means the value will not be available via the REST API and thus not available in the client code where it might be leaked. The server-side Proxy is the only one who will have access to this value.

We did however allow any authenticated agent to use this value to make requests to the Mailchimp API by setting "permRead": "EVERYBODY" but nobody else aside the admin who installed the application will be able to change it because of "permWrite": "OWNER"

This is a the recommended setup for storing configuration values which are used by all users but who must remain private due to their sensitivity.

Since we will be making HTTP requests to third parties through the server-side Proxy, we need to declare

We also modified the package command to include the installer in our application bundle because we want to show a nice user interface for our settings when the application is installed. The installer will read our settings and will generate a form so the admin can provide the values we need, in this case the Mailchimp API Key you obtained from the previous step.

Let's package the application and install it:

  npm run package

Notice a file dist/app.zip has been created. That means the packaging is done, now we can install the application.

Head over to the Admin section of your Deskpro installation, open the Apps menu and choose Upload App. Click the green Select the app ZIP file button, navigate to the dist/app.zip in the folder where you initially cloned the boilerplate repository and finish the upload. You will be presented with a screen showing your application title and version and a green button. Click the green button to begin the install procedure. You should see a similar screen:

This screen is created by the installer just from reading the settings definition from the application manifest.

Copy the Mailchimp API key in the text field named API Key then click the green button to finish the install procedure. You should see a similar screen:

Notice the Dev Mode button in the top right corner. This button allows you to run your application in development mode, so you can see the changes as you write them without constantly installing the application.

After refreshing the page, run the following command to start the development server:

  npm run dev

Now click the Dev Mode mode button which will open a new tab. Click on a ticket to open up the side bar where the application is now running.

We have now seen how to add settings to our application that will allow administrators to configure the application. We have also seen how to secure those values and how to install and start our application in development mode. Next we will retrieve data from Mailchimp's API.

Building the application

To display the Mailchimp Subscriber Status we will first need to resolve the ticket owner's email address. Then, when talking to the Mailchimp API, we will have to use the server-side Proxy since we do not want to disclose our API Key. The server-side Proxy is useful not only for preserving secret values, but also when dealing with remote API's which are not CORS-enabled, which is also the case for Mailchimp

Retrieve ticket owner's email

To retrieve the data about the ticket's owner we need first an email address. Open the file src/main/javascript/App.jsx and add the following method to the App class:

  /**
  * @returns {Promise.<String, Error>}
  */
  readTicketOwnerEmail()
  {
    const { context } = this.props.dpapp;
    return context.getTabData().then(tabData => {

      const { emails, primary_email } = tabData.api_data.person;

      if (primary_email && primary_email.email) {
        return primary_email.email;
      }

      if (emails.length) {
        return emails[0].email;
      }

      return Promise.reject(new Error('could not find the email of the ticket owner'));
    });
  }

Retrieve the subscriber status

Next let's add a method that reads the subscriber status:

  /**
   * @param {string} email the subscriber email
   * @returns {Promise.<String, Error>}
   */
  readSubscriberStatus(email)
  {
    const { restApi } = this.props.dpapp;

    return restApi.fetchCORS(
      `https://us16.api.mailchimp.com/3.0/search-members?query=${email}`,
      {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json' ,
          'Accept': 'application/json' ,
          'X-Proxy-SignWith': 'basic_auth anystring:{{apiKey}}' 
        }
      }).then(response => {
        const { members } = response.body.exact_matches;
        if (members.length) {
          return members[0].status
        }
        return  null;
    })
    ;
  }

Quite a lot is happening here. First of all, Mailchimp's API is not CORS enabled so we need to use the server-side Proxy. This is possible by using the DeskproAPIClient, specifically via the fetchCORS method.

This method accepts as parameters a request url and a request configuration object similar to the one accepted by the global fetch function from the Fetch API

You may have noticed the X-Proxy-SignWith header which is a Proxy header, meaning it will not get forwarded to the external third party API. It contains instructions on how to sign the request and in our case we are instructing the proxy to add an Authorization: Basic <token> header to the final request were <token> is the base64 encoded value of anystring:{{apiKey}}

You may have also noted the {{apiKey}} syntax which denotes a request placeholder. This is how we instruct the Proxy to inject values of protected storage items at key places into the request.

Your Mailchimp API Key which has the following format: TOKEN-DATA-CENTER. You should check if the last part of your API Key is us16. If it is not, then replace us16 in the your request url with the DATA-CENTER part of your API Key.

For example, if your Mailchimp API key would be '0a000000000000000000000000abcded-us12', the DATA-CENTER part would be us12 and the request url would be:

  `https://us12.api.mailchimp.com/3.0/search-members?query=${email}`

Finishing up the application

Finally let's connect the two previous methods. Add the following method, which saves the ticket's status:

  componentDidMount()
  {
    this.readTicketOwnerEmail()
      .then(email => this.readSubscriberStatus(email))
      .then(status => {
        this.setState({ status: status || 'unknown'  })
      })
    ;
  }

And replace the render method with the following:

  render() {
    const { status } = this.state || {} ;
    return status ? (<p style={{textAlign: "center"}}> Member status: <b>{status.charAt(0).toUpperCase() + status.slice(1)}</b></p>) : null;
  }
}

Now open another ticket to run the full application in development mode:

To test the application with some real values, head over the Mailchimp API Playground and create a list with one member using your own email then create a ticket. Open the ticket and the application will show you the status in the ticket's sidebar.

Final recap

We have used many powerful features of the Deskpro Apps Framework:

  • we added settings that allow administrators to configure the application before it is installed

  • we learned how to protect sensitive information

  • we learned how to use the server-side Proxy to make request to third parties and how to use request placeholders to inject values from storage into the final request

Last updated