Local Authentication Using Node.js

Authentication means validating your credentials like username and password to confirm your identity. The system or application confirms that you are the true user for accessing the private or confidential data. Authorization is a process of verifying that you have access to do something.

In other words, Authorization is about who somebody is and what they are allowed to do. In this project, we shall work on authentication for now.

NPM modules for this project

  • bcrypt
  • body-parser
  • connect-flash
  • ejs
  • express
  • express-session
  • mongoose
  • morgan
  • nodemon
  • passport
  • passport-local

Passport

Passport is an authentication middleware for NodeJS. It is extremely flexible and modular. It can be easily dropped into any NodeJS web-based application. It has a set of strategies that support authentication using a username and password.

Why Passport

  • 140+ strategies for different types
  • Easily handle success and failure state
  • Single sign-on with OpenId and OAuth
  • Support persistent sessions
  • Dynamic scope and sessions
  • Lightweight
  • Easy to learn and use

Bcrypt

The first rule of the database is to never save your credentials in plain text. If in any case, the database is hacked, then the hacker can use that data very easily. So, by decrypting that data, it will be useless for a hacker.

Connect-flash

It's a special area of the session used for storing messages. Messages are written in flash and cleared after being displayed to the user. The flash is typically used in combination with redirects, ensuring that the message is available on the next page being rendered.

Body-parser

It is a middleware which is used to obtain information from the body of post request.

Morgan

It is optional because it is only used to handle the request logger for the request to the server from the user.

Mongoose

Mongoose is a package for Nodejs for accessing the MongoDB and performing CRUD operations very easily.

Nodemon

Restart the server again and again automatically on the saving file event so that the developer can develop the server more efficiently and save headaches from having to shift to the command prompt or terminal.

Folder Structure

|-- application
   | app.js
   | package.json
   | tree.txt
   +---app
   |    |    routes.js
   |    \---model
   |         user.js
   +---config
   |     database.js
   |     passport.js
   +---node_modules
   +---static
   |    +---css
   |    |    style.css
   |    +---js
   |    |    main.js
   |    \---vendors
   |         +---css
   |         |    bootstrap.min.css
   |         \---js
   |              bootstrap.min.js
   |              jquery.js
   \---views
        index.ejs
        login.ejs
        profile.ejs
        signup.ejs
        \---partial
             footer.ejs
             header.ejs
        \---global
             css.ejs
             js.ejs

/package.json

{
  "name": "oauthpractice",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Abhishek",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^1.0.3",
    "body-parser": "^1.18.2",
    "connect-flash": "^0.1.1",
    "cookie-parser": "^1.4.3",
    "ejs": "^2.5.7",
    "express": "^4.16.2",
    "express-session": "^1.15.6",
    "mongoose": "^5.0.7",
    "morgan": "^1.9.0",
    "nodemon": "^1.15.1",
    "passport": "^0.4.0",
    "passport-facebook": "^2.1.1",
    "passport-google": "^0.3.0",
    "passport-local": "^1.0.0"
  }
}

/app.js

const express = require("express");
const expressSession = require("express-session");
const cookieParser = require("cookie-parser");
const morgan = require("morgan");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const passport = require("passport");
const flash = require("connect-flash");

const dbconfig = require("./config/database");

mongoose.connect(dbconfig.url);

const app = express();

// For Frontend
app.set("view engine", "ejs");
app.use(bodyParser.urlencoded({ extended: false }));
app.use("/static", express.static("static"));

// For loggers
app.use(morgan('dev'));

// For Sessions
app.use(cookieParser());
app.use(expressSession({
    secret: "ThisIsOauthProject",
    saveUninitialized: true,
    resave: false
}));

app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
require("./config/passport")(passport);

const port = process.env.PORT || 4080;

require("./app/routes")(app, passport);

console.log("Server is running at -> " + port);
app.listen(port);

app/model/user.js

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");

const userSchema = mongoose.Schema({
    local: {
        username: String,
        password: String
    }
});

userSchema.methods.generateHash = function(password){
    return bcrypt.hashSync(password, bcrypt.genSaltSync(9));
};

userSchema.methods.validPassword = function(password){
    return bcrypt.compareSync(password, this.local.password);
}

var User = mongoose.model("userdetails", userSchema);
module.exports = User;

app/routes.js

const User = require("./model/user");

