Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Using PassportJS In Your Node.js And Couchbase Web App

TwitterFacebookRedditLinkedInHacker News

I was at an event not too long ago where someone recommended I take a look at PassportJS for my Node.js applications. In case you’re unfamiliar with PassportJS, it is an authentication middleware that makes it easy to work with logins, whether they are with basic login, or with social media accounts such as Facebook and Twitter.

Being that I’m into different authentication techniques, as seen in my other oauth articles, I figured it wouldn’t be a bad idea to do a walkthrough on PassportJS with Express Framework for Node.js.

There is a prerequisite that must be taken care of before pressing on into this tutorial. You need to have Node.js already installed and the Node Package Manager (NPM) functioning. You also need to have a functional Facebook account with access to your Facebook developer dashboard.

Create a New Node.js Project

With everything ready to go, let’s start by creating a new Express Framework project on our desktop. Using the Command Prompt (Windows) or Terminal (Mac & Linux), execute the following:

npm init

Answer all the questions to the best of your ability. If you prefer, you can just create a package.json file at the root of your project with the following in it:

{
    "name": "nodejs-passport-example",
    "version": "0.0.1",
    "description": "Use PassportJS for authentication in a CEAN stack application",
    "keywords": [
        "nodejs",
        "couchbase",
        "twitter",
        "facebook",
        "passportjs"
    ],
    "author": "Nic Raboy",
    "license": "Apache2",
    "dependencies": {}
}

The project has now been initialized.

Including Project Dependencies

Now we need to install all the Node.js dependencies that will be used in our project. From the Command Prompt or Terminal, execute the following:

npm install couchbase express body-parser cookie-parser express-session jade passport passport-facebook uuid --save

Let’s figure out what each of these dependencies does.

The couchbase package will allow us to interface from our application to Couchbase Server. The express package will allow us to use Express Framework. The body-parser package will allow us to accept POST requests. The cookie-parser and express-session packages will allow PassportJS to store authentication sessions while we use the application. The jade package will allow us to use Jade Markdown and the uuid package will allow us to generate unique values. Finally we include passport and passport-facebook to allow us to use PassportJS and authenticate with Facebook.

If this is your first time hearing of Passport, not a problem. It functions by using what they call Strategies in your project. When you try to authenticate in your application, a particular strategy will be executed that contains all your authentication logic, whether that be simple username and password login, or through social media. We’ll see more on this later on.

Couchbase Server & Facebook Configuration

Before we start coding our project we need to do a few more things in terms of configuration. At the root of your project, create a file called config.json and add the following:

{
    "couchbase": {
        "server": "127.0.0.1:8091",
        "bucket": "passportjs-example"
    },
    "facebook": {
        "client_id": "CLIENT_ID",
        "client_secret": "CLIENT_SECRET",
        "callback_url": "http://localhost:3000/facebook/callback"
    }
}

This configuration file will hold all our static values to be used within the application.

Configuring Couchbase Server

If you haven’t already installed Couchbase Server 4.0 or higher, do so now because we need to set it up to work with our configuration file.

In the Couchbase Server dashboard, create a new bucket called passportjs-example or whatever you’ve named it in your config.json file. Because we’re going to be using what is called N1QL further in our project, this bucket needs to have a primary index created.

If you’re in a Mac, run the following from your Terminal:

./Applications/Couchbase Server.app/Contents/Resources/couchbase-core/bin/cbq

If you’re on Windows, run the following from your Command Prompt:

C:/Program Files/Couchbase/Server/bin/cbq.exe

Both these commands will launch the Couchbase Query Client (CBQ) which will allow us to create indices, among other things. Execute the following in CBQ to create the index that we need for our project:

CREATE PRIMARY INDEX ON `passportjs-example` USING GSI;

Couchbase Server is now good to go.

Configuring Facebook

The final configuration we need to make, as seen in our config.json file is the Facebook configuration.

In your Facebook developer dashboard, create a new application. Copy the application id and the client secret from your dashboard into the appropriate places in your config.json file. Finally, navigate to Settings -> Advanced -> Valid OAuth redirect URIs in your developer dashboard and add whatever Facebook callback URL that you placed in the config.json file. This is where Facebook will drop users after a successful login.

