Implement OAuth into your Express, Koa or Hapi applications using Grant

Free Course

Build Your First Node.js Website

Node is a powerful tool to get JavaScript on the server. Use Node to build a great website.

What does OAuth mean?

There are a few things we need to know to be able to use OAuth in our applications.

OAuth is used by the web service providers to provide access to user resources in a secure manner. Each developer using this service must create an OAuth application and, after, requires the user to grant access to it.

As a part of the OAuth flow, the user is redirected to the web service’s server, to log in (this step can be omitted if the user has already logged in).

img-facebook-login The next screen is where the user may be asked to grant access to specific parts of his user information located in the service.

In this article, our application is called Grant OAuth and we’ll be asking for access to the user’s groups and likes.

img-facebook-grant We can click on the Edit the info you provide button to view the detailed information about the permissions we’re giving to that application.

img-facebook-permissions Once the user agrees to use our application, he’s redirected back to our web site with the generated access token. We need to store that token and send it along, with each request made, to the service on behalf of the user.


Grant

Luckily, there are already great tools available to help us integrate OAuth into our web application, therefore we won’t need to know each and every detail of the OAuth specification.

In this article, we’ll walk through the Grant module configuration and usage – the easiest way to add support for multiple web services, using OAuth, for your application.

Grant is OAuth middleware for Express, Koa and Hapi. Express is a web framework for NodeJS, which is often used in web sites built with NodeJS. It gives us support for middlewares, which are mini applications that can be plugged into our own application to perform specific tasks.

One of the main goals of Grant is to enable us to add support for multiple providers through a simple and straightforward configuration data structure. This is in contrast with other popular authentication middlewares for Express, such as Passport, where managing multiple providers in a single application can be cumbersome.

This allows us to easily implement login forms with support for multiple options so our users can log in using different service providers. A quick showcase of what can be done, easily, with Grant can be seen in the showcase site here.


Register OAuth Application

The first thing we need to do is to create our OAuth application. This can be done on the web service provider’s web site which we will want to use.

Twitter

For OAuth1 we need to know our application’s consumer_key andconsumer_secret

img-twitter-app

The next thing we need is the callback URL, or also known as the redirect URL. This is where the user will be redirected back to our server, after being granted access to our application.

img-twitter-redirect

Facebook

For OAuth2 we need to know our application’s client_id andclient_secret. Again, we will need to know where to redirect the user after having been granted access to our application. In this case Facebook requires us to specify only the domain of our web site.

img-facebook-app

To Sum it Up

In short, the minimum required information to configure OAuth properly is:

  1. consumer_key and consumer_secret or client_id and client_secret
  2. redirect or callback URL pointing back to our website

Grant requires that the redirect/callback URL specified in our OAuth application should have this format:


[protocol]://[host]/connect/[provider]/callback

For example, that could be:


https://myawesomesite.com/connect/twitter/callback

This route located on our web server is used internally by Grant to perform the last step of the OAuth flow, before we’re redirected to the path specified in our configuration’s callback key (see callback configuration option below).

Configure Express

The next thing we need is a configuration. Once we have the credentials for our OAuth application we can add them to the Grant’s configuration data structure.

config.json


{
  "server": {
    "protocol": "http",
    "host": "dummy.com:3000"
  },
  "facebook": {
    "key": "[APP_ID]",
    "secret": "[APP_SECRET]",
    "callback": "/handle_facebook_callback",
    "scope": [
      "user_groups",
      "user_likes"
    ]
  },
  "twitter": {
    "key": "[CONSUMER_KEY]",
    "secret": "[CONSUMER_SECRET]",
    "callback": "/handle_twitter_callback"
  }
}

The server key is required and it contains the host name of our server, along with the protocol we’re running our site on.

We should keep in mind that not all web service providers allow localhost as a value for the OAuth application redirect URL. The easiest way to get around this is to add a new entry to our host’s file like this:


127.0.0.1    dummy.com

Then in our OAuth application we should specify: http://dummy.com:3000/connect/[provider]/callback for redirect URL.

