Skip to content

web-authentication-documentation with [learnwithfair, Learn with fair, Rahatul Rabbi, Md Rahatul Rabbi ,rahatulrabbi]. In this repo I describes about Database Matching, Database Encryption Hashing, Hashing + Salting Password, Cookies and Session with Passport, Google OAuth with passport session based, Passport-jwt (token based)

Notifications You must be signed in to change notification settings

learnwithfair/web-authentication-documentation

Repository files navigation

WEB-AUTHENTICATION-DOCUMENTATION

Thanks for visiting my GitHub account!

Web Authentication is a web standard published by the World Wide Web Consortium. WebAuthn is a core component of the FIDO2 Project under the guidance of the FIDO Alliance see-more

Source Code (Download)

Click Here

Authentication Schema

Schema
schema

Table of Contents

  1. Database Matching
  2. Database Encryption
  3. Hashing Password
  4. Hashing + Salting Password
  5. Cookies & Session with Passport
  6. Google OAuth with passport session based
  7. Passport-jwt (token based)
  8. Follow Me

Level 1: Database matching

  • save(), find({property: value})
  • if hacker can access our database then our data is too much human readable
  • password checker online

Level 2: Database Encryption

  • read mongoose encryption documentation: https://www.npmjs.com/package/mongoose-encryption

  • install mongoose encryption npm install mongoose-encryption

  • create new mongoose Schema

    const mongoose = require("mongoose");
    const encrypt = require("mongoose-encryption");
    
    const userSchema = new mongoose.Schema({
      name: String,
      age: Number,
      // whatever else
    });
  • create an encryption key inside .env file

    ENCRYPTION_KEY = thisismyencryptionkey;
  • set encryption key with our schema

    const encrypt = require("mongoose-encryption");
    
    const encKey = process.env.ENCRYPTION_KEY;
    // encrypt age regardless of any other options. name and _id will be left unencrypted
    userSchema.plugin(encrypt, {
      secret: encKey,
      encryptedFields: ["age"],
    });
    
    User = mongoose.model("User", userSchema);

Level 3: Hashing password

  • no cncryption key; we will use hashing algorithm

  • hackers can not convert to plain text as no encryption key is available

  • md5 package: https://www.npmjs.com/package/md5

  • install md5 npm package: npm install md5

  • usage

    var md5 = require("md5");
    console.log(md5("message"));
    // 78e731027d8fd50ed642340b7c9a63b3
    
    // hash password when create it
    const newUser = new User({
      email: req.body.username,
      password: md5(req.body.password),
    });
    
    app.post("/login", async (req, res) => {
      try {
        const email = req.body.email;
        const password = md5(req.body.password);
        const user = await User.findOne({ email: email });
        if (user && user.password === password) {
          res.status(200).json({ status: "valid user" });
        } else {
          res.status(404).json({ status: "Not valid user" });
        }
      } catch (error) {
        res.status(500).json(error.message);
      }
    });

Level 4: Hashing + salting password

  • we can hash the password with some random number(salting)

  • install bcrypt npm package npm install bcrypt

  • usage

    const bcrypt = require("bcrypt");
    const saltRounds = 10;
    
    app.post("/register", async (req, res) => {
      try {
        bcrypt.hash(req.body.password, saltRounds, async function (err, hash) {
          const newUser = new User({
            email: req.body.email,
            password: hash,
          });
          await newUser.save();
          res.status(201).json(newUser);
        });
      } catch (error) {
        res.status(500).json(error.message);
      }
    });
    
    app.post("/login", async (req, res) => {
      try {
        const email = req.body.email;
        const password = req.body.password;
        const user = await User.findOne({ email: email });
        if (user) {
          bcrypt.compare(password, user.password, function (err, result) {
            if (result === true) {
              res.status(200).json({ status: "valid user" });
            }
          });
        } else {
          res.status(404).json({ status: "Not valid user" });
        }
      } catch (error) {
        res.status(500).json(error.message);
      }
    });

