In our last post, we designed the login process. This post will actually walk through the implementation. It assumes you have followed the steps in the post where we created the application’s skeleton and did the initial database migration steps so make sure you are starting from there. Also, please note that at the time this is being written, node.js is on version 0.8.11 so if this is a long time ago to you future travelers down this path, your mileage may vary. Login Screen: So our first step is to actually have a screen that gets rendered when someone navigates to the /login route. To do this in Node using Express is pretty straightforward. First, you need to create a view in the “views” subdirectory called login.jade (example below). If you aren’t familiar with the Jade templating technology, take a look at it’s home site.
extends layout block content form(action='/login', method='post')#login-form h1= title p You need to log in label Email input(name='user[email]') <br /> label Password input(name='user[password]', type='password') <br /> button(type='submit') Sign in
Next, we’ll need to add a file in the routes subdirectory called “login.js” to handle the rendering of a GET call to the /login route:
exports.index = function(req, res) { res.render('login', { title: 'Login' }); };
With that done, we can now connect our route in app.js and test it:
... var express = require('express') , routes = require('./routes') , user = require('./routes/user') , login = require('./routes/login') ... ... app.get('/', routes.index); app.get('/login', login.index); ...
Test it out by firing up the server from the command line (node app.js) and hitting localhost:3000/login from your browser. You should see a simple (and very unattractive) login page. Now we need to write some middleware to catch all http calls to the application and redirect them to this login page if they don’t have our session cookies. To do that, we’ll add some code to the top of app.js:
function sessionMiddleware(req, res, next) { if (req.url == '/login') { next(); } else { if (req.cookies.user == undefined) { res.redirect('/login'); } else if (req.cookies.session == undefined) { res.redirect('/login'); } else { next(); } } } ... ... app.use(express.methodOverride()); app.use(express.cookieParser()); app.use(sessionMiddleware); app.use(app.router); ...
Also add the cookieParser() and sessionMiddleware lines near the bottom. Now if you stop and restart your command-line and hit the site again, you’ll find that if you go to http://localhost:3000/ (or any other route for that matter), you will end up at the /login page. So now we need to do one last thing. Add code to actually capture the information entered on the login form, compare it to what we have in the database and set the necessary cookies to allow access to pages in the site. First things first, install a new npm module called ‘node-uuid’:
npm install node-uuid
Next, let’s add a migration to put a column called session_id on the users table:
db-migrate create add-session-id ... var dbm = require('db-migrate'); var type = dbm.dataType; exports.up = function(db, callback) { db.addColumn('users', 'session_id', 'string', callback); }; exports.down = function(db, callback) { db.removeColumn('users', 'session_id', callback); }; ... db-migrate up
Now, let’s create the code to deal with the POST on the login page. Modify the routes/login.js file to include a new method at the top of the file:
var crypto = require('crypto'); var uuid = require('node-uuid'); var pg = require('pg'); var connectionString = 'tcp://hrtool:password123%25@localhost/hrtool_dev'; exports.login = function(req, res) { pg.connect(connectionString, function(err, client) { shasum = crypto.createHash('sha1'); client.query('SELECT password from users where name=$1', [req.body.user.email], function(err, result) { hashedPassword = shasum.update(req.body.user.password).digest('base64'); if (result == undefined || result.rows.length != 1) { res.clearCookie('user'); res.clearCookie('session'); res.redirect('/login'); return; } if (result.rows[0].password == hashedPassword) { sessionID = uuid.v1(); client.query('update users set session_id=$1 where ' + 'name=$2', [sessionID, req.body.user.email], function(err, result) { }); res.cookie('user', req.body.user.email); res.cookie('session', sessionID); res.redirect('/'); } else { res.clearCookie('user'); res.clearCookie('session'); res.redirect('/login'); } }); }); }
Notice that we have to encode the ‘password123%’ password we chose to access PostgreSQL as ‘password123%25’ so that the percent sign is properly escaped in the connection string. We now have to add the route to the bottom of the app.js file:
... app.get('/login', login.index); app.post('/login', login.login); ...
If we re-launch the application at the command-line, login should be working using our ‘admin’, ‘password123%’ userid and password that we inserted into the database a few posts ago. The final thing we need to do is to modify our middleware to actually verify that the cookies passed in are legitimate. First, use npm to install another node module called “async” that allows us to do some synchronous work without too much difficulty. To do that, let’s create a new top-level directory called “helpers” and create a file called security.js that has these contents:
var async = require('async'); var pg = require('pg'); var connectionString = 'tcp://hrtool:password123%25@localhost/hrtool_dev'; exports.checkLogin = function(req, res) { async.waterfall([ function databaseConnect(callback) { pg.connect(connectionString, function(err, client) { callback(null, client); }); }, function getSessionId(client, callback) { client.query("SELECT session_id from users where name=$1", [req.cookies.user], function(err, result) { callback(err, result); }); }, function compareSessionId(result, callback) { if (result && result.rows && result.rows.length > 0) { if (result.rows[0].session_id != req.cookies.session) { res.redirect('/login'); } } else { res.redirect('/login'); } } ], function(err, result) { console.log('err = ' + err); res.redirect('/login'); }); };
Now we just need to edit the app.js middleware code we created to call our helper function like this:
var security = require('./helpers/security'); function sessionMiddleware(req, res, next) { if (req.url == '/login') { next(); } else { if (req.cookies.user == undefined) { res.redirect('/login'); } else if (req.cookies.session == undefined) { res.redirect('/login'); } else { security.checkLogin(req, res); next(); } } } ...
At this point, we should be able to re-launch our application from the command-line, delete the cookies we have in the browser and poke around a bit. Hitting any URL should take us to the /login page. Entering valid credentials should get us in. If we delete the cookies and then try to navigate to the / page, then we should end up right back at the /login page.