Each key after that is a provider we want to configure and use in our application. In this case we want to configure and use both Twitter and Facebook. The key and the secret keys are required and they must contain the credentials for our OAuth application.

The callback key is the route on our server where we want to receive the access tokens after the OAuth flow is completed.

app.js

Our Express 4 application may look like this:


var express = require('express')
  , logger = require('morgan')
  , session = require('express-session')

var Grant = require('grant-express')
  , grant = new Grant(require('./config.json'))

var app = express()
app.use(logger('dev'))
// REQUIRED:
app.use(session({secret:'very secret'}))
// mount grant
app.use(grant)

app.get('/handle_facebook_callback', function (req, res) {
  console.log(req.query)
  res.end(JSON.stringify(req.query, null, 2))
})

app.get('/handle_twitter_callback', function (req, res) {
  console.log(req.query)
  res.end(JSON.stringify(req.query, null, 2))
})

app.listen(3000, function() {
  console.log('Express server listening on port ' + 3000)
})

In this example we’re utilizing a bunch of commonly used Express middlewares:


...
app.use(logger('dev'))
// REQUIRED:
app.use(session({secret:'very secret'}))
...

This is how we’ll require the Grant module and initialize it, by passing our configuration to it. (you need to install the Express middleware first npm install grant-express)


var Grant = require('grant-express')
  , grant = new Grant(require('./config.json'))

Then we mount the Grant middleware instance to our web application, just like any other middleware. It’s a good idea to mount Grant before your application specific middlewares.


app.use(grant)

Finally, we’ll need to define our routes that will handle the response data returned after the OAuth flow has been completed.


app.get('/handle_facebook_callback', function (req, res) {
  console.log(req.query)
  res.end(JSON.stringify(req.query, null, 2))
})

Here req.query contains all of the data returned after the OAuth flow.

Start the Flow

The only remaining thing is to start the server as usual.


$ node app.js

Next we’ll navigate to http://dummy.com:3000/connect/twitter in our web browser. Alternatively, to start the OAuth flow for Facebook we should navigate to http://dummy.com:3000/connect/facebook

img-twitter-connect

After the user grants access to our application, we receive the response data in our callback route.

img-twitter-callback

Once this is completed, we’re just printing back the information nicely formatted. In a real use case scenario, this is the place where we should store the user’s access token and access secret.

Configure Koa

Just as with the Express example above we need a configuration, containing our OAuth application credentials at minimum.

config.json

The configuration is exactly the same as in the Express example above:


{
  "server": {
    "protocol": "http",
    "host": "dummy.com:3000"
  },
  "facebook": {
    "key": "[APP_ID]",
    "secret": "[APP_SECRET]",
    "callback": "/handle_facebook_callback",
    "scope": [
      "user_groups",
      "user_likes"
    ]
  },
  "twitter": {
    "key": "[CONSUMER_KEY]",
    "secret": "[CONSUMER_SECRET]",
    "callback": "/handle_twitter_callback"
  }
}

The server key is required and it contains the host name of our server and the protocol we’re running our site on.

We should keep in mind that not all web service providers allow localhosts as a value for the OAuth application redirect URL. The easiest way to get around this is to add a new entry to our host’s file like this:


127.0.0.1    dummy.com

Then in our OAuth application we should specify http://dummy.com:3000/connect/[provider]/callback for redirect URL.

Each key after that is a provider that we’ll want to configure and use in our application. We want to configure and use both Twitter and Facebook. The key and the secret keys are required and they must contain the credentials for our OAuth application.

The callback key is the route on our server where we want to receive the access tokens after the OAuth flow is completed.

app.js

Our Koa application may look like this:


var koa = require('koa')
  , session = require('koa-session')
  , router = require('koa-router')
  , mount = require('koa-mount')
  , koaqs = require('koa-qs')
  , accesslog = require('koa-accesslog')

var Grant = require('grant-koa')
  , grant = new Grant(require('./config.json'))

var app = koa()
app.use(accesslog())
// REQUIRED:
app.keys = ['grant']
app.use(session(app))
// mount grant
app.use(mount(grant))
// other middlewares
app.use(router(app))
koaqs(app)

