Michael Herman

Software Developer

Social Authentication in Node.js With Passport

Passport is a library that provides a mechanism for easily setting up an authentication/registration system with support for several frameworks and auth providers. In this tutorial, we’ll demonstrate in detail how to integrate this library into a Node.JS/Express 4 application to provide user authentication through LinkedIn, Github, and Twitter using OAuth 2.0.

passport social auth

We will be using:

For all dependencies, please view the package.json file in the repo.

OAuth 2.0?

OAuth 2.0 is the successor of the OAuth protocol (open standard for authorization), which enables third-party applications, such as the one we’ll be building, access to an HTTP service without having to share secure credentials.

Project Setup

Let’s get started!

Boilerplate

Start by downloading the project structure from the Github repo.

You should have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
├── client
│   └── public
│       ├── css
│       │   └── main.css
│       └── js
│           └── main.js
├── package.json
└── server
    ├── app.js
    ├── bin
    │   └── www
    ├── routes
    │   └── index.js
    └── views
        ├── error.html
        ├── index.html
        └── layout.html

Passport

Install Passport as well as the specific Passport Strategies:

1
2
$ npm install passport@0.3.0 --save
$ npm install passport-github2@0.1.9 passport-linkedin@1.0.0 passport-twitter@1.0.3 --save

Create an “auth” directory in the “server” and add the following files:

1
2
3
4
└── auth
  ├── github.js
  ├── linkedin.js
  └── twitter.js

Add the Passport dependency to app.js:

1
var passport = require('passport');

Install the express session middleware:

1
$ npm install express-session@1.11.3 --save

And add it as a dependency:

1
var session = require('express-session');

Then add the required middleware:

1
2
3
4
5
6
7
app.use(session({
  secret: 'keyboard cat',
  resave: true,
  saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());

Configuration

Add a _config.js file to the “server” and add the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var ids = {
  github: {
    clientID: 'get_your_own',
    clientSecret: 'get_your_own',
    callbackURL: "http://127.0.0.1:3000/auth/github/callback"
  },
  linkedin: {
    clientID: 'get_your_own',
    clientSecret: 'get_your_own',
    callbackURL: "http://127.0.0.1:3000/auth/linkedin/callback"
  },
  twitter: {
    consumerKey: 'get_your_own',
    consumerSecret: 'get_your_own',
    callbackURL: "http://127.0.0.1:3000/auth/twitter/callback"
  }
};

module.exports = ids;

Make sure to add this file to your .gitignore since this will contain sensitive info.

MongoDB and Mongoose

Install Mongoose:

1
$ npm install mongoose@4.1.8 --save

Require the dependency in app.js:

1
var mongoose = require('mongoose');

Then establish the connection to MongoDB within app.js:

1
2
// *** mongoose *** //
mongoose.connect('mongodb://localhost/passport-social-auth');

Add a Mongoose Schema to a new file called user.js in a new folder, within “server”, called “models”:

1
2
3
4
5
6
7
8
9
10
11
12
var mongoose = require('mongoose');
var Schema = mongoose.Schema;


// create User Schema
var User = new Schema({
  name: String,
  someID: String
});


module.exports = mongoose.model('users', User);

Serialize and Deserialize

Passport needs to serialize and deserialize user instances from a session store to support login sessions. To add this funcionality, create an init.js file within the “auth” directory, and then add the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var passport = require('passport');
var User = require('../models/user');


module.exports = function() {

  passport.serializeUser(function(user, done) {
    done(null, user.id);
  });

  passport.deserializeUser(function(id, done) {
    User.findById(id, function (err, user) {
      done(err, user);
    });
  });

};

Routes and Views

Before we test, add the following route-

1
2
3
router.get('/login', function(req, res, next) {
  res.send('Go back and register!');
});

-and update the index.html file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{% extends 'layout.html' %}

{% block title %}{% endblock %}


{% block content %}

  <div class="container">

    <h1>{{ title }}</h1>
    <p>Welcome! Please Login.</p>
    <hr><br>
    <a href="/auth/linkedin" class="btn btn-default">LinkedIn</a>
    <a href="/auth/github" class="btn btn-default">Github</a>
    <a href="/auth/twitter" class="btn btn-default">Twitter</a>

  </div>

{% endblock %}

Sanity Check

Test this code to make sure all is well:

1
$ npm start

Once done, kill the server, and then commit your code and push to Github.

Need the updated code? Grab it here.

LinkedIn Auth

https://github.com/jaredhanson/passport-linkedin

For almost all of the strategies, you will need to-

  1. Create an app through the auth provider
  2. Update the config file with the required IDs and keys as well as a callback URL
  3. Configure the Passport strategy
  4. Add the required routes
  5. Update the view

Create an App

Navigate to LinkedIn Developers to register a new application. Just enter dummy info, make sure to add the callback - http://127.0.0.1:3000/auth/linkedin/callback - and update the config within the app:

1
2
3
4
5
linkedin: {
 clientID: 'ADD YOUR ID HERE',
 clientSecret: 'ADD YOUR SECRET HERE',
 callbackURL: "http://127.0.0.1:3000/auth/linkedin/callback"
},

Configure Strategy

https://github.com/jaredhanson/passport-linkedin#usage

Add the following code to linkedin.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin');

var User = require('../models/user');
var config = require('../_config');
var init = require('./init');

passport.use(new LinkedInStrategy({
    consumerKey: config.linkedin.clientID,
    consumerSecret: config.linkedin.clientSecret,
    callbackURL: config.linkedin.callbackURL
  },
  // linkedin sends back the tokens and progile info
  function(token, tokenSecret, profile, done) {

    var searchQuery = {
      name: profile.displayName
    };

    var updates = {
      name: profile.displayName,
      someID: profile.id
    };

    var options = {
      upsert: true
    };

    // update the user if s/he exists or add a new user
    User.findOneAndUpdate(searchQuery, updates, options, function(err, user) {
      if(err) {
        return done(err);
      } else {
        return done(null, user);
      }
    });
  }

));

// serialize user into the session
init();


module.exports = passport;

Aside for the Passport magic, you can see that we’re either updating the user, if the user is found, or creating a new user, if a user is not found.

Add Routes

https://github.com/jaredhanson/passport-linkedin#authenticate-requests

Update the routes with:

1
2
3
4
5
6
7
8
router.get('/auth/linkedin', passportLinkedIn.authenticate('linkedin'));

router.get('/auth/linkedin/callback',
  passportLinkedIn.authenticate('linkedin', { failureRedirect: '/login' }),
  function(req, res) {
    // Successful authentication
    res.json(req.user);
  });

Add in the dependency as well:

1
var passportLinkedIn = require('../auth/linkedin');

Sanity Check

Test this out. Be sure to use http://127.0.0.1:3000/ rather than http://localhost:3000/.

Now, let’s just duplicate that workflow for the remaining providers…

Github Auth

https://github.com/cfsghost/passport-github

Create an App

Again, create an app, adding in the correct callback URL, and add the given client ID and Secret Key to the _config.js file.

Configure Strategy

https://github.com/cfsghost/passport-github#configure-strategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var passport = require('passport');
var GitHubStrategy = require('passport-github2').Strategy;

var User = require('../models/user');
var config = require('../_config');
var init = require('./init');


passport.use(new GitHubStrategy({
  clientID: config.github.clientID,
  clientSecret: config.github.clientSecret,
  callbackURL: config.github.callbackURL
  },
  function(accessToken, refreshToken, profile, done) {

    var searchQuery = {
      name: profile.displayName
    };

    var updates = {
      name: profile.displayName,
      someID: profile.id
    };

    var options = {
      upsert: true
    };

    // update the user if s/he exists or add a new user
    User.findOneAndUpdate(searchQuery, updates, options, function(err, user) {
      if(err) {
        return done(err);
      } else {
        return done(null, user);
      }
    });
  }

));

// serialize user into the session
init();


module.exports = passport;

Add Routes

https://github.com/cfsghost/passport-github#authenticate-requests

1
2
3
4
5
6
7
8
9
10
var passportGithub = require('../auth/github');

router.get('/auth/github', passportGithub.authenticate('github', { scope: [ 'user:email' ] }));

router.get('/auth/github/callback',
  passportGithub.authenticate('github', { failureRedirect: '/login' }),
  function(req, res) {
    // Successful authentication
    res.json(req.user);
  });

Twitter Auth

https://github.com/jaredhanson/passport-twitter

Create an App

Create an app on the Twitter Developer page, and grab the Consumer Key and Secret.

Configure Strategy

https://github.com/jaredhanson/passport-twitter#configure-strategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var passport = require('passport');
var TwitterStrategy = require('passport-twitter').Strategy;

var User = require('../models/user');
var config = require('../_config');
var init = require('./init');

passport.use(new TwitterStrategy({
    consumerKey: config.twitter.consumerKey,
    consumerSecret: config.twitter.consumerSecret,
    callbackURL: config.twitter.callbackURL
  },
  function(accessToken, refreshToken, profile, done) {

    var searchQuery = {
      name: profile.displayName
    };

    var updates = {
      name: profile.displayName,
      someID: profile.id
    };

    var options = {
      upsert: true
    };

    // update the user if s/he exists or add a new user
    User.findOneAndUpdate(searchQuery, updates, options, function(err, user) {
      if(err) {
        return done(err);
      } else {
        return done(null, user);
      }
    });
  }

));

// serialize user into the session
init();


module.exports = passport;

Add Routes

https://github.com/jaredhanson/passport-twitter#authenticate-requests

1
2
3
4
5
6
7
8
9
10
var passportTwitter = require('../auth/twitter');

router.get('/auth/twitter', passportTwitter.authenticate('twitter'));

router.get('/auth/twitter/callback',
  passportTwitter.authenticate('twitter', { failureRedirect: '/login' }),
  function(req, res) {
    // Successful authentication
    res.json(req.user);
  });

Conclusion

Try adding some additional strategies, comment below if you have questions, and grab the final code from the repo.

Thanks for reading!

Comments