Wew, configuration and initialization is now complete! We are now ready to start coding our project.

Creating Our Node.js Server File

Our Node.js server file, represented by app.js found at the root of our project, is responsible for including and configuring various dependencies and telling our application what port to start listening on. Our file will look like this:

var express = require("express");
var bodyParser = require("body-parser");
var passport = require("passport");
var couchbase = require("couchbase");
var path = require("path");
var cookieParser = require("cookie-parser");
var session = require("express-session");
var config = require("./config");

var app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

app.use(session({ secret: "nraboy_is_cool" }));
app.use(passport.initialize());
app.use(passport.session());

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

passport.deserializeUser(function(userId, done) {
    done(null, userId);
});

module.exports.bucket = (new couchbase.Cluster(config.couchbase.server)).openBucket(config.couchbase.bucket);

app.use(express.static(path.join(__dirname, "public")));

var routes = require("./routes/routes.js")(app);

var authstrategies = require("./auth/strategies.js");

var server = app.listen(3000, function () {
    console.log("Listening on port %s...", server.address().port);
});

It is a large file so let’s break it down.

At the top we’re just requiring all the dependencies that we’ve downloaded. Nothing special there.

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());

Above we are configuring our application to accept JSON data and URL encoded data in our POST body as well as initializing the cookie middleware.

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

Because we plan on using Jade Markdown in our project we need to tell our application where to find these files and what engine to use. All Jade files will be in the views directory found at the root of our project.

app.use(session({ secret: "nraboy_is_cool" }));
app.use(passport.initialize());
app.use(passport.session());

Above we are setting a secret key to be used on our sessions and we’re initializing PassportJS and the session utility that interfaces our sessions with PassportJS.

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

passport.deserializeUser(function(userId, done) {
    done(null, userId);
});

When PassportJS stores and retrieves our sessions, they go through a serialization and de-serialization process. For simplicity, we are only planning to store the user id in our session. This user id will represent a particular NoSQL document that contains our user data.

module.exports.bucket = (new couchbase.Cluster(config.couchbase.server)).openBucket(config.couchbase.bucket);

Above we are connecting to the Couchbase Cluster defined in our config.json file and opening the particular bucket that we defined in our config.json file.

app.use(express.static(path.join(__dirname, "public")));

Here we are including the public directory found at the root of our project. This directory will contain all our front-end JavaScript, CSS, and font data.

var routes = require("./routes/routes.js")(app);

var authstrategies = require("./auth/strategies.js");

var server = app.listen(3000, function () {
    console.log("Listening on port %s...", server.address().port);
});

Finally we are including the files that contain our routes and authentication strategies. We are also telling our server to listen for requests on port 3000.

Adding Database Models

Before we worry about application routes and authentication strategies let’s worry about the functions that will connect our application to Couchbase Server. For simplicity we’re going to place all these functions in a class called AccountModel found in the project’s models/accountmodel.js file.

This class will contain the following functions:

  • facebookFindOrCreate which will be responsible for taking Facebook authentication data and either getting user account information or creating it if it doesn’t already exist.
  • findByUserId which will take a user id stored in either a session or the Facebook reference document and getting the user account information associated with it.
  • updateUser which will update the user account information for a particular user based on their user id and whatever change information was passed with it.

Before we worry about the functions, let’s create the class skeleton:

var uuid = require("uuid");
var db = require("../app").bucket;
var config = require("../config");
var N1qlQuery = require("couchbase").N1qlQuery;

function AccountModel() {};

module.exports = AccountModel;

All functions will go above the module.exports = AccountModel; line.

Finding or Creating a User From Facebook

Starting with the facebookFindOrCreate function:

AccountModel.facebookFindOrCreate = function(profile, callback) {
    var query = N1qlQuery.fromString(
        "SELECT users.* " +
        "FROM `" + config.couchbase.bucket + "` AS facebooklogin " +
        "JOIN `" + config.couchbase.bucket + "` AS users ON KEYS (\"user::\" || facebooklogin.uid) " +
        "WHERE META(facebooklogin).id = $1"
    );
    db.query(query, ["facebook::" + profile.id], function(error, result) {
        if(error) {
            return callback(error, null);
        }
        if(result.length <= 0) {
            var userDocument = {
                "type": "user",
                "uid": uuid.v4(),
                "firstname": profile.name.givenName,
                "lastname": profile.name.familyName,
                "email": profile.emails[0].value
            };
            var referenceDocument = {
                "type": "facebook",
                "uid": userDocument.uid
            };
            db.insert("user::" + userDocument.uid, userDocument, function(error, result) {
                if(error) {
                    return callback(error);
                }
                db.insert("facebook::" + profile.id, referenceDocument, function(error, result) {
                    if(error) {
                        return callback(error);
                    }
                    return callback(null, userDocument);
                });
            });
        } else {
            callback(null, result[0]);
        }
    });
}

This is our beefiest function of the three. First we run a Couchbase N1QL query against information that Facebook returned when authenticating (we’ll see it soon). If data was found then return it, otherwise the user hasn’t been created yet and we need to create it.

If result.length <= 0 it means our query came up dry. We will be doing two inserts as part of the creation process. The first insert will create the actual user document where all user data is stored. If that insert succeeds, we will proceed to doing our second insert for our Facebook reference. By having a Facebook reference, a relationship is established between the two documents because the Facebook reference document contains the user id to our user document.

In other words, when Facebook information is provided we can then do a lookup for user information. This is particularly useful if you want to have multiple methods of login (Facebook, Twitter, Google+) for a single user.

Regardless on if a user existed or not, we’re going to return the same information back to the route that called it.

Getting an Existing User Document

Now we can start looking at how to get a user based on its user id:

AccountModel.findByUserId = function(userId, callback) {
    var query = N1qlQuery.fromString(
        "SELECT users.* " +
        "FROM `" + config.couchbase.bucket + "` AS users " +
        "WHERE META(users).id = $1"
    );
    db.query(query, ["user::" + userId], function(error, result) {
        if(error) {
            return callback(error, null);
        }
        callback(null, result[0]);
    });
}

Much smaller than our last query, it still uses N1QL. Here we are trying to find a particular user document. The user document will have a key prefixed with user:: and the unique id will be appended to it. The appended value is what is passed from our route.

Updating an Existing User Document

Finally our last class function. Inside our models/accountmodel.js file, add the updateUser function as follows:

AccountModel.updateUser = function(userId, profile, callback) {
    db.get("user::" + userId, function(error, result) {
        if(error) {
            return callback(error, null);
        }
        var userDocument = result.value;
        userDocument.firstname = profile.firstname;
        userDocument.lastname = profile.lastname;
        userDocument.email = profile.email;
        db.replace("user::" + userId, userDocument, function(error, result) {
            if(error) {
                return callback(error, null);
            }
            return callback(null, userDocument);
        });
    });
}

We are doing two database calls with this function. First we are getting the document that should already exist in Couchbase Server. Then we are changing various information in that document based on what was passed in as profile data from the parent route. With the user document updated, we can now replace what exists in Couchbase.

Creating Routes For Our Application

Our application will have many routes, but only two render data from within our application. Before we worry about the routes, let’s add some skeleton code to our routes/routes.js file:

var passport = require("passport");
var AccountModel = require("../models/accountmodel");

var appRouter = function(app) {

}

module.exports = appRouter;

All routes will appear inside the appRouter function. Let’s start with our home page:

app.get("/", function(req, res, next) {
    res.render("index", {});
});

Although we haven’t created it yet, this route will render our index.jade file found in our project’s views directory. That will come later.

Next we have two routes related to Facebook authentication:

app.get("/auth/facebook", passport.authenticate("facebook"));

app.get("/facebook/callback", passport.authenticate("facebook", {successRedirect: "/secure"}));

The route /auth/facebook will take us to the Facebook oauth flow because of our use of passport.authenticate("facebook") which we’ll define in a bit. When the oauth flow has completed we will land on /facebook/callback which we also defined in our config.json file earlier. If we land here and login was successful we’ll be redirected to the /secure route that we define next.

app.get("/secure", function(req, res, next) {
    if(!req.user) {
        return res.redirect("/");
    }
    AccountModel.findByUserId(req.user, function(error, result) {
        res.render("secure", {"profile": result});
    });
});