app.get('/handle_facebook_callback', function *(next) {
  console.log(this.query)
  this.body = JSON.stringify(this.query, null, 2)
})

app.get('/handle_twitter_callback', function *(next) {
  console.log(this.query)
  this.body = JSON.stringify(this.query, null, 2)
})

app.listen(3000, function() {
  console.log('Koa server listening on port ' + 3000)
})

This is how we require the Grant module and initialize it, by passing our configuration to it. (you need to install the Koa middleware first npm install grant-koa)


var Grant = require('grant-koa')
  , grant = new Grant(require('./config.json'))

In this example we’re utilizing a bunch of commonly used Koa middlewares:


...
app.use(accesslog())
app.use(session(app))
app.use(router(app))
...

More importantly we’re using the koa-mount module to mount Grant to our web application:


app.use(mount(grant))

Finally, we define our routes that will handle the response data returned after the OAuth flow has been completed.


app.get('/handle_twitter_callback', function *(next) {
  console.log(this.query)
  this.body = JSON.stringify(this.query, null, 2)
})

Here this.query contains all of the data returned after the OAuth flow.

Start the Flow

The only remaining thing is to start the server. Currently, to use Koa we need NodeJS version 11 or higher. Also, passing the --harmony flag is required to run ES6 features.


$ node --harmony app.js

Navigate to http://dummy.com:3000/connect/twitter to initiate the flow for Twitter, or navigate to http://dummy.com:3000/connect/facebook to authenticate with Facebook.

img-facebook-connect

After the user grants access to our application, we’ll receive the response data in our callback route.

img-facebook-callback

Once this is complete, we’re just printing back the information nicely formatted. In a real use case scenario, which is the place where we should store the user’s access token and the refresh token (if present).

Configure Hapi

Using the same configuration we may use Hapi instead. (you need to install the Hapi middleware first npm install grant-hapi)

app.js


var Hapi = require('hapi')
  , yar = require('yar')

var Grant = require('grant-hapi')
  , grant = new Grant()

var server = new Hapi.Server()
server.connection({host: 'localhost', port: 3000})

server.route({method: 'GET', path: '/handle_facebook_callback', handler: function (req, res) {
  console.log(req.query)
  res(JSON.stringify(req.query, null, 2))
}})

server.route({method: 'GET', path: '/handle_twitter_callback', handler: function (req, res) {
  console.log(req.query)
  res(JSON.stringify(req.query, null, 2))
}})

server.register([{
  register: grant,
  options: require('./config.json')
}, {
  register: yar,
  options: {
    cookieOptions: {
      password: 'password',
      isSecure: false
    }
  }
}], function (err) {
  if (err) throw err
  server.start()
})

Here we’re importing the yar module which is required. Also the configuration is passed via the options key when registering the Grant middleware with our server. The rest of the logic is exactly the same as in the Express and Koa examples.

More Configuration Options

There are a few more configuration options that we might want to use.

Scope

Some service providers allow us to configure the permissions that our application will request from the user. These permissions are set inside the scope key of the Grant’s configuration:


{
  "facebook": {
    "key": "...",
    "secret": "...",
    "scope": [
      "user_groups",
      "user_likes"
    ]
  }
}

In this case, the user will be asked to grant access to its groups and likes on Facebook.

img-facebook-grant

img-facebook-permissions

State

Some providers require us to send a CORS token to ensure that the flow was initiated from our own server. The state key in the Grant’s configuration can be used for that purpose:


{
  "facebook": {
    "key": "...",
    "secret": "...",
    "state": "very secret"
  }
}

To be able to generate and send unique CORS token with each request we must utilize the dynamic configuration of Grant.

Custom Authorization Parameters

Some providers can use additional parameters. For example, Google uses special parameter for requesting offline access from the user:


{
  "google": {
    "key": "...",
    "secret": "...",
    "scope": ["...", "..."],
    "access_type": "offline"
  }
}

As can be seen from the example, access_type is a special key defined outside of the reserved ones.

Different Development Environments

