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

Featured Image
  • Languages: Node, Express.js
  • Tools Used: Passport.js
  • Time Saved: 4 weeks -> 30 mins
  • Source Code

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:

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

Step 1: 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");
});

Step 2: Building the UI

Let's put together some views for our sign-in and sign-up 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!

Step 3: 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),
    //...
  };
};

Step 4: Run the app

Run the application with npm start.

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

Step 5: 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!

Step 6: 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,
  })
);

Step 7: 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

Step 8: 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:

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

I'm building a new SaaS to automate content marketing for your SaaS

Check it out →

Tools for SaaS Devs