Level 5: Cookies & Session with passport

  • passport local strategy

  • npm install passport passport-local passport-local-mongoose express-session

  • my computer browser -> browse aliexpress (GET Request) -> to aliexpress server -> response the website -> add some items to the cart (post request to the server) -> aliexpress server will response and tell the browser to create a file in my computer for storing my selection -> so when next time we make a get request to the server we send the cookie with the get request -> server will return the cart again

  • cookie is a text file created by server on a user's device when we visit a website

  • that stores limited information such as login credentials - username, password; user preferences, cart contents from a web browser session

  • saving users behaviour

  • read more about cookies - https://www.trendmicro.com/vinfo/us/security/definition/cookies

  • types of cookies -> session cookie, presistent cookie, supercookie

  • login -> save user credentials as cookie for next time authentication -> log out and the session is destroyed

  • salt and hash is automatically generated by passport-local-mongoose

  • express session package create the cookie

  1. passport js framework has 2 separeate libraries
  • Passport JS Library (main) - maintain session information for user authentication
  • strategy library - methodology for authenticate an user - passport-local, passport-facebook, passport-oauth2 etc.
  1. Login process handled by 2 steps: i) session management (Passport.js), ii) authentication (strategy) npm install passport-local npm install passport-facebook

  2. for managing session Passport.js library takes help from express-session library npm install passport express-session

  3. source code

  • bootstrap the project

    • installing & requiring packages npm install express nodemon dotenv mongoose ejs cors

    • creating server

      //app.js
      const express = require("express");
      const cors = require("cors");
      const ejs = require("ejs");
      
      const app = express();
      
      app.set("view engine", "ejs");
      app.use(cors());
      app.use(express.urlencoded({ extended: true }));
      app.use(express.json());
      
      module.exports = app;
      
      //index.js
      const app = require("./app");
      const PORT = 4000;
      app.listen(PORT, () => {
        console.log(`app is running at http://localhost:${PORT}`);
      });
    • creating routes including try,catch

      // base url
      app.get("/", (req, res) => {
        res.render("index");
      });
      
      // register routes
      app.get("/register", (req, res) => {
        res.render("register");
      });
      
      app.post("/register", (req, res) => {
        try {
          res.status(201).send("user is registered");
        } catch (error) {
          req.status(500).send(error.message);
        }
      });
      
      // login routes
      app.get("/login", (req, res) => {
        res.render("login");
      });
      
      app.post("/login", (req, res) => {
        try {
          res.status(201).send("user is logged in");
        } catch (error) {
          req.status(500).send(error.message);
        }
      });
      
      // logout routes
      app.get("/logout", (req, res) => {
        res.redirect("/");
      });
      
      // profile protected routes
      app.get("/profile", (req, res) => {
        res.render("profile");
      });
    • creating ejs files

      • create layout

        <!-- views/layout/header.ejs -->
        <!DOCTYPE html>
        <html lang="en">
          <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta
              name="viewport"
              content="width=device-width, initial-scale=1.0"
            />
            <title>Document</title>
          </head>
          <body>
            <header>
              <nav>
                <a href="/">Home</a>
                <a href="/register">Register</a>
                <a href="/login">Login</a>
                <a href="/profile">Profile</a>
                <a href="/logout">Logout</a>
              </nav>
            </header>
          </body>
        </html>
        
        <!-- views/layout/footer.ejs -->
            <footer>
              <p>copyright by Rahatul Rabbi</p>
            </footer>
          </body>
        </html>
      • create pages

        <!-- views/index.ejs -->
        <%-include("layout/header")%>
        <main>
          <h1>Home Page</h1>
        </main>
        <%-include("layout/footer")%>
        
        <!-- views/register.ejs -->
        <%-include("layout/header")%>
        <main>
          <h1>Register Page</h1>
          <form action="/register" method="post">
            <div>
              <label for="username">username: </label>
              <input type="text" id="username" name="username" />
            </div>
            <br />
            <div>
              <label for="password">password: </label>
              <input type="password" id="password" name="password" />
            </div>
            <br />
            <button type="submit">Register</button>
          </form>
        </main>
        <%-include("layout/footer")%>
        
        <!-- views/login.ejs -->
        <%-include("layout/header")%>
        <main>
          <h1>Login Page</h1>
          <form action="/register" method="post">
            <div>
              <label for="username">username: </label>
              <input type="text" id="username" name="username" />
            </div>
            <br />
            <div>
              <label for="password">password: </label>
              <input type="password" id="password" name="password" />
            </div>
            <br />
            <button type="submit">Login</button>
          </form>
        </main>
        <%-include("layout/footer")%>
        
        <!-- views/profile.ejs -->
        <%-include("layout/header")%>
        <main>
          <h1>Profile Page</h1>
        </main>
        <%-include("layout/footer")%>
  • create model and connect to mongodb

    // models/user.model.js
    const mongoose = require("mongoose");
    const userSchema = mongoose.Schema({
      username: {
        type: String,
        require: true,
        unique: true,
      },
      password: {
        type: String,
        require: true,
      },
    });
    const User = mongoose.model("User", userSchema);
    module.exports = User;
    
    // config/database.js
    const mongoose = require("mongoose");
    mongoose
      .connect("mongodb://localhost:27017/passportDB")
      .then(() => {
        console.log("db is connected");
      })
      .catch((error) => {
        console.log(error.message);
      });
    
    // app.js
    require("./config/database");
  • register an user

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