The advantage of having our entire OAuth related configuration in one single JSON file is that we can easily define the configuration needed for each of our development environments.

config.json


{
  "development": {
    "server": {
      "protocol": "http",
      "host": "dummy.com:3000"
    },
    "facebook": {
      "key": "app credentials for our development OAuth app",
      "secret": "app credentials for our development OAuth app"
    },
    "twitter": {...},
    ...
  },
  "staging": {
    "server": {
      "protocol": "https",
      "host": "staging.myawesomesite.com"
    },
    "facebook": {
      "key": "app credentials for our staging OAuth app",
      "secret": "app credentials for our staging OAuth app"
    },
    "twitter": {...},
    ...
  },
  "production": {
    "server": {
      "protocol": "https",
      "host": "myawesomesite.com"
    },
    "facebook": {
      "key": "app credentials for our production OAuth app",
      "secret": "app credentials for our production OAuth app"
    },
    "twitter": {...},
    ...
  } 
}

app.js

Now, here’s what it looks like if we run our application using the NODE_ENV environment variable:


$ NODE_ENV=production node app.js

Then in our app.js file we can have the following code:


var config = require('./config.json')

var conf = config[process.env.NODE_ENV||'development']

var grant = new Grant(conf)

Here we’re accessing the desired environment configuration based on the flag passed to our application.

Configuration Overrides

Often when we do a deeper integration with some large web service, we may have to ask the user for a different set of permissions, in different parts of our application. Grant allows us to easily override our configuration through custom keys.

config.json


"facebook": {
  "key": "...",
  "secret": "...",
  "scope": ["publish_actions", "publish_stream"],
  "callback": "/facebook/callback"
  
  "groups": {
    "scope": ["user_groups", "friends_groups"]
  },
  
  "pages": {
    "scope": ["manage_pages"],
    "callback": "/facebook_pages/callback"
  }
}

Here groups and pages are custom keys. They are required to be of type Object {} and they cannot be one of the reserved keys.

If we navigate to /connect/facebook we’ll be asked to grant publish permissions to our account, and after the OAuth flow we’ll get the results in the /facebook/callback route.

If we want from the user to give us permissions to his groups and friend groups instead, we should navigate to /connect/facebook/groups. Alternatively for managing the user’s pages we should navigate to /connect/facebook/pages, also the results of the OAuth flow in this case will be received in the /facebook_pages/callback route.

Everything that’s inside a custom object key in the provider’s configuration, overrides the values specified directly in this provider. For example both the groups and the pages override here will use the same key, secret, scope and callback unless we override these keys inside them. So in this case the groups override overrides only the scope specified for the facebook provider, and the pages additionlly to its scope also sets its own callback route to use.

For example the link to fire up the flow for our Facebook pages override might look like this.


<a href="/connect/facebook/pages">Click here to allow us manage your Facebook pages</a>

Dynamic Configuration

In some cases, the regular static JSON configuration is not sufficient. For example, if we want to generate unique CORS token for each OAuth2 flow separately.

Grant offers a dynamic configuration via POST request to the /connect/[provider] route.

On the Client


<form action="/connect/facebook" method="post" accept-charset="utf-8">
  <input name="state" type="text" value="" />
  <input name="scope" type="checkbox" value="read" />
  <input name="scope" type="checkbox" value="write" />
  <button>submit</button>
</form>

Here we have a simple HTML form that, once submitted, makes a POST request to the /connect/facebook route. It also sends a state parameter and one for scope. Therefore, if we enter a string in the state text field, and choose a few scopes through the checkbox fields, then they will be sent to the /connect/facebook route and they will override the state and the scope defined in our facebook configuration. The important thing is that this override is valid only for this request, it doesn’t modify our JSON configuration in subsequent requests. Take a look at the Grant’s showcase web site OAuth Playground which uses this approach.

This technique works for static overrides as well. For example, we might want to override some value in our pages override. In that scenario, all we’ll need to do is make a POST request to the /connect/facebook/pages route.

On the Server

In some instances, we might want to do something on our server before sending the POST request to the connect route.


