Local Authentication with Express 4.x and Passport.js

muyiwa akin-ogundeji
19 min readDec 6, 2015

--

User accounts are a significant part of any non trivial web app. In this post we will implement Local Authentication using a simple Express 4.x app and passport.js. In the course of implementing this app we will deep dive into the authentication process starting from the HTML form and ending with a successful redirect to the user’s dashboard. We will also cover the relationship between forms, cookies, sessions and the passport authentication workflow.

NB. This post focuses on implementing authentication using Express 4.x framework, so concepts like middleware and how they’re used are integral to full understanding of the material covered in the post.

The entire code for this post is available at https://github.com/oakinogundeji/express4.x-LocalAuth

Let’s dig in!

Authentication starts with the HTML form.

The HTML form is where the entire authentication process begins. An HTML form provides a structured interface for data to be requested from users and processed by our servers. The actual <form> element doesn’t capture the data, it simply provides the interface and mechanism for processing the data. Data is captured by the form widgets generally a variation of the <input> element.

The 2 main <form> attributes responsible for where data is sent to and in what form it is sent for processing are “action” and “method”. “action” determines the ‘location’/URL to which the captured data is sent.

NB in this case, i’m assuming that we’re not using a frontend framework to drive the UI i.e. the form is rendered from the server.

“method” determines the HTTP method/verb which modifies how the data is transmitted and triggers the appropriate route on the server. An example of these interactions is:

HTML:
<form action=”/signup” method=”post”>…</form>
Server route:
router.post('/signup', function (req, res) {
// do something with the received data
});

By default, the ‘enctype’ of an HTML form when the ‘method=”post” ‘ attribute is used is “application/x-www-form-urlencoded”. What this means essentially is that the data extracted from the form widget is an encrypted string very similar to the ‘query string’ used with a ‘GET’ operation only that the data is enclosed in the form body.

The <form> widgets which perform the actual data capture MUST have a ‘name’ attribute which identifies the data the input widget holds and allows the server to properly map data to identifier when the form data is being processed.

Example:

<input type=”text” name=”username”>

Authentication requires middleware to extract the form data.

So, the user has entered data into the form and submitted it. The data is now on it’s way to the server, safely enclosed within the HTTP request body. Without middleware to extract the data enclosed in the form body, the data is useless to the server, and the form is useless to the user. This is where the ‘body-parser’ middleware comes to play.

‘body-parser’ extracts the ‘POST’ data from the body of the HTTP request and uses it to populate the ‘body’ property of the request object. i.e. successful ‘body-parsing’ will create a ‘req.body’ object which has properties that correspond to the ‘name’ attributes of the <input> widgets of the <form>.

Example:

HTML:
<form action="/signup" method="post">
<input type=”text” name=”username”>
</form>
Server:
router.post('/signup', function (req, res) {
var name = req.body.name;
console.log(name);
res.end();
});

Authentication requires a model.

When i say ‘Authentication requires a model’ — what do i mean?

Well, the data captured by the <form> and extracted by the ‘body-parser’ middleware has to represent something… perhaps a ‘User’. This ‘User’ needs to be represented in some meaningful form by the application.

In the world of computers, data is used to represent real world entities, and a ‘model’ is a data structure used to represent these entities. In the Node world, a popular tool for creating data models is Mongoose.js (we won’t delve too deeply into the inner workings of Mongoose in this post).

In mongoose, before a user model can be created, a schema has to be defined. Thus the user model may look like the following:

user schema:
var UserSchema = mongoose.Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String
},
created_on: {
type: Date,
default: Date.now
}
});
user model:
var UserModel = mongoose.model('User', UserSchema);

Authentication requires a database for persistence.

It’s no good capturing user data via the form, extracting it using middleware, and the representing that data as a model if the model can’t be persisted and referred to over and over again as long as the user visits the application.

