NodeJS REST authentication using Passport and OAuth2 + social network

Sandak picture Sandak · Oct 9, 2014 · Viewed 10.3k times · Source

I'm working on REST api using NodeJS. For authentication I decided to use Passport. I want truly RESTful api. So it means I have to use tokens instead of sessions.

I want to let users login using username and password, or using social networks like Facebook, Google and Twitter.

I make my own OAuth2.0 server for issuing Access and Refresh tokens using oauth2orize module. So now I can register new user and then issue them tokens. I followed this tutorial:

http://aleksandrov.ws/2013/09/12/restful-api-with-nodejs-plus-mongodb/

Verifying user for route:

// api ------------------------------------------------------------------------------------
    app.get('/api/userInfo',
        passport.authenticate('bearer', { session: false }),
        function(req, res) {
            // req.authInfo is set using the `info` argument supplied by
            // `BearerStrategy`.  It is typically used to indicate scope of the token,
            // and used in access control checks.  For illustrative purposes, this
            // example simply returns the scope in the response.
            res.json({ user_id: req.user.userId, name: req.user.username, scope: req.authInfo.scope })
        }
    );

All this works quite well. Unfortunately I don't know how to implement social authentication.

I was reading this tutorial:

http://scotch.io/tutorials/javascript/easy-node-authentication-facebook 

But in this tutorial they are not making a truly RESTful api. I already implemented user schema according this tutorial where tokens for local user are stored in separated models.

// define the schema for our user model
var userSchema = mongoose.Schema({
    local: {
        username: {
            type: String,
            unique: true,
            required: true
        },
        hashedPassword: {
            type: String,
            required: true
        },
        created: {
            type: Date,
            default: Date.now
        }
    },
    facebook: {
        id: String,
        token: String,
        email: String,
        name: String
    },
    twitter: {
        id: String,
        token: String,
        displayName: String,
        username: String
    },
    google: {
        id: String,
        token: String,
        email: String,
        name: String
    }
});

But now, how can I verify user?

passport.authenticate('bearer', { session: false }),

this is verifying only bearer token against to my db, but how can I verify social tokens? Am I missing something?

Answer

Iliyan Trifonov picture Iliyan Trifonov · Jun 9, 2015

I am using Facebook login for my own RESTful API for my Notepads app here. I started the application as one that will be used as a web page but still the communication after the login will be through the API.

Then I decided to create a mobile version of the same app that will use the API. I decided to make it that way: The mobile app logs in through Facebook and sends the facebook user id and FB access token to the API, the API calls Facebooks's API to verify these params and if successful registers a new user(or logs in an existing one) in my app's DB, creates a custom token for this user and returns it to the mobile app. From here the mobile app sends this custom token to authenticate the app with the API.

Here's some code:

The auth in the API (uses the fbgraph npm module):

var graph = require('fbgraph'),
Promise = require('bluebird')
...
Promise.promisify(graph.get);
...
var postAuthHandler = function (req, res) {
    var fbUserId = req.body.fbId,
    fbAccessToken = req.body.fbAccessToken,
    accessToken = req.body.accessToken;
    ...
    graph.setAppSecret(config.facebook.app.secret);
    graph.setAccessToken(fbAccessToken);

    var graphUser;
    var p = graph.getAsync('me?fields=id,name,picture')
        .then(function (fbGraphUser) {
            //when the given fb id and token mismatch:
            if (!fbGraphUser || fbGraphUser.id !== fbUserId) {
                console.error("Invalid user from fbAccessToken!");
                res.status(HttpStatus.FORBIDDEN).json({});
                return p.cancel();
            }

            graphUser = fbGraphUser;

            return User.fb(fbUserId);
        })
        .then(function (user) {
            if (user) {
                //user found by his FB access token
                res.status(HttpStatus.OK).json({accessToken: user.accessToken});
                //stop the promises chain here
                return p.cancel();
            }
            ...create the user, generate a custom token and return it as above...

https://github.com/iliyan-trifonov/notepads-nodejs-angularjs-mongodb-bootstrap/blob/6617a5cb418ba8acd6351ef9a9f69228f1047154/src/routes/users.js#L46 .

The User model:

var userSchema = new mongoose.Schema({
    facebookId: { type: String, required: true, unique: true },
    accessToken: { type: String, required: true, unique: true },
    name: { type: String, required: true },
    photo: { type: String, required: true },
    categories: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Category' }],
    notepads: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Notepad' }]
});

https://github.com/iliyan-trifonov/notepads-nodejs-angularjs-mongodb-bootstrap/blob/master/src/models/user.js#L9 .

The Facebook auth in the mobile app:

               auth: function(fbId, fbAccessToken) {
                return $http({
                    url: apiBase + '/users/auth',
                    data: {
                        fbId: fbId,
                        fbAccessToken: fbAccessToken
                    },
                    method: 'POST',
                    cache: false
                });
            },
            ...

https://github.com/iliyan-trifonov/notepads-ionic/blob/master/www/js/services.js#L33 .

The mobile app sends the token with the request:

  notepads: {
            list: function() {
                return $http({
                    url: apiBase + '/notepads?insidecats=1' + '&token=' + User.get().accessToken/*gets the token from the local storage*/,
                    method: 'GET',
                    cache: false
                });
            },

It's an Ionic/Angular/Cordova app. The Facebook login from the mobile app starts the Facebook app installed on your phone or opens a popup to login in Facebook. Then a callback returns the Facebook user's id and access token to my mobile app.

The fbgraph npm module: https://github.com/criso/fbgraph