module.exports = function(app, passport){
    app.get("/", function(req, res){
        res.render("index.ejs");
    });

    app.get("/signup", function(req, res){
        res.render("signup.ejs", { message: req.flash("signupMsg") });
    });

    app.post("/signup", passport.authenticate("local-signup", {
        successRedirect: "/",
        failureRedirect: "/signup",
        failureFlash: true
    }));

    app.get("/login", function(req, res){
        res.render("login.ejs", { message: req.flash("loginMsg") });
    });

    app.post("/login", passport.authenticate("local-login", {
        successRedirect: "/profile",
        failureRedirect: "/login",
        failureFlash: true
    }));

    app.get("/profile", isLoggedIn, function(req, res){
        res.render("profile.ejs", { user : req.user });
    });

    app.get("/logout", function(req, res){
        req.logout();
        res.redirect("/");
    });
};

function isLoggedIn(req, res, next){
    if(req.isAuthenticated()) return next();
    res.redirect("/login");
}

/config/database.js

module.exports = {
    url: "mongodb://localhost:27017/oauthpractice"
};

/config/passport.js

const localStrategy = require("passport-local").Strategy;
const User = require("../app/model/user");

module.exports = function(passport){

    passport.serializeUser(function(user, done){
        done(null, user.id);
    });

    passport.deserializeUser(function(id, done){
        User.findById(id, function(err, user){
            done(err, user);
        });
    });

    passport.use("local-signup", new localStrategy(
        {   
            usernameField: "email",
            passwordField: "password",
            passReqToCallback: true
        }, function(req, email, password, done){
            process.nextTick(function(){
                User.findOne({"local.username": email }, function(err, user){
                    if (err) return done(err);
                    if (user) return done(null, false, req.flash("signupMsg", "Email already exist"));
                    else{
                        var newuser = new User();
                        newuser.local.username = email;
                        newuser.local.password = newuser.generateHash(password);
                        newuser.save(function(err){
                            if (err) throw err;
                            return done(null, newuser);
                        });
                    }   
                });
            });
        }
    ));

    passport.use("local-login", new localStrategy(
        {   
            usernameField: "email",
            passwordField: "password",
            passReqToCallback: true
        }, function(req, email, password, done){
            process.nextTick(function(){
                User.findOne({"local.username": email }, function(err, user){
                    if (err) return done(err);
                    if (!user) return done(null, false, req.flash("loginMsg", "User not found"));
                    if (!user.validPassword(password)) return done(null, false, req.flash("loginMsg", "Password invalid"));
                    return done(null, user);   
                });
            });
        }
    ));
};

/static/css/style.css

.block-center > div {
    float: none;
    margin: 0 auto;
}

body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    justify-content: space-between;
}

footer {
    text-align: center;
    background-color: #555;
    padding: 20px;
    color: white;
}

/views/partial/global/css.

<link rel="stylesheet" type="text/css" href="/static/vendors/css/bootstrap.min.css">  
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">  

/views/partial/global/js.ejs

<script src="/static/vendors/js/jquery.js"></script>  
<script src="/static/vendors/js/bootstrap.min.js"></script>  
<script src="/static/js/main.js"></script>  

/views/partial/header.ejs

<nav class="navbar navbar-default" role="navigation">  
    <!-- Brand and toggle get grouped for better mobile display -->  
    <div class="navbar-header">  
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">  
            <span class="sr-only">Toggle navigation</span>  
            <span class="icon-bar"></span>  
            <span class="icon-bar"></span>  
            <span class="icon-bar"></span>  
        </button>  
        <a class="navbar-brand" href="#">OauthProject</a>  
    </div>  
  
    <!-- Collect the nav links, forms, and other content for toggling -->  
    <div class="collapse navbar-collapse navbar-ex1-collapse">  
        <ul class="nav navbar-nav">  
            <li class="active"><a href="/">home</a></li>  
            <li class="active"><a href="/login">Login</a></li>  
            <li class="active"><a href="/signup">Local Signup</a></li>  
        </ul>  
    </div><!-- /.navbar-collapse -->  
</nav>  

/views/partial/footer.ejs

<footer>  
    <div>  
        <h5>Copyright@ footer</h5>  
    </div>  
</footer>  

/views/index.ejs

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Home</title>  
    <% include ./partial/header.ejs%>  