A very popular database in the Node world is MongoDB. Mongoose is actually an ORM (Object Relational Mapper) for MongoDB. In essence, Mongoose makes it possible for the data captured by the form to be presented to MongoDB in a format which MongoDB requires i.e. BSON(Binary JSON).

Mongoose connects to the MongoDB database and persists the various instances of the User Model to the dBase whenever those instances are created.

Authentication requires session management.

So far, we are able to capture user data, process the data and store it in the dBase — but there’s a catch. HTTP is a stateless protocol, which means that the information captured from the user is useful only for that request, as soon as the user refrehes the page or navigates to another page (which may be a protected page) the credentials so painstakingly entered, captured, processed and stored are ‘lost’.

Yup, HTTP doesn’t remember the user’s credentials and neither can the Express app without the use of special middleware namely ‘express-session’.

To understand why we need ‘express-session’ (or some other session middleware) lets really understand what sessions is all about.

Because HTTP is a stateless protocol, it doesn’t retain the credentials or any other info about a user when a particular request-response cycle has ended. This means that without a means to refer to a user’s already authenticated credentials as the user traverses the web site/app, the user must resend these credentials for each HTTP request made — bad juju!

Sessions is a mechanism to logically group related HTTP requests from a single origin which are made to a single server and persist the state of the established connection across multiple request-response cycles.

The key to sessions is the cookie — that part of a HTTP header which holds info about the user agent originating the request tot he server. Cookies are stored on the client’s browser and sent with each HTTP request to the server.

There are 2 major ways to implement session management — 1) cookie based and 2) session store based.

A cookie based approach stores session info in an encrypted form on the cookie set for the client by the server. The major advantage to cookie based implementations is that they give the client complete flexibility in how the server is accessed. This means that the server can be accessed with ease across multiple devices and multiple servers can be accessed by a single client since all that’s tracked is the cookie sent with the HTTP request. The disadvantage to this approach is that since cookies are limited to only 4KB per cookie with a max of 20 cookies per domain, only a limited amount of session info can be persisted for each user.

A session store based approach on the other hand uses a dBase to securely store session info per user. This means that a large and rich amount of data about the user can be safely stored behind the servers and used to customize the user experience. The disadvantage to this approach is that it’s more complicated to scale such stores across multiple servers since the only info stored on the cookie is the session id and that id maps to a single dBase entry.

Having examine these 2 options, we will use the excellent ‘express-session’ middleware in this example. It uses a session store based approach to session management.

RECAP.

So far we have HTML forms to capture and transmit data from the user, we have body parser middleware to extract the data from the HTTP body, we have a user model which we can apply the data against to create new users, we have a dBase in which new users can be stored and the credentials of existing users can be retrieved. Finally we have session management middleware which makes it possible to track authenticated users requests across multiple HTTP cycles.

These elements are all that we need to authenticate users to our application. However, there is a reason that passport.js is so popular — it can be a real pain to flawlessly implement authentication in a web app. Using passport.js ‘local-strategy’ makes this process almost trivial.

A few other bits we need include the use of the excellent ‘bcrypt-nodejs’ middleware which allows us to securely encrypt and decrypt our users passwords.

With the theory understood, lets get down to the code!!!

Directory Structure:

The directory structure for our app looks like this:

You may notice how i like to structure my projects: i split off the ‘routes’ from the main ‘express app’, i believe the main app should simply hold app-wide configurations, settings and middleware. Since express 4.x makes it possible for us to create the router using ‘express.Router()’ and to mount it as a ‘mini application’ i believe the method allows us to declutter our main app and makes our code easier to read and maintain.

Notice also that there is a server.js, this holds the Node HTTP server instance which uses app.js as the request handler (i.e. server = http.createServer(app);) — this allows us to create the ‘express app’ as a separate module and export the configured app instance so that other modules may ‘require’ it as needed.

Tools:

