OAuth code flow implementation using Node.JS and Dropbox JavaScript SDK

// By Ruben Rincon • Mar 13, 2019

In this blog post we show you how to implement an OAuth authorization code grant flow using Node.JS and Dropbox JavaScript SDK that works with multiple users. The code presented here is for a development environment, as you need to carefully implement security measures not considered in this blog post when using a production environment. 

We will walk you through the configuration of a Dropbox application, installing and configuring the libraries needed for the project, and finally the code implementation that supports the OAuth authorization code grant flow using JavaScript. If you are unfamiliar with OAuth, we recommend you to read first the Dropbox OAuth guide.

Configuring a Dropbox app

You'll need to have a Dropbox account to access the APIs. If you don't already have one, you can sign up for a free account here. Then go to the Dropbox App Console and click "Create App".

Choose Dropbox API, App Folder access type, enter a unique name and click on "Create app". For more information about the App console and access type, visit our Getting Started guide.

Creating an app in the Dropbox developer's App Console
Creating a Dropbox app in the App Console

Now enable additional users in this app. In the settings page of your app you will find a button to do this. This step is important if you want users different to the owner of the app to be authorized.

Image of the App Settings page of the developer console, which contains the "Apply for production" button
Apply for production from your App Settings page

Pre-register a redirect URI in the Dropbox admin console. For dev purposes, you can use localhost which is the only permitted http URI. So enter http://localhost:3000/auth and press the "Add" button.

Image of the OAuth whitelist on your App Setting page
Whitelist localhost as a redirect URI on your App Settings page

Setting up your project

To install Node.JS, go to Nodejs.org and get the latest version. For this project, we need support for EcmaScript7 (ES7) so any version above 8.2.1 will work. 

After Node.JS is installed in your local dev environment, create a project structure using Express generator. We will use Handlebars as the template engine which leverages actual HTML. 

For more information about Express, visit their website. We also offer a short explanation about an Express project structure in this tutorial

First install express generator running:

npm install express-generator -g

Now create a project called "dbxoauth" using the handlebars template engine. Run the following command:

express --hbs dbxoauth

Then, navigate to the folder created and install dependencies:

cd dbxoauth
npm install

We will be using the following node libraries:

  • dotenv: to avoid hardcoding sensitive data such as keys and secrets
  • express-session: to enable Web sessions used to store OAuth tokens and avoid the need to authenticate each time, as well as adding support to multiple users
  • dropbox: Dropbox JavaScript SDK
  • isomorphic-fetch: library required by Dropbox JavaScript SDK
  • crypto: to generate random strings
  • node-cache: to store data in local cache

Run the following command to install the libraries above: 

npm install dotenv express-session dropbox isomorphic-fetch crypto node-cache --save

It is important not to hardcode any sensitive information. What you can do is create an .env file where you place sensitive data and make sure that file is never uploaded to a remote repository independently of the version control system you use. If you are using git, then you will add the .env to the .gitignore file. The dotenv library will read values from the .env file and add them to the current process. You can access these values via process.env.<variable_name> 

To load the values in the .env file, add this code as the first line of your app.js file:

require('dotenv').config({silent: true});

And create a .env file at the project root level with the following content:

DBX_APP_KEY=<your_app_key_from_developer_console>
DBX_APP_SECRET=<your_app_secret_from_developer_console>
SESSION_ID_SECRET=<create_your_own_secret>

Web sessions will provide a mechanism to store access tokens and avoid the need to authenticate each time, additionally are a key element to handle multiple users. Let’s configure the middleware that enables Web sessions. For this, we use the express-session library. Whenever a browser makes a request to the server, a session for that user is retrieved and a cookie on the client will hold a reference to that session.

In the app.js file, right after the app variable is assigned (var app = express();) add the following code:

//session configuration
const session = require('express-session');
 
let session_options = {
  secret: process.env.SESSION_ID_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { secure: false } //only for dev purpose
}
app.use(session(session_options));

This is minimal initialization that sets a cookie when the browser first hits the server and allows to use non-secure http connections for development purposes only. Notice that session data is not saved in the cookie itself, just the encrypted session ID. Session data is stored server-side. The SESSION_ID_SECRET is used to sign the session ID cookie.

Note: If you plan to move to a production environment you need to take special attention to this configuration. You can learn more about this library in their GitHub repository. It also uses by default MemoryStore that according to their site “Is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing”.  Additionally, whenever you stop the server, the MemoryStore is cleared out. You may consider using Redis as we explain in this other tutorial.

OAuth code flow

We will implement an OAuth authorization code grant flow with the below following sequence.

Image of the authorization code Oauth flow (also known as the 3-legged OAuth flow)
Diagram of Dropbox's authorization code (3-legged) OAuth flow

