Box UI Elements in practice using Node, Express, and Pug

Jonathan LeBlanc
Box Developer Blog
Published in
4 min readAug 4, 2017

Several weeks ago we launched Box Elements, allowing you to embed highly customizable UI components into your site or app. I like looking at features as a complete end-to-end whole, so that’s exactly the question we’re going to try to answer below: how do we build an end-to-end integration of the Box UI Elements?

Content Explorer UI Element in Action

As you go through the samples and docs, you’ll notice pretty quickly that you need to drop an access token directly into your front-end code. So, there are a few things we need to consider:

  • We’ll need a server-side component to generate the access token, because exposing our keys on the front-end is how your app ends up in the news…and not in the good way.
  • We have to downscope the access token so that it only allows access to features you want in the UI Element, and not, let’s say, complete account access. This keeps us nice and safe.

For those who want to skip ahead and look at just the code instead, the full application sample and setup instructions are available here.

For this guide, I’m assuming that you’ve already set up a JWT / OAuth application and have generated your private / public key pair. If you haven’t, embedded in the Github sample are some steps on how to do that.

Let’s walk through some of the core mechanisms of the application, starting with our packages and template setup.

// Initialize packages
const app = require('express')();
const appConfig = require('./config.js');
const BoxSDK = appConfig.boxSDK;
const fs = require('fs');
const http = require('http');
require('pug');
app.set('views', './templates');
app.set('view engine', 'pug');

Within the includes, we’re doing a few things:

  • Adding in Express for the server, Pug for the templates (formally Jade), and the file system for reading in our private key file.
  • Including the config.js file that houses all of our keys and configuration data (you’ll want to change this to your specific app values). This file is automatically downloaded to your machine when you generate a private / public key pair in the Box Developer Console
  • Adding the Box Node SDK for setting up the application.

We then set the location of our pug templates (the ./templates folder), and set pug as our view engine.

Our next block is to setup the Box Node SDK.

// Fetch private key for signing the JWT
let secret = '';
try {
secret = fs.readFileSync(appConfig.privateKeyPath);
} catch (err) {
console.error(err);
}
// Instantiate new Box SDK instance
const sdk = new BoxSDK({
clientID: appConfig.jwtClientId,
clientSecret: appConfig.jwtClientSecret,
appAuth: {
keyID: appConfig.publicKeyId,
privateKey: secret,
passphrase: appConfig.keyPass
}
});
// App client auth
const client = sdk.getAppAuthClient('enterprise', appConfig.enterpriseId);

We read in the private key from wherever it’s stored, create a new instance of the Box SDK, passing in all required credentials and key password, then set up a new client.

Now comes the step where we decide which template to render. We’re setting up the Express app to route traffic that goes to /elements/ELEMENT_NAME to its appropriate template, so our URI structures would look something like this if the server is running atlocalhost:3000:

// Render element via serviceName = contentExplorer, contentPicker, contentPreview, contentUploader
app.get('/elements/:serviceName', (req, res) => {
const service = req.params.serviceName;
// Downscope token
client.exchangeToken(appConfig.tokenScopes[service]).then((tokenInfo) => {
// Render pug template with token and ID
res.render(service, { at: tokenInfo.accessToken, fid: appConfig.elementIds[service] });
}).catch((err) => {
console.error(err);
});
});
// Create server
http.createServer(app).listen(3000, () => {
console.log('Server started: Listening on port 3000');
});

Once we get the UI Element name to render, we need to call client.exchangeToken(...) to downscope the token. Essentially this is just restricting what the access token has access to in order to make it safe for embedding into the front-end code.

The first argument, appConfig.tokenScopes[service] pulls the string of scopes for the given component, which is stored in config.js.

Once done, we render the template for that UI Element, passing in the downscoped token (at) and the file or folder ID to use for the component (fid). For instance, we would pass the ID of the folder we want to display for the content explorer.

If you head over to the config.js file in the sample app, you can see all of the possible UI Elements specific scopes for each feature, but for the sake of completeness, here are all possible scopes for the downscoped token, linked to their respective scope pages:

Lastly, let’s take a look at one of the pug templates.

doctype html
html(lang='en')
head
title Box Content Explorer
script(src='https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Intl')
link(rel='stylesheet', href='https://cdn01.boxcdn.net/platform/elements/1.0.2/en-US/explorer.css')
body
div(class='container', style='height:600px')

script(src='https://cdn01.boxcdn.net/platform/elements/1.0.2/en-US/explorer.js')

script(type="text/javascript").
var folderId = '#{fid}';
var accessToken = '#{at}';
var contentExplorer = new Box.ContentExplorer();
contentExplorer.show(folderId, accessToken, {
container: '.container'
})

This particular template is for the Content Explorer Element. if you look at the sample HTML provided on that Element’s page, you’ll see that all the template is a “pug-ified” version of that HTML, with the folderId and accessToken variables replaced with the values that we send when rendering the template.

That’s all there is to it. Most of the code is built around routing the service to the right template, but other than that we’re just creating a new Box instance and downscoping the token.

If you have any questions along the way, feel free to reach out to us on the forum, or reach out to me directly on Twitter.

--

--

Jonathan LeBlanc
Box Developer Blog

Emmy award winner, O'Reilly author, open source contributor, Senior Director of Developer Advocacy at Box.