We will use the following tools to build our simple app: ‘express 4.x’ (web app framework), ‘body-parser’ (extract POST data from HTTP request), ‘express-session’ (session store based session management middleware), ‘connect-mongo’ (an ‘express-session’ compatible dBase connector to implement a session store on MongoDB), ‘mongoose’ (MongoDB ORM for user data modelling), ‘passport’ (Authentication middleware for ‘express’), ‘passport-local’ (local authentication implementer for passport), and ‘ejs’ (templating engine for express).

We will also use the following supporting libraries ‘morgan’ (for logging), ‘should’ (BDD assertion library for express), ‘supertest’ (powerful assertion library for testing RESTful endpoints) as well as ‘mocha’ (test runner).

These tools are represented in ‘package.json’ as follows:

First Step — config.js:

We begin creating our app with the ‘config’ directory which initially holds config.js, development.json and production.json as follows:

config.js examine the NODE_ENV variable to determine which environment Node is operating in, then it simply exports the appropriate ‘.json’ file for that environment (in this example we limit it to only ‘dev’ and ‘production’ environments).

‘development.json’ holds the app configuration for a development environment e.g dbURL(MongoDB database URL).

For the sake of brevity, ‘production.json’ holds the same values as ‘development.json’ . In a production app, values such as ‘dbURL’ and ‘host’ would reflect production realities.

With the basic app-wide config in place, we can proceed to the main app.js module.

Next step — app.js

Note line 25, we require the ‘routes’ module but it hasn’t been created yet. We can safely do this because we need to create the main app, before we setup our routes.

Lets walk through this module to clearly understand what it does.

Lines 1–10 are fairly straightforward, line 11 has us ‘require’ing the session store and passing in the ‘session’ instance created in line 9. Lines 13–26 are also straightforward, on line 27, we do something ‘strange’ — we define a variable ‘app.locals.errMsg’ and we set it to either itself, or null.

‘locals’ is a special ‘express’ construct which makes variables attached to it as properties available on the ‘view’ templates. There are 2 forms of locals — app-wide and request-specific. An app-wide locals is available to all reques/response objects and can be rendered by any route in the app. This makes it handy for setting ‘global’ variables such as ‘error messages’ and other types of feedback. The ‘request’ specific locals are used for passing request specific data into the ‘view’ templates e.g a request specific confirmation message and this is made possible via the ‘response’ object which is the counterpart of a specific request object i.e. ‘res.locals’.

In lines 32–35 we define app-wide configurations such as the ‘port’, ‘app environment, mapping the app ‘views’ to the location where the view templates reside and finally setting the ‘view engine’ to render files with the ‘.ejs’ extension. This means that we don’t need to add the extension of the view files when we reference them.

On line 37, we connect ‘mongoose’ to the ‘MongoDB’ dBase by passing in the dBase URL which we have previously set in ‘development.json’, we define a variable ‘db’ which represents the ‘mongoose’ dBase connection and then define 3 callbacks:

  1. If there’s an error connecting to the dBase, the first callback on lines 39–42 fires and alerts us.
  2. Lines 43–45 define a callback which fires just once the first time ‘mongoose’ successfully connects to the dBase.
  3. Lines 46–48 define a callback which fires once when ‘mongoose’ disconnects from the dBase.

Lines 53–63 define the ‘middleware’ stack for the ‘express’ app. Note the order of the stack — first logging all requests made to the server, then parsing any ‘JSON’ content in the request body if available, next parsing any ‘POST’ data in the request body if available and finally passing the processed ‘request’ object to the ‘session’ middleware for session management.