app.post("/register", async (req, res) => {
  try {
    const user = await User.findOne({ username: req.body.username });
    if (user) return res.status(400).send("User already exist");
    const newUser = new User(req.body);
    await newUser.save();
    res.status(201).send(newUser);
  } catch (error) {
    req.status(500).send(error.message);
  }
});
  • encrypt the user password using bcrypt hashing+salting npm install bcrypt
//app.js
const bcrypt = require("bcrypt");
const saltRounds = 10;

app.post("/register", async (req, res) => {
  try {
    const user = await User.findOne({ username: req.body.username });
    if (user) return res.status(400).send("User already exist");

    bcrypt.hash(req.body.password, saltRounds, async (err, hash) => {
      const newUser = new User({
        username: req.body.username,
        password: hash,
      });
      await newUser.save();
      res.redirect("/login");
    });
  } catch (error) {
    res.status(500).send(error.message);
  }
});
  • create and add session npm install passport express-session connect-mongo

    //Import the main Passport and Express-Session library
    const passport = require("passport");
    const session = require("express-session");
    // for storing session in different collection
    const MongoStore = require("connect-mongo");
    
    // setting middleware
    app.set("trust proxy", 1); // trust first proxy
    app.use(
      session({
        secret: "keyboard cat",
        resave: false,
        saveUninitialized: true,
        store: MongoStore.create({
          mongoUrl: "mongodb://localhost:27017/testPassportDB",
          collectionName: "sessions",
        }),
        // cookie: { secure: true },
        // cookie: { maxAge: 1000 * 60 * 60 * 24 },
      })
    );
    
    app.use(passport.initialize());
    // init passport on every route call.
    app.use(passport.session());
    // allow passport to use "express-session".
  • set passport-local configuration npm install passport-local

    // passport.js
    const User = require("../model/user.model");
    const passport = require("passport");
    const bcrypt = require("bcrypt");
    const LocalStrategy = require("passport-local").Strategy;
    passport.use(
      new LocalStrategy(async function (username, password, done) {
        try {
          const user = await User.findOne({ username: username });
          // wrong username
          if (!user) {
            return done(null, false, { message: "Incorrect Username" });
          }
    
          // wrong password
          if (!bcrypt.compare(password, user.password)) {
            return done(null, false, { message: "Incorrect Password" });
          }
    
          // if user found
          return done(null, user);
        } catch (error) {
          return done(error);
        }
      })
    );
    
    // create session id
    // whenever we login it creares user id inside session
    passport.serializeUser((user, done) => {
      done(null, user.id);
    });
    
    // find session info using session id
    passport.deserializeUser(async (id, done) => {
      try {
        const user = await User.findById(id);
        done(null, user);
      } catch (error) {
        done(error, false);
      }
    });
  • authenticate user using passport-local

// app.js
// login using passport-local strategy
const checkLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) {
    return res.redirect("/profile");
  }
  next();
};