When a request is received in the home route /, we check if the session for that user has already a token stored in it, if that is the case, we can use the token directly to get resources from Dropbox, if not, then we need to get a token. To do that, we generate a random state using the crypto library and the node-cache library to save in cache for a few minutes with the state as key and the session id as value. Then we use the Dropbox JavaScript SDK to obtain the authorization URL and redirect the user to that address. This will handle multiple users as each user will have a different state and session id. If the user takes too long to validate credentials and authorize the app with Dropbox, the cache will expire, adding an extra layer of security.

After completing authentication and authorization with Dropbox, the user will be sent back to the redirect URL along with the state string we sent and a code.  We validate the state checking that there was a session id for it and use the Dropbox JavaScript SDK to exchange the code for a token. Finally, the token received will be saved within the current session so next time the user reaches the home page /, they will be able to use Dropbox resources as long as their token is still valid, bypassing the need for authorization.

To implement this flow, we need to add two routes to /routes/index.js. You can simply replace the whole file with the following code:

// /routes/index.js
var express = require('express');
var router = express.Router();
const controller = require('../controller');
 
router.get('/', controller.home); //home route
router.get('/auth', controller.auth); //redirect route
 
module.exports = router;

Now add the controller. Create a controller.js at the project root level with the following code:


const crypto = require('crypto');
const NodeCache = require( "node-cache" );
const Dropbox = require('dropbox').Dropbox;
const fetch = require('isomorphic-fetch');

//Redirect URL to pass to Dropbox. Has to be whitelisted in Dropbox settings
const OAUTH_REDIRECT_URL='http://localhost:3000/auth';

// Dropbox configuration
const config = {
  fetch: fetch,
  clientId: process.env.DBX_APP_KEY,
  clientSecret: process.env.DBX_APP_SECRET
};

var dbx = new Dropbox(config);
var mycache = new NodeCache();

module.exports.home =  async (req, res, next)=>{
  if(!req.session.token){
    //create a random state value
    let state = crypto.randomBytes(16).toString('hex');
    // Save state and the session id for 10 mins
    mycache.set(state, req.session.id, 6000);
    // get authentication URL and redirect
    authUrl = dbx.getAuthenticationUrl(OAUTH_REDIRECT_URL, state, 'code');
    res.redirect(authUrl);
  } else {
    // if a token exists, it can be used to access Dropbox resources
    dbx.setAccessToken(req.session.token);
    try{
      let account_details = await dbx.usersGetCurrentAccount();
      let display_name = account_details.name.display_name;
      dbx.setAccessToken(null); //clean up token

      res.render('index', { name: display_name});
    } catch(error){
      dbx.setAccessToken(null);
      next(error);
    }
  }
}

// Redirect from Dropbox
module.exports.auth = async(req, res, next)=>{

  if(req.query.error_description){
    return next( new Error(req.query.error_description));
  } 

  let state= req.query.state;
  if(!mycache.get(state)){
    return next(new Error("session expired or invalid state"));
  } 

  if(req.query.code){
    try{
      let token =  await dbx.getAccessTokenFromCode(OAUTH_REDIRECT_URL, req.query.code);
      // store token and invalidate state
      req.session.token = token;
      mycache.del(state);
      res.redirect('/');
    }catch(error){
         return next(error);
    }
  }
}

Finally, modify the home page to display the information retrieved from Dropbox. When a user successfully authenticates and has a valid token, we get the display_name using the Dropbox JavaScript SDK and show it on the page. Replace the /views/index.hbs with this code:

<!-- /views/index.hbs -->
<h1>Logged in!</h1>
<p>Hello {{name}}</p>

You can now start the server locally on your machine

npm start

And go to http://localhost:3000 in your browser. You will go through the authentication, authorization flow and finally to a page that shows your name retrieved from Dropbox.

Images of each stage of the authorization code OAuth flow
Images of each screen your users will see during an authorization code (3-legged) OAuth flow

🎉 Congratulations! You have successfully implemented an OAuth authorization code grant flow.

Where to go from here

W e recommend checking out the Dropbox OAuth guide for more information on OAuth. If you’re interested in more Dropbox tutorials, check out our previous blog posts, which contain guides to:

  1. Writing a script for a simple expense organizer app
  2. Writing a photo gallery Web Service from scratch with Node.JS and Dropbox with production deployment on Heroku
  3. Photo gallery tutorial with tags using the File Properties API

Have ideas for other guides you would like to see? Let us know by posting in the Dropbox Developer Community forums

If you have any questions about this or need help with anything else, you can always contact us here.

Build with Dropbox today at   www.dropbox.com/developers


// Copy link