Grow your SaaS organically with Content Marketing.
Try for free →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.
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:
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");
});
Let's put together some views for our sign-in and sign-up routes.
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
.
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!
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 application with npm start
.
Navigate to http://localhost:3000/local/signup and sign up using email/password.
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!
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 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
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("/");
});
});
Congratulations, we just built a usable authentication flow to sign our users. We learned how to:
I'm building a new SaaS to automate content marketing for your SaaS
Tools for SaaS Devs