This document shows how to make this example.

Initial project setup

  1. Open the command-line (Git Bash or Terminal).

  2. Let’s create a new project

    express --no-view Node5-Authentication
  3. Go into the project folder you created. After this step, assume you are in this folder.

    cd Node5-Authentication
  4. From the command line, install dependencies listed in package.json. This command may take a while to run.

    npm install

Database setup

  1. Install Node.js bindings to PostgreSQL .

    npm install pg --save
  2. We will need to define DATABASE_URL into .env to access the database.

    # Windows users only (replace password with the database password you created during installation)
    echo "DATABASE_URL='postgres://postgres:password@localhost/postgres'" > .env
    # Mac users
    echo "DATABASE_URL='postgres://localhost'" > .env
  3. To read the .env file into process.env, install dotenv:

    npm install dotenv --save

Authentication dependencies setup

  1. Install bcryptjs. This encrypts plain text password. Reference: https://www.npmjs.com/package/bcryptjs

    npm install bcryptjs --save
  2. Install three packages

    passport.js. Passport is authentication middleware for Node.js (Reference: http://www.passportjs.org/)

    passport-local: local authentication (not using third-party authentication, such as passport-google, passport-twitter, passport-github…​)

    npm install passport passport-local express-flash --save
  3. Install session middleware. Authentication requires session middleware

    npm install express-session --save

Create a table and insert rows

  1. Connect to the local database: You will need to start Postgres first (Windows users: go to git bash and you will need to provide a password)

    # Windows users
    psql -U postgres
    # Mac users
    psql
  2. Once connected, create a user table. Remember: NEVER store the password directly in the database.

    CREATE TABLE users ("id" serial primary key, username text, password text, fullname text, prefer text);
  3. Insert a user (admin) with password hello (for TESTING purposes only):

    INSERT INTO users (username,password,fullname,prefer) VALUES('admin','$2a$10$tXMKF036p0ZYIxF/cJEHauw/TFrcho4DXy41Kt12D3Lbnzr221hmK','Alan Turing','Prof');
    How to create an encrypted password?

    Create a file bcrypt.js

    var bcrypt = require('bcryptjs');
    var salt = bcrypt.genSaltSync(10);
    var hash = bcrypt.hashSync("hello", salt);
    console.log(salt);
    console.log(hash);
    When you run bcrypt.js, you will see encrypted 'hello'
    node bcrypt.js

    Did you see it?

Let’s modify files

  1. Modify app.js:

    var express = require('express');
    var path = require('path');
    var cookieParser = require('cookie-parser');
    var logger = require('morgan');
    
    // add this line before var indexRouter = require('./routes/index');
    // If not, you MAY have "password must be a string" error
    var env = require('dotenv').config();
    
    var indexRouter = require('./routes/index');   // index.js
    var usersRouter = require('./routes/users');   // users.js
    
    var app = express();
    
    
    // new stuff starts here
    var session = require('express-session');
    
    // Flash is needed for passport middleware
    var flash = require('express-flash');
    
    const Client = require('pg').Client;
    // create an instance from Client
    const client = new Client({
      connectionString: process.env.DATABASE_URL
    });
    client.connect(); //connect to database
    
    
    // javascript password encryption (https://www.npmjs.com/package/bcryptjs)
    var bcrypt = require('bcryptjs');
    //  authentication middleware
    var passport = require('passport');
    // authentication locally (not using passport-google, passport-twitter, passport-github...)
    var LocalStrategy = require('passport-local').Strategy;
    
    passport.use(new LocalStrategy({
      usernameField: 'username', // form field name
      passwordField: 'password'
      },
      function(username, password, done) {
      client.query('SELECT * FROM users WHERE username = $1', [username], function(err, result) {
        if (err) {
          console.log("SQL error"); //next(err);
          return done(null,false, {message: 'sql error'});
        }
        if (result.rows.length > 0) {
          let matched = bcrypt.compareSync(password, result.rows[0].password);
           if (matched) {
            console.log("Successful login, ", result.rows[0]);
            return done(null, result.rows[0]);
          }
        }
        console.log("Bad username or password");
        // returning to passport
        // message is passport key
        return done(null, false, {message: 'Bad username or password'});
      });
    })
    );
    
    // Store user information into session
    passport.serializeUser(function(user, done) {
      //return done(null, user.id);
      return done(null, user);
    });
    
    // Get user information out of session
    passport.deserializeUser(function(id, done) {
      return done(null, id);
    });
    
    // Use the session middleware
    // configure session object to handle cookie
    // req.flash() requires sessions
    // secret: 'webDev' This is the secret used to sign the session ID cookie.
    // It should be a long, randomly-generated string to ensure security.
    app.use(session({
      secret: 'WebDev',
      resave:false,
      saveUninitialized: true,
    }));
    
    app.use(flash());
    
    app.use(passport.initialize());
    app.use(passport.session());
    // new stuff ends here
    
    
    
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', indexRouter);
    app.use('/users', usersRouter);
    
    module.exports = app;
  2. Modify routes/users.js:

    var express = require('express');
    var router = express.Router();
    /*
    // localhost:3000/users/
    router.get('/', function(req, res, next) {
      res.send('respond with a resource');
    });
    */
    
    // new stuff
    var path = require('path');
    var env = require('dotenv').config();
    // Connect to process.env.DATABASE_URL when your app initializes:
    // Read only reference value (const)
    // get only Client class from pg package
    const Client = require('pg').Client;
    
    // create an instance from Client
    const client = new Client({
      connectionString: process.env.DATABASE_URL
    });
    
    // connect to the DATABASE_URL
    client.connect();
    
    var passport = require('passport');
    var bcrypt = require('bcryptjs');
    
    router.get('/logout', function(req, res, next){
      req.logout(function(err) {
        if (err) {
          console.log("unable to logout:", err);
          return next(err);
        }
      });   //passport provide it
      res.redirect('/'); // Successful. redirect to localhost:3000/
    });
    
    
    
    function loggedIn(req, res, next) {
      if (req.user) {
        next(); // req.user exists, go to the next function (right after loggedIn)
      } else {
        res.redirect('/users/login'); // user doesn't exists redirect to localhost:3000/users/login
      }
    }
    
    router.get('/profile',loggedIn, function(req, res){
      // req.user: passport middleware adds "user" object to HTTP req object
      res.sendFile(path.join(__dirname,'..', 'public','profile.html'));
    });
    
    function notLoggedIn(req, res, next) {
      if (!req.user) {
        next();
      } else {
        let prefer = req.user.prefer;
        res.redirect('/users/profile?name='+prefer);
      }
    }
    
    // localhost:3000/users/login
    router.get('/login', notLoggedIn, function(req, res){
      //success is set true in sign up page
      res.sendFile(path.join(__dirname,'..', 'public','login.html'));
    });
    
    // localhost:3000/users/login
    router.post('/login',
      // This is where authentication happens - app.js
      // authentication locally (not using passport-google, passport-twitter, passport-github...)
      passport.authenticate('local', { failureRedirect: 'login?message=Incorrect+credentials', failureFlash:true }),
      function(req, res,next) {
        let prefer = req.user.prefer;
        console.log("fullname: ", prefer);
        res.redirect('/users/profile?name='+prefer); // Successful. redirect to localhost:3000/users/profile
    });
    
    
    router.get('/signup',function(req, res) {
      // If logged in, go to profile page
      if(req.user) {
        let prefer = req.user.prefer;
        return res.redirect('/users/profile?name='+prefer);
      }
      res.sendFile(path.join(__dirname,'..', 'public','signup.html'));
    });
    
    function createUser(req, res, next){
      var salt = bcrypt.genSaltSync(10);
      var password = bcrypt.hashSync(req.body.password, salt);
    
      client.query('INSERT INTO users (username, password, fullname, prefer) VALUES($1, $2, $3, $4)', [req.body.username, password,req.body.fullname,req.body.prefer], function(err, result) {
        if (err) {
          console.log("unable to query INSERT");
          return next(err); // throw error to error.hbs.
        }
        console.log("User creation is successful");
        res.redirect('/users/login?message=We+created+your+account+successfully!');
      });
    }
    
    router.post('/signup', function(req, res, next) {
      client.query('SELECT * FROM users WHERE username=$1',[req.body.username], function(err,result){
        if (err) {
          console.log("sql error ");
          next(err); // throw error to error.hbs.
        }
        else if (result.rows.length > 0) {
          console.log("user exists");
          res.redirect('/users/signup?error=User+exists');
        }
        else {
          console.log("no user with that name");
          createUser(req, res, next);
        }
      });
    });
    // new stuff ends here
    
    module.exports = router;
  3. Modify public/index.html and save:

    <html>
    <head>
      <title>Express</title>
      <link rel="stylesheet" href="/stylesheets/style.css">
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    
    </head>
    <body>
      <nav class="menu">
    		<ul>
          <li><a href="/users/login">Log in</a> | </li>
    			<li><a href="/users/signup">Sign up</a></li>
    		</ul>
    	</nav>
      <h1>Express</h1>
      <p>Welcome to Express</p>
    </body>
    </html>
  4. Create public/login.html and save:

    <html>
    <head>
      <title>Express</title>
      <link rel="stylesheet" href="/stylesheets/style.css">
      <link rel='stylesheet' href='/stylesheets/login.css' />
    
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    
      <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin="anonymous"></script>
      <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin="anonymous"></script>
      <script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="anonymous"></script>
    
      <script src="/javascripts/showPassword.js" ></script>
    
    </head>
    
    <body>
      <nav class="menu">
    		<ul><a href="/">Back to Home</a></ul>
      </nav>
      <main class="text-center">
        <!-- localhost:3000/users/login -->
        <form class="form-signin" method="post" action="login">
          <div id="message">
            <!-- This element's contents will be replaced with your component. -->
          </div>
          <h1 class="h3 mb-3 font-weight-normal">Sign in</h1>
          <input class="form-control" type="text" name="username" placeholder="User name or email address" required autofocus>
          <input class="form-control" type="password" name="password" id="password" placeholder="Password" required>
    
          <div class="checkbox mb-3">
            <label><input type="checkbox" onclick="showPassword()"> Show password</label>
          </div>
          <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
    
          <div style="margin-top:15px ; border-top: 1px solid #888; padding-top:15px" >
            Don't have an account! <a href="signup" >Sign Up Here</a>
          </div>
        </form>
        </main>
      <!-- Load our React component. -->
      <script src="/javascripts/login.js" type="text/babel"></script>
    </body>
    </html>
  5. Create public/signup.html and save:

    <html>
    <head>
      <title>Express</title>
      <link rel="stylesheet" href="/stylesheets/style.css">
      <link rel='stylesheet' href='/stylesheets/login.css' />
    
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    
      <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin="anonymous"></script>
      <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin="anonymous"></script>
      <script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="anonymous"></script>
      <script src="/javascripts/showPassword.js" ></script>
    
    </head>
    <body>
      <nav class="menu">
        <ul><a href="/">Back to Home</a></ul>
    	</nav>
    
      <main class="text-center">
        <form class="form-signin" method="post" action="signup">
          <div id="message">
            <!-- This element's contents will be replaced with your component. -->
          </div>
          <h1 class="h3 mb-3 font-weight-normal">Create a new account</h1>
          <input class="form-control" type="text" name="fullname" placeholder="Full Name" required autofocus>
          <input class="form-control" type="text" name="prefer" placeholder="Preferred Name" required>
          <input class="form-control" type="text" name="username" placeholder="User name or email address" required>
          <input class="form-control" type="password" name="password" id="password" placeholder="Password" required>
    
          <div class="checkbox mb-3">
            <label><input type="checkbox" onclick="showPassword()"> Show password</label>
          </div>
          <button class="btn btn-lg btn-primary btn-block" type="submit">Sign up</button>
    
          <div style="margin-top:15px ;border-top: 1px solid 888; padding-top:15px" >
            Already have an account? <a href="login" >Sign In Here</a>
          </div>
    
        </form>
        </main>
    
      <!-- Load our React component. -->
      <script src="/javascripts/signup.js" type="text/babel"></script>
    </body>
    </html>
  6. Create public/profile.html and save:

    <html>
    <head>
      <title>Express</title>
      <link rel="stylesheet" href="/stylesheets/style.css">
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
      <nav class="menu">
    		<ul>
    			<li><a href="/">Back to Home</a> | </li>
    			<li><a href="logout">Log out</a></li>
    		</ul>
      </nav>
      <h1><div id="name"></div> </h1>
      <h2>Welcome to your profile!</h2>
      <h3>Feel free to make this page user friendly.</h3>
      <br>
      <script>
        const urlParams = new URLSearchParams(window.location.search);
        const name = urlParams.get('name');
        if (name) {
          document.getElementById('name').innerHTML = name;
        }
      </script>
    </body>
    </html>
  7. Create public/javascripts/login.js and save:

    const urlParams = new URLSearchParams(window.location.search);
    const message = urlParams.get('message');
    
    if (message) {
      const LoginComponent = () => {
        return (
          <>
          <p>{message}</p>
          </>
        );
      };
    
      const login = ReactDOM.createRoot(document.getElementById('message'));
      login.render(<LoginComponent />);
    }
  8. Create public/javascripts/signup.js and save:

    const urlParams = new URLSearchParams(window.location.search);
    const message = urlParams.get('error');
    
    if (message) {
      const SignupComponent = () => {
        return (
          <>
          <p>{message}. Use different user name!</p>
          </>
        );
      };
    
      const signup = ReactDOM.createRoot(document.getElementById('message'));
      signup.render(<SignupComponent />);
    }
  9. Create public/javascripts/showPassword.js and save:

    function showPassword() {
      var x = document.getElementById("password");
      if (x.type === "password") {
        x.type = "text";
      } else {
        x.type = "password";
      }
    }
  10. Modify public/stylesheets/style.css and save:

    body {
      padding: 50px;
      font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
    }
    a {
      color: #00B7FF;
    }
    #message {
      color: red;
    }
    nav ul li {
      display: inline;
      list-style-type: none;
    }
    .menu {text-align: right;}
  11. Create public/stylesheets/login.css and save:

    main {
      height: 100%;
      display: -ms-flexbox;
      display: flex;
      -ms-flex-align: center;
      align-items: center;
      padding-top: 40px;
      padding-bottom: 40px;
      background-color: #f5f5f5;
    }
    .form-signin {
      width: 100%;
      max-width: 330px;
      padding: 15px;
      margin: auto;
    }
    .form-signin .checkbox {
      font-weight: 400;
    }
    .form-signin .form-control {
      position: relative;
      box-sizing: border-box;
      height: auto;
      padding: 10px;
      font-size: 16px;
    }
    .form-signin .form-control:focus {
      z-index: 2;
    }
  12. Run the server to see the example project.

    npm start
  13. Go to http://localhost:3000/