OAuth2 with NodeJS , Express and AngularJS to access Twitter

The sole client-side approach presented in OAuth with AngularJS would only work if CORS restrictions were relaxed. In an Intranet environment we could achieve this by letting users start their browsers – Chrome – with disabled web security (chrome –disable-web-security).

While this workaround perfectly works from a user’s perspective it still remains unsatisfactory from an engineering point of view. As you can read up here  there seems to be no way to retrieve data from the v1.1 API without any backend subsystem.

Well, so be it then.

nodejsproxyI have switched from a pure client SPA model to a classic client/server model where all authentication and data retrieval logic takes place in the server subsystem. Since the client is written in JavaScript it was pretty obvious to give node.js a try. Additionally, the routing capabilites of Express framework became quite useful here so I could chain two separate modules “Authenticator” and “TweetsService” and bind them to my route ‘/tweets’.

The following picture shows the overall system architecture

Architecture

In my Express app initialization code I bind the tweetsearch router to '/tweets'.

...
var tweetsearch = require('./routes/tweetsearch.js');

var app = express();
...
...
app.use('/tweets', tweetsearch)
...

The router itself is defined as follows

var express = require('express');
var request = require('request');
var router = express.Router();
var tweetService = require('./../lib/tweets').TweetsService();
var authService = require('./../lib/authenticator').Authenticator();

router.get('/', function (req, res, next) {
        authService.authenticate({}, function (token) {
            req.access_token = token;
            next();
        })
    },
    function (req, res, next) {
        var opts = {
            query: req.query.q,
            access_token: req.access_token
        }
        tweetService.search(opts, function (data) {
            res.json(data);
        });
    }
);
module.exports = router;

Two middleware components using authService and tweetService are chained and are processed sequentially. authService will retrieve the access_token needed to gain access to Twitter’s search api. In a callback the access_token is bound to the HTTP request object and thus becomes available in the second middleware component.

The authenticator itself is provided as a node.js module and contains a slightly modified version of the client code.
Subsequent authentication calls will be served by a cached access_token (naive approach just for demonstration and testing purposes).

var request = require('request');

function Auth() {
    var access_token;
    var consumerKey = <your consumer key >
    var consumerSecret = <your consumer secret>
    var credentials = new Buffer(consumerKey + ':' + consumerSecret).toString('base64'); // base64 encoding
    var post_options = {
        url: 'https://api.twitter.com/oauth2/token',
        qs: {
            grant_type: 'client_credentials'
        },
        headers: {
            'Authorization': 'Basic ' + credentials,
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
        }
    }

    function getAccessToken() {
        return access_token;
    }

    function setAccessToken(token) {
        access_token = token;
    }

    this.authenticate = function (ops, callback) {
        if (access_token) {
            callback(getAccessToken());
        } else {
            request.post(post_options, function (error, response, body) {
                    setAccessToken(JSON.parse(body).access_token);
                    callback(getAccessToken());
                }
            )
        }

    }
}

module.exports.Authenticator = function () {
    return new Auth();
}

The TweetsService has been developed as a node.js module as well where most of the client side code can be reused.


var Client = require('node-rest-client').Client;

module.exports.TweetsService = function () {
    var twitterClient = new Client();

    twitterClient.registerMethod('search', 'https://api.twitter.com/1.1/search/${action}', 'GET');

    this.search = function (opts, callback) {
        console.log('Query: ' + opts.query);
        console.log('Bearer token:' + opts.access_token);

        var args = {
            path: {'action': 'tweets.json'},
            parameters: {
                'q': opts.query,
                count: 10,
                randomParam: Math.random() * 1000
            },
            headers: {Authorization: 'Bearer ' + opts.access_token}
        }
        twitterClient.methods.search(args, function (data, response) {
            callback(data);
        })
    }
    return this;
}

As for the AngularJS part the previous client side logic simply reduces to:

    var serviceModule = angular.module('InfoDashboard.services', ['ngResource'])
    serviceModule
        .factory('twitter', function ($resource, $http) {
            var r = $resource('/tweets',
                {
                    action: 'tweets.json',
                    count: 10,
                    randomParam: Math.random() * 1000
                }, {
                    get: {method: 'GET'},
                    paginate: {method: 'GET'}
                })

            return r;
        }
...

Cheers
dokmatik