</head>  
<body>  
    <% include ./partial/global/css.ejs%>  
      
    <div class="container">  
          
        <div class="jumbotron">  
            <div class="container text-center">  
                <h1><span class="fa fa-lock"></span>Node Authentication</h1>  
                <p> Register or Signup with</p>  
                <p>  
                    <a href="/signup"><button type="button" class="btn btn-primary btn-sm">Local Register</button></a>  
                    <a href="/login"><button type="button" class="btn btn-primary btn-sm">Local Login</button></a>  
                </p>  
            </div>  
        </div>  
          
    </div>  
  
    <% include ./partial/footer.ejs%>  
    <% include ./partial/global/js.ejs%>  
</body>  
</html>  

/views/signup.ejs

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Signup</title>  
    <% include ./partial/header.ejs%>  
</head>  
<body>  
    <% include ./partial/global/css.ejs%>  
      
    <div class="container">  
        <div class="row">  
            <div class="col-xs-12 col-sm-7 col-md-7 col-lg-7">  
                <div class="jumbotron">  
                    <form action="/signup" method="POST" role="form">  
                        <legend>Signup</legend>  
                        <div class="form-group">  
                            <label for="email">Email</label>  
                            <input type="text" class="form-control" name="email" placeholder="Enter name">  
                        </div>  
                        <div class="form-group">  
                            <label for="pass">Password</label>  
                            <input type="password" class="form-control" name="password" placeholder="Enter name">  
                        </div>  
                        <button type="submit" class="btn btn-primary">Submit</button>  
                        <a href="/"><button type="button" class="btn btn-primary">Back</button></a>  
                        <% if( message.length > 0 ){ %>  
                            <br />  
                            <br />  
                            <div class="alert alert-info">  
                                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>  
                                <strong><%= message %></strong>  
                                <br> Please click on x button to close that bar  
                            </div>                      
                        <% } %>  
                    </form>  
                </div>  
            </div>  
        </div>  
    </div>  
      
    <% include ./partial/footer.ejs%>  
    <% include ./partial/global/js.ejs%>  
</body>  
</html>  

/views/login.ejs

<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1.0">  
    <meta http-equiv="X-UA-Compatible" content="ie=edge">  
    <title>Login</title>  
    <% include ./partial/header.ejs%>  
</head>  
<body>  
    <% include ./partial/global/css.ejs%>  
      
    <div class="container">  
        <div class="row">  
            <div class="col-xs-12 col-sm-7 col-md-7 col-lg-7">  
                <div class="jumbotron">  
                    <form action="/login" method="POST" role="form">  
                        <legend>Login</legend>  
                        <div class="form-group">  
                            <label for="email">Email</label>  
                            <input type="text" class="form-control" name="email" placeholder="Enter name">  
                        </div>  
                        <div class="form-group">  
                            <label for="pass">Password</label>  
                            <input type="password" class="form-control" name="password" placeholder="Enter name">  
                        </div>  
                        <button type="submit" class="btn btn-primary">Submit</button>  
                        <a href="/"><button type="button" class="btn btn-primary">Back</button></a>  
                        <% if( message.length > 0 ){ %>  
                            <br />  
                            <br />  
                            <div class="alert alert-info">  
                                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>  
                                <strong><%= message %></strong>  
                            </div>                      
                        <% } %>  
                    </form>  
                </div>  
            </div>  
        </div>  
    </div>  
  
    <% include ./partial/footer.ejs%>  
    <% include ./partial/global/js.ejs%>  
</body>  
</html>  

/views/profile.ejs

<!DOCTYPE html>    
<html lang="en">    
<head>    
    <meta charset="UTF-8">    
    <meta name="viewport" content="width=device-width, initial-scale=1.0">    
    <meta http-equiv="X-UA-Compatible" content="ie=edge">    
    <title>Signup</title>    
    <% include ./partial/header.ejs%>    
</head>    
<body>    
    <% include ./partial/global/css.ejs%>    
        
    <div class="container">    
        <div class="row">    
            <div class="col-md-12 col-sm-12">    
                <h2>Profile Page</h2>    
            </div>    
            <div class="col-md-6 col-sm-6">    
                <div class="well">    
                    <h3><span class="fa fa-user"></span> Local</h3>    
                    <h5>Username = <%= user.local.username %></h5>                    
                    <h5>Password = <%= user.local.password %></h5>                    
                    <h5>UserId = <%= user._id %></h5>                    
                    <a type="button" href="/logout" class="btn btn-danger">Logout</a>    
                </div>    
            </div>    
        </div>    
    </div>    
    
    <% include ./partial/footer.ejs%>    
    <% include ./partial/global/js.ejs%>    
</body>    
</html>


Similar Articles