1
Write a post

How to build a simple session-based authentication system with NodeJS from scratch.

Published Apr 06, 2017Last updated Apr 09, 2017
How to build a simple session-based authentication system with NodeJS from scratch.

For those of us not familiar with what an Authentication System is, fear not.

Authentication system is one which allows a user to access a resource only after supplied credentials are compared with that stored in the database and found to be the same.

Authentication can either be Session-based or Token-based.
Session-based authentication makes use of cookie stored in the user's browser in order to verify their identity after Login while Token-based authentication makes use of JSON Web Tokens(JWT) which is sent along with every request to verify the user's identity and this makes it stateless. To know more about their differences Read more...

After searching the internet on how to write a session-based authentication system in NodeJS from scratch without using any module/library and I couldn't find any good resource on that, I decided to come up with this tutorial for curious minds like me.

Let's get started. Below are the list of technologies/packages needed to build our authentication system:

NodeJS: It's a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js(or NodeJS) uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. This will be our back-end language for this project.

(Node Package Manager)NPM: It's a package manager for NodeJS.

Express: It's a fast, unopinionated, minimalist web framework for NodeJS. It's undoubtedly the most-popular web framework for NodeJS which provides a robust set of features for web and mobile applications.

Sequelize: Sequelize is a promise-based Node.js Object Relational Mapper(ORM) for Postgres, MySQL, SQLite and Microsoft SQL Server. It features solid transaction support, relations, read replication and more.

PostgreSQL: PostgreSQL is an advanced object-relational database management system
that supports an extended subset of the SQL standard, including
transactions, foreign keys, subqueries, triggers, user-defined types
and functions.

Body-Parser: It's a body parsing middleware which allows you to parse incoming request bodies in a middleware before your handlers, available under the req.body property.

Cookie-Parser: It's a cookie parsing middleware which allows you to parse Cookie header and populate req.cookies with an object keyed by the cookie names.

Express-Session: It's a simple session middleware for Express which allows you to manage sessions in your NodeJS-Express applications.

Bcrypt: It's a NodeJS package that allows us to hash passwords for security purposes.

Morgan: It's a HTTP request logger middleware for NodeJS, which allows you to print information about your request on the command line.

Conventions and Assumptions:
1. For command-line commands, I won't mention "Press Enter", in order to save time. Kindly press the "Enter" key after typing the commands.
2. It is assumed that you are using a unix command-line application like Power Shell on Windows or Terminal on Mac.
3. Code snippets that starts with $ indicates commands to be run on your command-line(mostly one-line code) while those that did not start with $ are meant to be inside our files.
4. The aim of this tutorial is not primarily to follow the convention and best-practices out there, but to basically show us how to create a simple authentication system in NodeJS.

To verify that you have the NodeJS, NPM and PostgreSQL installed on your PC, type the following commands on your command-line one after the other:

$ node --version
$ npm --version
$ psql --version

If you have their version printed, then you are Good to go. Let's create the database-user and the database for our application. Type the following on your command-line:

$ psql -c 'CREATE ROLE "postgres" with Login Superuser Createrole CreateDB Replication BypassRLS'

We need to create an equivalent database for postgres user before we can work with it.

$ psql -c 'CREATE DATABASE "postgres"'

and then create the database for our application:

$ psql -U postgres -c 'CREATE DATABASE "auth-system"'

Let's create the directories for our application. Navigate to the your desired root folder(e.g: Desktop/Documents) on your command-line, then type:

$ mkdir auth-system && cd auth-system

The first command creates the folder while the second command navigates to the directory. Let's create the remaining directories.

$ mkdir models public

The models direcory will host our User model while public direcory will host our static files .

To initialize a NodeJS application and forcefully avoid unnecessary questions by Mr. NPM 😃 , type this on the command-line:

$ npm init -f

Ready? Let's install the NPM packages needed by our application. Type this on the command-line:

$ npm install --save bcrypt body-parser cookie-parser express express-session morgan pg sequelize

The command above will add the packages to our list of dependencies inside the package.json file and will also create a node_modules directory(if one doesn't exist) with our packages installed inside.

Next is to create our User model file and express-configuration file. Type the following on your command-line:

$ touch models/user.js server.js

The command above will create a user.js file inside the models directory and also server.js file in your root directory.

Our Folder structure should now look this way:
Screen Shot 2017-04-06 at 9.24.56 PM.png

Now let's add the following code to our models/user.js file:

var Sequelize = require('sequelize');
var bcrypt = require('bcrypt');

// create a sequelize instance with our local postgres database information.
var sequelize = new Sequelize('postgres://postgres@localhost:5432/auth-system');

// setup User model and its fields.
var User = sequelize.define('users', {
    username: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
    },
    email: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
    },
    password: {
        type: Sequelize.STRING,
        allowNull: false
    }
}, {
    hooks: {
      beforeCreate: (user) => {
        const salt = bcrypt.genSaltSync();
        user.password = bcrypt.hashSync(user.password, salt);
      }
    },
    instanceMethods: {
      validPassword: function(password) {
        return bcrypt.compareSync(password, this.password);
      }
    }    
});