// login routes
app.get("/login", checkLoggedIn, (req, res) => {
  try {
    res.render("login");
  } catch (error) {
    req.status(500).send(error.message);
  }
});

// register routes
app.get("/register", checkLoggedIn, (req, res) => {
  res.render("register");
});
  • check user is already logged in or not

    const checkAuthenticated = (req, res, next) => {
      if (req.isAuthenticated()) {
        return next();
      }
      res.redirect("/login");
    };
    
    // profile protected routes
    app.get("/profile", checkAuthenticated, (req, res) => {
      res.render("profile");
    });
  • logout route setup

    // logout routes
    app.get("/logout", (req, res) => {
      try {
        req.logout((err) => {
          if (err) {
            return next(err);
          }
          res.redirect("/");
        });
      } catch (error) {
        req.status(500).send(error.message);
      }
    });
  • finally the entire app.js

    const express = require("express");
    const cors = require("cors");
    const ejs = require("ejs");
    const bcrypt = require("bcrypt");
    const saltRounds = 10;
    const passport = require("passport");
    const session = require("express-session");
    // for storing session in different collection
    const MongoStore = require("connect-mongo");
    
    require("./config/database");
    require("./config/passport");
    
    const User = require("./model/user.model");
    
    const app = express();
    
    app.set("view engine", "ejs");
    app.use(cors());
    app.use(express.urlencoded({ extended: true }));
    app.use(express.json());
    
    // setting middleware
    app.set("trust proxy", 1); // trust first proxy
    app.use(
      session({
        secret: "keyboard cat",
        resave: false,
        saveUninitialized: true,
        store: MongoStore.create({
          mongoUrl: "mongodb://localhost:27017/passportDB",
          collectionName: "sessions",
        }),
        // cookie: { maxAge: 1000 * 60 * 60 * 24 },
      })
    );
    
    app.use(passport.initialize());
    // init passport on every route call.
    app.use(passport.session());
    // allow passport to use "express-session".
    
    const checkLoggedIn = (req, res, next) => {
      if (req.isAuthenticated()) {
        return res.redirect("/profile");
      }
      next();
    };
    
    // base url
    app.get("/", (req, res) => {
      res.render("index");
    });
    
    // register routes
    app.get("/register", checkLoggedIn, (req, res) => {
      res.render("register");
    });
    
    app.post("/register", async (req, res) => {
      try {
        const user = await User.findOne({ username: req.body.username });
        if (user) return res.status(400).send("User already exist");
    
        bcrypt.hash(req.body.password, saltRounds, async (err, hash) => {
          const newUser = new User({
            username: req.body.username,
            password: hash,
          });
          await newUser.save();
          res.redirect("/login");
        });
      } catch (error) {
        res.status(500).send(error.message);
      }
    });
    
    // login routes
    app.get("/login", checkLoggedIn, (req, res) => {
      res.render("login");
    });
    
    app.post(
      "/login",
      passport.authenticate("local", { successRedirect: "/profile" })
    );
    
    // logout routes
    app.get("/logout", (req, res) => {
      try {
        req.logout((err) => {
          if (err) {
            return next(err);
          }
          res.redirect("/");
        });
      } catch (error) {
        req.status(500).send(error.message);
      }
    });
    
    const checkAuthenticated = (req, res, next) => {
      if (req.isAuthenticated()) {
        return next();
      }
      res.redirect("/login");
    };
    
    // profile protected routes
    app.get("/profile", checkAuthenticated, (req, res) => {
      res.render("profile");
    });
    
    module.exports = app;