...
var request = require('request')
var grant = new Grant(require('./config.json'))
...
app.get('/connect_facebook', function (req, res) {
  var url = grant.config.facebook.protocol + '://'
    + grant.config.facebook.host + '/connect/facebook'
  request.post(url, {
    form: {
      // generate random 6 digit number
      state: (Math.floor(Math.random() * 999999) + 1)
    },
    followRedirect: false
  }, function (err, _res, body) {
    res.set('set-cookie', _res.headers['set-cookie'][0])
    res.redirect(_res.headers.location)
  })
})

Here we’ll use the request module to make a POST request inside the /connect_facebook route of our Express application. Grant exposes its configuration through the config key in this instance.


var grant = new Grant(require('./config.json'))
grant.config[provider]

So we’re using that to construct the absolute connect url. Once completed, we’ll send the POST request along with some generated CORS token for the state field.


request.post(url, {
  form: {
    // generate random 6 digit number
    state: (Math.floor(Math.random() * 999999) + 1)
  },
  followRedirect: false
}

The important bit here is that we need to set the request’s followRedirect option to false, because we want to redirect away from the route we’re currently in.

Next, is setting up the cookie, as it contains the session variables used by Grant, and then we’ll redirect to the authorization url for that provider.


res.set('set-cookie', _res.headers['set-cookie'][0])
res.redirect(_res.headers.location)

Alternatively we can use a GET request with the /connect/[provider]/[override?] route to achieve the same result:


app.get('/connect_facebook2', function (req, res) {
  // some random 6 digit number
  var state = (Math.floor(Math.random() * 999999) + 1)

  res.redirect('/connect/facebook?state=' + state)
})

Response Data

After each OAuth flow the results are returned in the final callback on our server – the one configured via the callback key in our configuration.


{
  "facebook": {
    "key": "...",
    "secret": "...",
    "callback": "/facebook_callback"
  }
}

Then in our web application we handle that callback url like this:


app.get('/facebook_callback', function (req, res) {})

After successful OAuth1 flow the req.query key in Express, or the this.query key in Koa contains: access_token, access_secret and a raw key:


{
  "access_token": "OAuth1 access token",
  "access_secret": "OAuth1 access secret",
  "raw": {} // the raw response returned from the web service provider
}

After successful OAuth2 flow in exactly the same manner we have: access_token, refresh_token (optional) and a raw key:


{
  "access_token": "OAuth2 access token",
  "refresh_token": "OAuth2 refresh token",
  "raw": {} // the raw response returned from the web service provider
}

The raw key contains the entire response data as it was received from the server without any modifications added to it. The access_token, access_secret and the refresh_token keys are just convenient shortcuts.

In the case of an error, Grant returns the result in an error key:


{
  "error": {
    "message": "Something bad happened"
  }
}

What’s Next

Once we have our user’s access tokens secured, we may want to get that user’s profile. Luckily, there are great modules for that as well – Such module is Purest.

Purest is a generic REST API client library built on top of Request, that can help in easily accessing any REST API provider.

For example, to get our user’s Facebook profile, we may have the following code:


var Purest = require('purest')
  , facebook = new Purest({provider:'facebook'})

facebook.query()
  .get('me')
  .auth('[ACCESS_TOKEN]')
  .request(function (err, res, body) {
    // here body is a parsed JSON object containing things such as
    // id, first_name, last_name, gender, username and so on
  })

In exactly the same way we can get the profile for any other provider. For example, for Twitter we may have the following code:


var Purest = require('purest')
  , twitter = new Purest({provider:'twitter', key:'[CONSUMER_KEY]', secret:'[CONSUMER_SECRET]'})

twitter.query()
  .select('users/show')
  .where({screen_name:'scotch_io'})
  .auth('[ACCESS_TOKEN]', '[ACCESS_SECRET]')
  .request(function (err, res, body) {
    // here body is a parsed JSON object containing things such as
    // id, screen_name and so on
  })

Simeon

Simeon Velichkov is a full stack web developer, who is passionate about open source software and web standards. You can find some of his latest projects and links to social media at http://simov.github.io