Authentication System using Passport.js, Node.js, and MongoDB - Part 2: Local Sign up and Sign in

In this guide, we will create an authentication system that will let our users log in using Local Sign-In.

Authentication System using Passport.js, Node.js, and MongoDB - Part 2: Local Sign up and Sign in
  • Languages: Node, Express.js
  • Tools Used: Passport.js
  • Time Saved: 4 weeks -> 30 mins
  • Source Code
  • Live Demo

This is Part 2 in the series of guides on creating an authentication system using Passport.js, Node.js and Mongo for your SaaS app.

Introduction

Authentication is used to verify a customer's identity using an email/password or a social login like Google, Facebook etc. Passport.js makes it easy to set up OAuth2 login strategies from all identity providers and acts a uniform interface between the user and the application. This is great for futureproofing your application and making it that much easier to maintain.

Sign up screen that allows users to sign up using their email address

In Part 2, we will:

  1. Build a local sign up/sign in button to authenticate users using a traditional email and password
  2. Save and manage user data in MongoDB
  3. Give useful notifications using Express Flash to the user

Adding Routes

Let's start by adding two backend routes to serve up views in Express.js. In app.js

app.get("/local/signup", (req, res) => {
  res.render("local/signup.ejs");
});

app.get("/local/signin", (req, res) => {
  res.render("local/signin.ejs");
});

Building the UI

Let's put together some views for our signin and signup routes.  

UI - Sign Up

Our lovely team at SaaSBase has created an awesome looking view for the sign up screen.

Completed sign up screen with styling included

In the interest of time, let's create an EJS file called views/local/signup.ejs and copy over the content from the repo here.

The most important logic on the page is the form that makes a POST call to the /auth/local/signup endpoint with the provided name, email, and password from the user.

We can also style the page by copying over the CSS from here and adding it to a new file called public/css/index.css.

Sweet, we have our Sign Up UI ready to go!

Save user profile on Sign Up

On the backend, we can capture the POST call and save the user in MongoDB.

We add a limit to the password that it has to be more than seven characters long to ensure users create a strong password. If they create a weak one, we redirect them to the sign up page and ask them to try again.

We also use the brcypt package to hash our password so as to not save the plain-text password and protect our user in the event of an security breach.

As an example, password would be saved as cGFzc3dvcmQ= in the database after bcrypt hashes it. To compare passwords later on, the package provides a method called compareSync that takes the hashed password plus the entered password and tells you if they are a match or not.

To learn more about how Bcrypt works, read here.

In app.js

//...
const uuid = require("uuid");
const bcrypt = require('bcrypt');
const UserService = require("./src/user");
//...

require("./src/config/passport");
require("./src/config/google");
require("./src/config/local");

app.post("/auth/local/signup", async (req, res) => {
  const { first_name, last_name, email, password } = req.body

  if (password.length < 8) {
    req.flash("error", "Account not created. Password must be 7+ characters long");
    return res.redirect("/local/signup");
  }

  const hashedPassword = await bcrypt.hash(password, 10)

  try {
    await UserService.addLocalUser({
      id: uuid.v4(),
      email,
      firstName: first_name,
      lastName: last_name,
      password: hashedPassword
    })
  } catch (e) {
    req.flash("error", "Error creating a new account. Try a different login method.");
    return res.redirect("/local/signup")
  }

  return res.redirect("/local/signin")
});

//...

We can add a new method to save a local user to MongoDB by editing src/user/user.service.js

//...

const addLocalUser = (User) => ({ id, email, firstName, lastName, password }) => {
  const user = new User({
    id, email, firstName, lastName, password, source: "local"
  })
  return user.save()
}

module.exports = (User) => {
  return {
    addLocalUser: addLocalUser(User),
    //...
  }
}

Run the app

Run the application with npm start.

Navigate to http://localhost:3000/local/signup and sign up using email/password.

UI - Sign In

In the interest of time, let's create an EJS file called views/local/signin.ejs and copy over the content from the repo here.

The most important logic on the page is the form that makes a POST call to the /auth/local/signin endpoint with the provided email, and password from the user.

Preview of the completed sign up screen UI

Sweet, we have our Sign In UI ready to go!

Set up Passport for Local Sign In

We can use Passport's Local Strategy to make it easy for us to validate if the user's credentials can be accepted or not.

Create a new file called src/config/local.js

const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy;
const UserService = require('../user')
const bcrypt = require('bcrypt');

passport.use(new LocalStrategy(
  async function (email, password, done) {
    const currentUser = await UserService.getUserByEmail({ email })

    if (!currentUser) {
      return done(null, false, { message: `User with email ${email} does not exist` });
    }

    if (currentUser.source != "local") {
      return done(null, false, { message: `You have previously signed up with a different signin method` });
    }
    console.log("currentuser", currentUser)
    if (!bcrypt.compareSync(password, currentUser.password)) {
      return done(null, false, { message: `Incorrect password provided` });
    }
    return done(null, currentUser);
  }
));

We can now add this strategy to app.js and use it when the POST call is made to /auth/local/signin

require("./src/config/local");

app.post('/auth/local/signin',
  passport.authenticate('local', {
    successRedirect: '/profile',
    failureRedirect: '/local/signin',
    failureFlash: true
  })
);

Run the app

Run the application with npm start.

Navigate to http://localhost:3000/local/signin and sign in. You should be redirected to the Profile page if the login was successful.

Profile page for a successfully signed in user

Logout a User

In app.js

app.get("/auth/logout", (req, res) => {
  req.flash("success", "Successfully logged out");
  req.session.destroy(function () {
    res.clearCookie("connect.sid");
    res.redirect("/");
  });
});

Next Steps

Congratulations, we just built a usable authentication flow to sign our users. We learned how to:

  1. Build a local sign up/sign in button to authenticate users using a traditional email and password
  2. Save and manage user data in MongoDB
  3. Give useful notifications using Express Flash to the user
Didn't find this guide useful? Let me know