Level 6: Google OAuth with passport session based

  • setup database name in db and also for session

  • change in schema

    const mongoose = require("mongoose");
    const userSchema = mongoose.Schema({
      username: {
        type: String,
        require: true,
        unique: true,
      },
      googleId: {
        type: String,
        require: true,
      },
    });
    
    const User = mongoose.model("User", userSchema);
    module.exports = User;
  • inside login ejs add <a href="/auth/google">Login with Google</a>

  • create dynamic user name in profile page Welcome <%=username%>

  • configure strategy

    • we need client id, client secret
    // passport.js
    require("dotenv").config();
    const User = require("../models/user.model");
    const passport = require("passport");
    
    const GoogleStrategy = require("passport-google-oauth20").Strategy;
    
    passport.use(
      new GoogleStrategy(
        {
          clientID: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
          callbackURL: "http://localhost:5000/auth/google/callback",
        },
        function (accessToken, refreshToken, profile, cb) {
          User.findOne({ googleId: profile.id }, (err, user) => {
            if (err) return cb(err, null);
    
            // not a user; so create a new user with new google id
            if (!user) {
              let newUser = new User({
                googleId: profile.id,
                username: profile.displayName,
              });
              newUser.save();
              return cb(null, newUser);
            } else {
              // if we find an user just return return user
              return cb(null, user);
            }
          });
        }
      )
    );
    
    // create session id
    // whenever we login it creares user id inside session
    passport.serializeUser((user, done) => {
      done(null, user.id);
    });
    
    // find session info using session id
    passport.deserializeUser(async (id, done) => {
      try {
        const user = await User.findById(id);
        done(null, user);
      } catch (error) {
        done(error, false);
      }
    });
    
    // app.js
    const express = require("express");
    const cors = require("cors");
    const ejs = require("ejs");
    const app = express();
    require("./config/database");
    require("dotenv").config();
    require("./config/passport");
    const User = require("./models/user.model");
    
    const passport = require("passport");
    const session = require("express-session");
    const MongoStore = require("connect-mongo");
    
    app.set("view engine", "ejs");
    app.use(cors());
    app.use(express.urlencoded({ extended: true }));
    app.use(express.json());
    
    app.set("trust proxy", 1); // trust first proxy
    app.use(
      session({
        secret: "keyboard cat",
        resave: false,
        saveUninitialized: true,
        store: MongoStore.create({
          mongoUrl: process.env.MONGO_URL,
          collectionName: "sessions",
        }),
        // cookie: { secure: true },
      })
    );
    
    app.use(passport.initialize());
    app.use(passport.session());
    
    // base url
    app.get("/", (req, res) => {
      res.render("index");
    });
    
    const checkLoggedIn = (req, res, next) => {
      if (req.isAuthenticated()) {
        return res.redirect("/profile");
      }
      next();
    };
    
    // login : get
    app.get("/login", checkLoggedIn, (req, res) => {
      res.render("login");
    });
    
    app.get(
      "/auth/google",
      passport.authenticate("google", { scope: ["profile"] })
    );
    
    app.get(
      "/auth/google/callback",
      passport.authenticate("google", {
        failureRedirect: "/login",
        successRedirect: "/profile",
      }),
      function (req, res) {
        // Successful authentication, redirect home.
        res.redirect("/");
      }
    );
    
    const checkAuthenticated = (req, res, next) => {
      if (req.isAuthenticated()) {
        return next();
      }
      res.redirect("/login");
    };
    
    // profile protected route
    app.get("/profile", checkAuthenticated, (req, res) => {
      res.render("profile", { username: req.user.username });
    });
    
    // logout route
    app.get("/logout", (req, res) => {
      try {
        req.logout((err) => {
          if (err) {
            return next(err);
          }
          res.redirect("/");
        });
      } catch (error) {
        res.status(500).send(error.message);
      }
    });
    
    module.exports = app;

Level 7: passport-jwt (token based)

  • how token based works
    • user register using username, password to the server -> server creates a token for the user -> so next time when user make any request server give access by validating the given token
  • folder and file structure
    • server
      • models
        • user.model.js
      • config
      • app.js
      • index.js
      • .env
      • .gitignore
  • initialize npm and install package npm init -y && npm install express nodemon cors dotenv bcrypt mongoose
  • test

Follow Me

Facebook, Youtube, Instagram

About

web-authentication-documentation with [learnwithfair, Learn with fair, Rahatul Rabbi, Md Rahatul Rabbi ,rahatulrabbi]. In this repo I describes about Database Matching, Database Encryption Hashing, Hashing + Salting Password, Cookies and Session with Passport, Google OAuth with passport session based, Passport-jwt (token based)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published