Prerequisites
To follow along with this tutorial, you’ll need to have Node and MongoDB installed on your machine. You can install Node by heading to the official Node download page and grabbing the correct binaries for your system.
Also, here is a link to download mongodb.
Creating the Project
Once all of the prerequisite software is set up, we can get started. We’ll begin by creating the folder for our app and then accessing that folder on the terminal:
mkdir Auth
cd Auth
To create the node app, we’ll use the following command:
npm init -y
Now, we'll need to install express and other dependencies:
npm i express bcryptjs body-parser connect-flash ejs express-session mongoose passport passport-local validatorjs
Now let's configure babel to make use of es6
syntax
npm i @babel/core @babel/node --save-dev
Then we create a file .babelrc
in the root of our project folder and paste:
{
"presets": ["@babel/preset-env"]
}
When that’s done, create an index.js
file in the root folder of your app and add the following content to it:
import express from 'express';
import path from 'path';
import ejs from 'ejs';
import flash from 'connect-flash';
import session from 'express-session';
import passport from 'passport';
import mongoose from 'mongoose';
const __dirname = path.resolve();
const app = express();
const port = process.env.PORT || 5000;
//body parser
app.use(express.json());
app.use(express.urlencoded({extended: false}));
mongoose.Promise = global.Promise;
//Database Connection
mongoose.connect(
'mongodb://localhost:27017/passport',
{useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false }
)
.then(() => console.log('Database Connected'))
.catch(error => console.log(error));
//Session
app.use(session({
secret: 'secret',
resave: true,
saveUninitialized: true
}));
//Passport Middleware
app.use(passport.initialize());
app.use(passport.session());
//Flash
app.use(flash());
//Global Variables
app.use((req, res, next) => {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
res.locals.user = req.user || null;
next();
});
//set view engine
app.use(expressLayouts);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.static(path.join(__dirname, 'public')))
//load routes
import indexRouter from './routes/index.js';
import usersRouter from './routes/users.js';
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.listen(port, ()=>{
console.log(`Server running on port ${port}`);
});
Now let us explain the above code.
First, we import Express and create our Express app by calling express()
. Then we define the directory from which to serve our static files.
As you can, see we’re configuring express-session with a secret to sign the session ID cookie (you should choose a unique value here), and two other fields, resave and saveUninitialized
. The resave field forces the session to be saved back to the session store, and the saveUninitialized
field forces a session that is “uninitialized” to be saved to the store. To learn more about them, check out their documentation, but for now it’s enough to know that for our case we want to keep them false.
Then, we import passport
and initialize it along with its session authentication middleware, directly inside our Express app
Routes
Now let us create six directories controllers
, middleware
, models
, public
, routes
, views
. Inside routes
directory, let's create two files index.js
and users.js
.
Inside ./routes/index.js
, add this code:
import Router from 'express';
const router = Router();
//auth middleware
import auth from "../middleware/auth.js";
router.get('/', (req, res)=>{
res.render('welcome');
});
//dashboard
router.get('/dashboard', auth, (req, res)=>{
res.render('dashboard', {name: req.user.name});
});
export default router;
In the above code, we're making use of express router to handle our app routes. Obviously we have two routes. The first get request will render a view welcome
which will display our app's landing page.
The second route will display the users dashboard which we will handle
very soon.
Then finally, we export it to use in ./index.js
.
Let's add one more file that will handle users routes ./routes/users.js
import Router from 'express';
const router = Router();
import UserController from "../controllers/UserController.js";
//Login
router.get('/login', UserController.showLoginForm);
router.post('/login', UserController.login);
//Register
router.get('/register', UserController.registerationForm);
router.post('/register', UserController.register);
router.get('/logout', UserController.logout);
export default router;
Controllers
Now let's create a file ./controllers/usersController.js
with this content
import Validator from 'validatorjs';
import bcrypt from 'bcryptjs';
import passport from 'passport';
import User from "../models/User.js";
import passportLocal from 'passport-local';
const LocalStrategy = passportLocal.Strategy;
//Passport auth
passport.use(
new LocalStrategy({usernameField: 'email', }, (email, password, done) =>{
//Match User
User.findOne({email: email.toLowerCase()})
.then(user => {
if (!user){
return done(null, false, {message: 'Email not found'})
}
//Match Passwords
bcrypt.compare(password, user.password, (err, isMatched) => {
if (err) throw err;
if (isMatched){
return done(null, user);
}else {
return done(null, false, {message: 'Incorrect Password'})
}
});
})
.catch(err => console.log(err))
})
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
}); // End of passport config
const UserController = {
showLoginForm: (req, res) => {
//If already logged in return to dashboard
if (req.isAuthenticated()) {
return res.redirect('/dashboard');
}
res.render('login');
},
login: (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/users/login',
failureFlash: true
})(req, res, next);
},
logout: (req, res)=>{
req.logout();
req.flash('success_msg', 'You are logged out');
res.redirect('/users/login')
},
registerationForm: (req, res) => {
//If already logged in return to dashboard
if (req.isAuthenticated()) {
return res.redirect('/dashboard');
}
res.render('register');
},
register: async (req, res) => {
//Validate requests
const validation = new Validator(
{
name: req.body.name,
email: req.body.email,
password: req.body.password,
password_confirmation: req.body.password_confirmation
},
{
name: ['required', 'string', 'min:4', 'max:255'],
email: ['required', 'email'],
password: ['required', 'min:6', 'confirmed']
}
);
// If validation fails
if (validation.fails()){
//get errors
const errname = validation.errors.get('name');
const erremail = validation.errors.get('email');
const errpassword = validation.errors.get('password');
//get form data
let {name, email, password} = req.body;
return res.render('register', {errname, erremail, errpassword, name, email, password} );
}
//Validation pass
//Check if email exists
const emailExists = await User.findOne({email: req.body.email})
if (emailExists) {
const {name, email, password} = req.body;
let erremail = 'This email already exists';
return res.render('register', {name, email, password, erremail} );
} else //If Email does not exist...
{
//save new user
await validation.passes(()=> {
//Hash Password
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(req.body.password, salt, function(err, hash) {
const newUser = new User({
name: req.body.name,
email: req.body.email.toLowerCase(),
password: hash
});
newUser.save().then(() =>{
//Redirect to Login Page
req.flash('success_msg', 'Registration successful, you may login now')
return res.redirect('login')
}).catch(() => {
return res.send('could not save user');
});
});
}); // ./bcrypt
}); // ./validation passes
}
} // ./register Async
}
export default UserController;
Inside the above code we use:
Validator.js: To validate our form inputs before it hits the database.
Bcrypt.js: For hashing our passwords before it gets to the database.
Passport: authentication middleware for Node. js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application.
- Passsport-local: To handle email and passwords.
And of course we are importing ../models/User.js
. Which we will explain as we move on.
Models
Inside ./models/User.js
, let's have this code where we define our schema with the help of mogoose
schema
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name:{
type: String,
required: true,
},
email:{
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now()
}
});
const User = mongoose.model('users', UserSchema);
export default User;
Middleware
Now inside ./midleware/auth.js
, we will have a code to make sure it the user is not logged in, he'll be redirected login
page.
// ./midleware/auth.js
const auth = (req, res, next) => {
if (req.isAuthenticated()){
return next();
}
req.flash('error_msg', 'Please login');
res.redirect('/users/login');
}
export default auth;
public
This directory that will serve our static files.
Inside ./public/
we'll have two directories css
and js
where we can add bootstrap.css
and bootstrap.js
files respectively .
Alternatively, bootstrap cdn can be found here
Views
The views
directory is where our html files can be found.
We'll begin by creating one directory
./views/layout
and five files ./views/welcome.ejs
, ./views/dashboard.ejs
./views/layout
is going to have two filese ./views/layout/header.ejs
and ./views/layout/footer.ejs
.
EJS is a simple templating language that lets you generate HTML markup with plain JavaScript.
// ./views/layout/header.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Nodejs Passport Authentication</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
</head>
<body style="background: #ccc;">
<nav class="navbar navbar-expand-lg navbar-dark bg-info">
<a class="navbar-brand" href="/">Passport Authentication</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse text-center" id="navbarNav">
<ul class="navbar-nav">
<% if (user == null){ %>
<li class="nav-item">
<a class="nav-link" href="/users/login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/users/register">Register</a>
</li>
<% } %>
<li class="nav-item">
<a class="nav-link" href="/dashboard"><b>Dashboard</b></a>
</li>
</ul>
</div>
</nav>
<div class="container">
<div class="row justify-content-center">
<div class="col col-md-4">
// ./views/layout/footer.ejs
</div>
</div>
</div>
<script src="/js/jquery-3.3.1.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script>
</script>
</body>
</html>
// ./views/welcome.ejs
<%- include('layouts/header') -%>
<h1>Welcome to passport authentication</h1>
<%- include('layouts/footer') -%>
// ./views/dashboard.ejs
<%- include('layouts/header') -%>
<h1>Dashboard</h1>
<h3>
Welcome
<span class="text-success font-weight-bold"><%= name %></span>
</h3>
<a href="/users/logout">Logout</a>
<%- include('layouts/footer') -%>
// ./views/login.ejs
<%- include('layouts/header') -%>
<h1 class="text-info text-center mt-4">Login</h1>
<% if (success_msg != ''){ %>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<%= success_msg %>
</div>
<% } %>
<% if (error_msg != ''){ %>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<%= error_msg %>
</div>
<% } %>
<% if (error != ''){ %>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<%= error %>
</div>
<% } %>
<form action="/users/login" method="post">
<div class="form-group">
<input type="email" name="email" placeholder="Email" class="form-control">
</div>
<div class="form-group">
<input type="password" name="password" placeholder="Password" class="form-control">
</div>
<div class="form-group">
<input type="submit" placeholder="Confirm Password" value="Login" class="btn btn-primary btn-block">
</div>
</form>
<%- include('layouts/footer') -%>
// ./views/register.ejs
<%- include('layouts/header') -%>
<h1 class="text-info text-center mt-4">Register</h1>
<form action="/users/register" method="post">
<!-- Name -->
<div class="form-group">
<input type="text" name="name"
placeholder="Name"
class="form-control <%= typeof errname !== 'undefined'? 'is-invalid': '' %>"
autocomplete="off"
value="<%= typeof name !=='undefined' ? name : ''%>"
>
<!-- Name Errors -->
<% if (typeof errname !== 'undefined') { %>
<span class="invalid-feedback error-message" role="alert">
<strong><%= errname %></strong>
</span>
<% } %>
</div>
<!-- Email -->
<div class="form-group">
<input type="email" name="email"
placeholder="Email"
class="form-control <%= typeof erremail !== 'undefined'? 'is-invalid': '' %>"
value="<%= typeof email !== 'undefined' ? email : ''%>"
>
<!-- Email Errors -->
<% if (typeof erremail !== 'undefined'){ %>
<span class="invalid-feedback error-message" role="alert">
<strong><%= erremail %></strong>
</span>
<% } %>
</div>
<!-- Password -->
<div class="form-group">
<input type="password"
name="password"
placeholder="Password"
class="form-control <%= typeof errpassword !== 'undefined'? 'is-invalid': '' %>"
value="<%= typeof password !== 'undefined' ? password : ''%>"
>
<!-- Password Errors -->
<% if (typeof errpassword !== 'undefined'){ %>
<span class="invalid-feedback error-message" role="alert">
<strong><%= errpassword %></strong>
</span>
<% } %>
</div>
<!-- Confirm Password -->
<div class="form-group">
<input type="password"
name="password_confirmation"
placeholder="Confirm Password"
class="form-control"
>
</div>
<div class="form-group">
<input type="submit" placeholder="Confirm Password" value="Submit" class="btn btn-primary btn-block">
</div>
</form>
<%- include('layouts/footer') -%>
Now we can start our app by typing node index.js
from the terminal