In lines 59–63, we do the following:

  1. Provide an ‘identifier’/label/name for the cookie used for that session i.e. ‘xpressBlu.sess’.
  2. Define the ‘session store’ as the ‘connect-mongo’ instance. This store can connect to the same dBase as the ‘mongoose’ connection but it stores its data in a ‘collection’ called ‘sessions’ by default. Note the ‘touchAfter’ property, this tells the ‘session store’ to update all cookies only after a period of 24 hours regardless of how many ‘requests’ the cookie has been used with except for requests that modify session data. This reduces dBase access request as only modified cookies (i.e. cookies whose session data has been modified) are stored to the dBase — neat huhn? Next a session secret is defined — note that this could be defined via ‘development.json’ and accessed as a ‘config’ variable. Next we set ‘resave’ and ‘saveUnintialized’ options to ‘false’. This means that we don’t ‘resave’ unmodified sessions to the store and neither do we save new and unmodified session (uninitialized) to the store — both these settings helps reduce dBase qccess and prevent ‘race conditions’. Finally we set the ‘max-age’ of the ‘cookie’. You can use whatever value suits your purpose, in this example it’s set to 15 minutes.
  3. Next step is to ‘mount’ the router ‘min app’ to the ‘root’ of the app as the ‘mount point’. This means that any requests which pass through the ‘root’ of the app will be passed to the router for handling by ‘express’.
  4. Lastly, we export the app module.

User Models.

Having setup the main app, we can create the user model using ‘mongoose’. We will also create an ‘utilities’ module which we can use to create users or use helpful functions.

As explained earlier, ‘mongoose’ models require that a ‘schema’ is first defined before a model can be constructed. Think of ‘schemas’ as blueprints and ‘models’ as moulds. Anything defined in the blueprint is contained in the mould, however objects are created from the mould and not the blueprint.

In this module, pay attention to lines 37–43, 2 ‘schema’ methods are defined. This is similar to defining methods on an object or functions prototype in Javascript. Any instance of that object or function has access to those methods. The same hold true here.

The ‘schema’ methods provide the mechanism to encrypt and decrypt a users password which is used to confirm that the user is authentic.

This module simply defines several useful functions which make it easier to work with the user model module e.g. using the ‘errHandler’ function, one can get deeper insight into any error which occurs while trying to create a new user.

Next step — passport.js

After setting up the main app.js and creating the user model module, we can turn our attention to passport. Notice that we still haven’t created the ‘router’ module. This is deliberate as the router will need all these pre created modules in order to properly respond to any request.

At this stage, we can add the ‘passport’ module to the ‘config’ directory as follows:

Note how in lines 5–7 we pull in dependencies from modules which have been previously created.

Before walking through this module, lets understand passport.js and its authentication flow.

There are 3 main ‘parts’ which work together to make it possible to use passport in an ‘express’ app.

  1. ‘require’ing the passport module and then using the passport.initalize()/passport.session() middleware. passport.initialize() initializes the passport middleware for use, because it is middleware, this initialization can be on an app-wide or route-specific basis, usually we use it app-wide and thus it tends to come after the ‘express-session’ middleware. ‘passport.session()’ is passport middleware which allows passport to be used for persistent login sessions i.e login sessions which may span multiple hours or days regardless of whether the user is actually connected to the app or not (a common example is the ‘remember me’ checkbox). While some people prefer to apply both passport.initalize() & passport.session() as app-wide middleware in app.js, my personal preference is to use them where they’re actually neede — in the router mini app.
  2. Within the passport module itself i.e. ‘config/passport.js’ we need to ‘serialize’ users to the session and ‘deserialize’ them from the session. ‘passport.serializeUser()’ intercepts the ‘request’ object before it is passed to the route handlers and looks for the user which matches the provided credentials in the dBase, it then appends that user’s unique ‘id’ from the dBase (‘_id’ in the case of MongoDB) to the request cookie before passing the modified request object to the route handler. If any operation needs to be applied against the user who initiated the request, passport.desrializeUser() is able to reference the actual user object from the ‘session store’ and make it available as ‘req.user’. Thses 2 pieces of passport middleware work in conjucntion to make persistent sessions possible.
  3. ‘passport.authenticate()’ middleware used as route specific middleware in the route handler works hand-in-glove with the passport authentication strategies. passport.authenticate intercepts the request object passed to the route handler and calls the appropriate passport strategy to perform authentication. The passport strategy does its thing and returns control to passport.authenticate with the success or error result. If all goes well, then the authenticated user is available as ‘req.user’ for the next handler in line on the route, if there’s an error, passport truncates the request as configured (we’ll see this soon).