Because we have sessions running, our authorization status can always be seen in the req.user variable. If it is null it means we are not authenticated. If we are authenticated, go ahead and find the user account information via the findByUserId function that we created earlier.

Our final route will only process POST data:

app.post("/secure", function(req, res, next) {
    if(!req.user) {
        return res.redirect("/");
    }
    AccountModel.updateUser(req.user, req.body, function(error, result) {
        if(!error) {
            res.redirect("/secure");
        }
    })
});

Again we make sure the user is authenticated, but after we take our POST body and pass it to the updateUser function we created earlier for updating the user document in Couchbase.

Designing Views in Jade Markdown to Compliment Our Routes

Everything that renders to the screen will be done using Jade Markdown. There will be only two screens, but we’ll have a base template that includes all of our scripts and CSS to prevent code duplication.

Creating the Layout View

The layout view will be a single location for all scripts and CSS. Any other view will be nested inside it. Inside your project’s views/layout.jade file, add the following code:

doctype html
html
    head
        link(rel="stylesheet", href="/css/bootstrap.min.css")
        script(src="/js/bootstrap.min.js")
    body
        div(style="padding: 20px")
            block content

If this is your first time playing with Jade, it is indent sensitive, kind of like Python. You can see here that we’re only including Twitter Bootstrap, so now is a good time to download it. Place the min.css files from the download in your project’s public/css directory and the min.js files from the download in your project’s public/js directory. Also copy over the fonts directory from Bootstrap and place it in your project’s public directory.

Creating the Index View

Now we can create our child views. The index view, found at views/index.jade, will only contain a button for signing in with Facebook. This is for simplicity. It can be seen below:

extends layout

block content
    strong Enter via Social Media
    br
    a(href="/auth/facebook")
        button(class="btn btn-primary") Facebook

When the user clicks the button, they are taken to the /auth/facebook route which in reality is just the Facebook oauth flow.

Creating The Secure View Protected by PassportJS

Finally we have the view we are protecting with PassportJS. Open your project’s views/secure.jade file and include the following:

extends layout

block content
    h1 Welcome #{profile.firstname} #{profile.lastname}
    form(method="POST", action="/secure")
        div(class="form-group")
            label(for="firstname") First Name
            input(type="text", name="firstname", class="form-control", id="firstname", placeholder="First Name", value="#{profile.firstname}")
        div(class="form-group")
            label(for="lastname") Last Name
            input(type="text", name="lastname", class="form-control", id="lastname", placeholder="Last Name", value="#{profile.lastname}")
        div(class="form-group")
            label(for="email") Email
            input(type="text", name="email", class="form-control", id="email", placeholder="Email", value="#{profile.email}")
        input(type="submit", class="btn btn-success", value="Save")

This view contains a form that is pre-populated with data passed in from the route. Data passed in will be formatted with #{}, for example #{profile.firstname} which came from the profile variable we passed from the route.

When the user submits the form they will be taken to the POST route where data is then updated in Couchbase.

Developing Our PassportJS Authentication Strategy

Now for the thing you came here to see! Although small, here is the PassportJS logic found in our project’s auth/strategies.js file:

var passport = require("passport");
var FacebookStrategy = require('passport-facebook').Strategy;
var config = require("../config");
var AccountModel = require("../models/accountmodel");

passport.use(new FacebookStrategy({
    clientID: config.facebook.client_id,
    clientSecret: config.facebook.client_secret,
    callbackURL: config.facebook.callback_url
}, function(accessToken, refreshToken, profile, done) {
    AccountModel.facebookFindOrCreate(profile, function(error, user) {
        if(error) {
            return done(error);
        }
        done(null, user);
    });
}));

It uses the PassportJS Facebook strategy and includes the configuration information we set in the config.json file. Upon successfully signing into Facebook, we choose to pass the full profile object into our facebookFindOrCreate function that we created earlier.

That is all there is to it!

Conclusion

We just saw how to use the PassportJS middleware to authenticate users in a Node.js application. Data accessed with PassportJS is saved and retrieved from Couchbase Server, typical in applications that allow users to login or register via their social media accounts.

The full source code to this project can be found on the Couchbase Labs GitHub repository.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.