// create all the defined tables in the specified database.
sequelize.sync()
    .then(() => console.log('users table has been successfully created, if one doesn\'t exist'))
    .catch(error => console.log('This error occured', error));

// export User model for use in other files.
module.exports = User;

Please refer to the comments above each block for explanation on what each block of code does. Feel free to use the comment-box below for any concerns.

It's time to setup our express configuration inside server.js file:

var express = require('express');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var morgan = require('morgan');
var User = require('./models/user');

// invoke an instance of express application.
var app = express();

// set our application port
app.set('port', 9000);

// set morgan to log info about our requests for development use.
app.use(morgan('dev'));

// initialize body-parser to parse incoming parameters requests to req.body
app.use(bodyParser.urlencoded({ extended: true }));

// initialize cookie-parser to allow us access the cookies stored in the browser. 
app.use(cookieParser());

// initialize express-session to allow us track the logged-in user across sessions.
app.use(session({
    key: 'user_sid',
    secret: 'somerandonstuffs',
    resave: false,
    saveUninitialized: false,
    cookie: {
        expires: 600000
    }
}));


// This middleware will check if user's cookie is still saved in browser and user is not set, then automatically log the user out.
// This usually happens when you stop your express server after login, your cookie still remains saved in the browser.
app.use((req, res, next) => {
    if (req.cookies.user_sid && !req.session.user) {
        res.clearCookie('user_sid');        
    }
    next();
});


// middleware function to check for logged-in users
var sessionChecker = (req, res, next) => {
    if (req.session.user && req.cookies.user_sid) {
        res.redirect('/dashboard');
    } else {
        next();
    }    
};


// route for Home-Page
app.get('/', sessionChecker, (req, res) => {
    res.redirect('/login');
});


// route for user signup
app.route('/signup')
    .get(sessionChecker, (req, res) => {
        res.sendFile(__dirname + '/public/signup.html');
    })
    .post((req, res) => {
        User.create({
            username: req.body.username,
            email: req.body.email,
            password: req.body.password
        })
        .then(user => {
            req.session.user = user.dataValues;
            res.redirect('/dashboard');
        })
        .catch(error => {
            res.redirect('/signup');
        });
    });


// route for user Login
app.route('/login')
    .get(sessionChecker, (req, res) => {
        res.sendFile(__dirname + '/public/login.html');
    })
    .post((req, res) => {
        var username = req.body.username,
            password = req.body.password;

        User.findOne({ where: { username: username } }).then(function (user) {
            if (!user) {
                res.redirect('/login');
            } else if (!user.validPassword(password)) {
                res.redirect('/login');
            } else {
                req.session.user = user.dataValues;
                res.redirect('/dashboard');
            }
        });
    });


// route for user's dashboard
app.get('/dashboard', (req, res) => {
    if (req.session.user && req.cookies.user_sid) {
        res.sendFile(__dirname + '/public/dashboard.html');
    } else {
        res.redirect('/login');
    }
});


// route for user logout
app.get('/logout', (req, res) => {
    if (req.session.user && req.cookies.user_sid) {
        res.clearCookie('user_sid');
        res.redirect('/');
    } else {
        res.redirect('/login');
    }
});


// route for handling 404 requests(unavailable routes)
app.use(function (req, res, next) {
  res.status(404).send("Sorry can't find that!")
});


// start the express server
app.listen(app.get('port'), () => console.log(`App started on port ${app.get('port')}`));

Static Files

Now, let's create the necessary static files(signup.html, login.html and dashboard.html) for our application. Type the following command on your command-line:

$ touch public/signup.html public/login.html public/dashboard.html

Our Folder structure should now look this way:
Screen Shot 2017-04-06 at 10.33.14 PM.png

Let's add the following code to our public/signup.html:

<html>
    <head>
        <title>Login Here</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    </head>
    <body class="container">
        <div class="page-header">
            <h1>Simple Auth-System</h1>
        </div>

        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav">
                        <li><a href="/">Home</a></li>
                        <li><a href="/signup">Sign Up</a></li>
                        <li><a href="/dashboard">Dashboard</a></li>
                    </ul>

                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="/login">Log In</a></li>
                        <li><a href="/logout">Log Out</a></li>
                    </ul>
                </div><!-- /.navbar-collapse -->
            </div><!-- /.container-fluid -->
        </nav>

        <div class="container row">
            <div class="jumbotron col-sm-4 pull-center">
                <form action="/signup" method="post">
                    <div>
                        <label>Username:</label>
                        <input type="text" name="username"/>
                    </div>
                    <div>
                        <label>Email:</label>
                        <input type="text" name="email"/>
                    </div>    
                    <div>
                        <label>Password:</label>
                        <input type="password" name="password"/>
                    </div>
                    <div>
                        <input class="btn btn-primary" type="submit" value="Sign Up"/>
                    </div>
                </form>                  
            </div>          
        </div>
    </body>
</html>

Next is the public/login.html:

<html>
    <head>
        <title>Login Here</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    </head>
    <body class="container">
        <div class="page-header">
            <h1>Simple Auth-System</h1>
        </div>

        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav">
                        <li><a href="/">Home</a></li>
                        <li><a href="/signup">Sign Up</a></li>
                        <li><a href="/dashboard">Dashboard</a></li>
                    </ul>

                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="/login">Log In</a></li>
                        <li><a href="/logout">Log Out</a></li>
                    </ul>
                </div><!-- /.navbar-collapse -->
            </div><!-- /.container-fluid -->
        </nav>

        <div class="container row">
            <div class="jumbotron col-sm-4 pull-center">
                <form action="/login" method="post">
                    <div>
                        <label>Username:</label>
                        <input type="text" name="username"/>
                    </div>
                    <div>
                        <label>Password:</label>
                        <input type="password" name="password"/>
                    </div>
                    <div>
                        <input class="btn btn-primary" type="submit" value="Log In"/>
                    </div>
                </form>                  
            </div>          
        </div>
    </body>
</html>

And finally our public/dashboard.html:

<html>
    <head>
        <title>Login Here</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    </head>
    <body class="container">
        <div class="page-header">
            <h1>Simple Auth-System</h1>
        </div>

        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav">
                        <li><a href="/">Home</a></li>
                        <li><a href="/signup">Sign Up</a></li>
                        <li><a href="/dashboard">Dashboard</a></li>
                    </ul>

                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="/login">Log In</a></li>
                        <li><a href="/logout">Log Out</a></li>
                    </ul>
                </div><!-- /.navbar-collapse -->
            </div><!-- /.container-fluid -->
        </nav>

        <div class="container row">
           <h1>Hi, Welcome to your Dashboard</h1>
        </div>
    </body>
</html>

Now Let's fire up the server, type the following command on your command-line:

$ node server.js

Open your browser and navigate to http://localhost:9000/signup, register with a username, email and password.

After registration and successful login, check your browser to confirm that the cookie has been saved. You should see something similar to this:
Screen Shot 2017-04-06 at 10.38.50 PM.png

Click the logout button and check your cookie once again to confirm that it has been deleted.

Yippee!!! We just finished building our mini authentication system, without using Passport or any other magical module to achieve that and that was pretty simple!!!.

Watch out for the next episode of this tutorial, where we will get to use a templating engine which will allow us to render dymanic contents on our page and also for content-reusability.

I like and do appreciate feedback, kindly use the comment-box below for questions, suggestions and feedback. Thanks

Discover and read more posts from JAMIU Abdulmujeeb
get started
Enjoy this post?

Leave a like and comment for JAMIU

5
4
4Replies
lachlan greenbank
2 months ago

This is a great tutorial thanks for posting,

i have an issue running the code though.

When i try to login to my created user i get this error

Unhandled rejection TypeError: user.validPassword is not a function
at C:\Users\lachlan\Documents\expressApp\app.js:94:31
at tryCatcher (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\promise.js:512:31)
at Promise._settlePromise (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\promise.js:569:18)
at Promise._settlePromise0 (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\promise.js:614:10)
at Promise._settlePromises (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\promise.js:693:18)
at Async._drainQueue (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\async.js:133:16)
at Async._drainQueues (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\async.js:143:10)
at Immediate.Async.drainQueues (C:\Users\lachlan\Documents\expressApp\node_modules\bluebird\js\release\async.js:17:14)
at runCallback (timers.js:666:20)
at tryOnImmediate (timers.js:639:5)
at processImmediate [as _immediateCallback] (timers.js:611:5)

it has something to do with the instance method in the user.js file.

i copied and pasted all your code exactly so i don’t see how this issue is arising, thanks for any help you can provide with this error.

Thank you

The Master
4 months ago

Smells nice

ABDULRAHMAN
4 months ago

Great one. It is indeed a very neat and straightforward way to simple authentication system in NodeJS. Keep it up.

Abdulmujeeb Jamiu
4 months ago

Thanks

Show more replies

Get curated posts in your inbox

Learn programming by reading more posts like this