These 3 ‘parts’ need to be properly configured so that passport.js can be used in our ‘express’ app. One more thing, another good reason to set ‘saveUninitialized’ to ‘false’ in the ‘express-session’ middleware is that passport adds the ‘req.user’ property to the request object even if the session has not been otherwise modified. This means that if ‘saveUninitialized’ is set to ‘true’, race conditions will occur and the passport modifications may be discarded — this can cause a lot of frustrating troubleshooting — i know, it happened to me so BEWARE!

Passport’s local-strategy covers both login/sign-up operations. For clarity, i like to split them up into separate ‘named’ strategies. A named strategy simply means that we attach a unique identifier to the strategy so that it can be directly referenced in the route handlers.

In the ‘local-signup’ strategy beginning from line 37, we tell passport that we want the ‘username’ and ‘password’ arguments which the callback expects by default to instead be represented by ‘email’ and ‘password’.

Notice the ‘passReqToCallback’ option is set to ‘true’, this is because there’s other info that we need from the HTML form data apart from ‘email’ and ‘password’ for creating a valid user according to our user model module i.e. we also need ‘username’! So passing the request object to the callback ensures that the ‘username’ data can be extracted from ‘req.body.username’.

Line 43, ensures that creation of the user is an async operation… this is inline with Node’s philosophy of non’blocking io.

The ‘verify’ callback is passed 4 arguments — req, email, password and done. It uses this data to query the database to search for the ‘single’ user who has the supplied ‘email’. This is possible because we set the ‘email’ property of the user model as ‘unique:true’ in user.js.

The first operation within the ‘verify’ callback is a dBase connection error handler. If no error connecting to the dBase, it then searches for the user. Because this is ‘signup’ i.e. creating new users, if such an email already exists in the dBase than it means that that user already exist. Passport therefore calls done() and passes 3 arguments, ‘null’ (signifying no error), ‘false’ (signifying that a new user was not created) and an optional informational error message which can be rendered in the view. For our purposes, we have created the app.locals.errMsg variable to make it easy to render this error message. Calling done() returns control of the passport authentication flow to passport.authenticate from the authentication strategy(more on that in the section on routes).

If the email does not exist, then the new user is created. Notice line 56 — we see here that before the password extracted from the HTML form is saved to the Dbase, it’s hashed by calling the ‘generateHash()’ method of the user model instance.

Next step is to save the newly created user, if there’s any error saving the user to the dBase, we handle it else we call done() and pass the newly created user to passport.authenticate and that is how this ‘user’ is made available as ‘req.user’.

‘local-login’ works in similar fashion to ‘local-signup’ except in this case, if the user can’t be found in the dBase, then we return an informational; on line 90, if the user is found, but the password is not valid, we also return an informational message otherwise we authenticate the user and return control to passport.authenticate.

Now the Views:

Having completed all the preceding steps, the next ‘logical’ step is to create the ‘views’ which the routes will render in response to requests. I particularly like ‘Embedded JS’ for server side templating mainly because the syntax doesn’t conflict with the template syntax for my preferred frontend library(i.e. Vue.js).

The views are available at https://github.com/oakinogundeji/express4.x-LocalAuth/tree/master/views , we won’t examine them exhaustivly, but we’ll look at the ‘login’ view to see how the HTML form is implemented.

Note lines 14–16, the ‘app.locals.errMsg’ variable previously defined in app.js is used here to pass any error feedback messages to the user. Notice also the ‘action’ and ‘method’ attributes of the actual HTML form as well as the ‘name’ attributes of the form input widgets.

Routes:

With all the preceding work done, we can finally create our router module.

Note how in lines 6 & 7 we pull in both the passport and utilities modules. The ‘isLoggedIn’ function (lines 18–23) is used to protect the ‘/dashboard’ route, essentially it ensures that only logged in i.e. authenticated users can access that route.

The interesting portion is covered by lines 56–71/ 78–93, they both do the same thing. Lets walk through what’s happening.

Line 57, we’ve already reviewed how passport.authenticate functions, we’re using a custom callback (because of how we want the informational messages to be handled) which is which we call passport.authenticate() within the route handler.

Notice the 3 parameters passed to passport.authenticate — these come from the done() method within the passport authentication strategy e.g. ‘local-signup’. Note for instance lines 59 & 81, because we haven’t defined a custom error handler, this error which represents the dBase connection error will trigger a ‘HTTP 500’ response i.e. internal server error.

Lines 61 & 83 are triggered only if there’s no dBase connection error but for some reason there’s an error creating/retrieving the user i.e within the passport authentication strategy done is called with ‘false’ as the argument which maps to ‘user’ in the passport.authenticate() callback. The informational error message allows us to provide feedback to the user on the type of user error which occurred, This is also why we can send a ‘409’ status because the error is correctable by the user. HTTP 409' stands for ‘conflict’ and it means there’s a conflict between what the server expects and what the user is providing and its only used in circumstances in which the user can resolve the conflict perhaps by re-sending the info.

Finally if all goes well, lines 69 & 91 are triggered for their respective routes and the user is granted access to the dashboard.

For the ‘/dashboard’ route, notice line 98 — isLoggedIn(0 protects the dashboard from unauthenticated access by checking if the ‘req.isAuthenticated()’ method is truthy (lines 18–23). If truthy, it calls next() i.e. allowing the redirect, if falsy it redirects to the ‘/login’ route.

Lines 97–99 simply render the ‘dashboard’ view and pass some data to the template for rendering.

When the logout route is triggered, the ‘.logout()’ method is called on the ‘req’ object which causes the passport session to be terminated, ‘req.session.destroy()’ is called which destroys the session on the ‘session store’ and finally the user is redirected to the root i.e. ‘home page’.

Lastly we have some ‘/api’ routes which an administrator can use to see how many users exist in the dBase and other stuff.

Tying it all together-server:

Server.js is the entry point to our app. We simply create a Node http server and bind our configured ‘express’ app to it.

Wheew..!

Go ahead, run the app. It should look like the following:

Testing our code:

I’m sure you’ve noticed that contrary to ‘best practices’ we didn’t write any tests for our code before we created our app. The reason for that is that from my perspective, i’m not a big fan of BDD/TDD. Rather i believe software should be well reasoned out in advance and carefully architected against requirements, when the software has been built, then we can test to make sure it conforms to expectations.

We will write 11 tests to check our app against various scenarios. We will use mocha, should and supertest.

The test will cover the following areas in no particular order:

  1. can an unauthenticated user access the protected ‘/dashboard’ route?
  2. what happens if a user tries to signup without filling all the fields?
  3. what happens if a user ties to use an existing email address?
  4. what happens if a user meets all the conditions to sign up successfully?
  5. what happens if a non existent user tries to login?
  6. what happens if an existing user supplies an invalid username?
  7. what happens if an existing user supplies an invalid password?
  8. what happens if an existing supplies valid credentials?
  9. what happens if a logged in user tries to logout?

The test files can be found at https://github.com/oakinogundeji/express4.x-LocalAuth/tree/master/test simply open the directory in the terminal and type ‘mocha’… the 11 tests should pass.

Thank you for reading, please leave comments below.

I understand some people may prefer this post was less verbose with all the code and explanations, but this is the type of info i longed for when i was just starting out as a web developer.

--

--