More
    Web DevelopmentBackend DevelopmentUser Authentication & Session Management in Express & MongoDB

    User Authentication & Session Management in Express & MongoDB [Google OAuth2.0 Included]

    Many a time, you might want to restrict a user to access some page or functionality on a web app.

    That restriction can be applied if you have a proper user authentication system to register and login the users into their account.

    But, how does all that work behind the scenes?

    In this project, I am assuming that you have already initialized a project using npm.

    Create a new app.js file and install the following modules:

    • dotenv – to secure the API keys, which we will be using for connecting with Google OAuth2.0 authentication system.
    • express – for writing node server.
    • body-parser – to parse the user post request body data.
    • ejs – templating engine to generate some pages.
    • mongoose – for using mongodb easily.
    • express-session – to manage user sessions.
    • passport – local authentication system.
    • passport-local-mongoose – passport mongoose plugin.
    • passport-google-oauth20 – passport google oauth2.0 authentication strategy plugin.
    • mongoose-findorcreate – mongoose utility function to find and create an object or field in mongodb.

    Enter the following command in project integrated terminal, to install the above discussed dependencies:

    $ npm install dotenv express body-parser ejs mongoose express-session passport passport-local-mongoose passport-google-oauth20 mongoose-findorcreate

    We are gonna make a project where we will be having a page where a user can submit a secret (A piece of text, which a user want to tell the world, but being anonymous, i.e., hiding him/her identity to other users) after being logged in to their account.

    All the users can read the secrets of other users.

    For all of that, we will be creating all the required pages first:

    Create a folder named views and create the following files in it:

    login.ejs

    Add the following code in login.ejs

    <%- include('partials/header') %>
    
    <div class="container mt-5">
      <h1>Login</h1>
    
      <div class="row">
        <div class="col-sm-8">
          <div class="card">
            <div class="card-body">
    
              <!-- Makes POST request to /login route -->
              <form action="/login" method="POST">
                <div class="form-group">
                  <label for="email">Email</label>
                  <input type="email" class="form-control" name="username">
                </div>
                <div class="form-group">
                  <label for="password">Password</label>
                  <input type="password" class="form-control" name="password">
                </div>
                <button type="submit" class="btn btn-dark">Login</button>
              </form>
    
            </div>
          </div>
        </div>
    
        <div class="col-sm-4">
          <div class="card">
            <div class="card-body">
              <a class="btn btn-block" href="/auth/google" role="button">
                <i class="fab fa-google"></i>
                Sign In with Google
              </a>
            </div>
          </div>
        </div>
    
      </div>
    </div>
    
    <%- include('partials/footer') %>
    

    register.ejs

    Add the following code in register.ejs

    <%- include('partials/header') %>
    <div class="container mt-5">
      <h1>Register</h1>
    
      <div class="row">
        <div class="col-sm-8">
          <div class="card">
            <div class="card-body">
    
              <!-- Makes POST request to /register route -->
              <form action="/register" method="POST">
                <div class="form-group">
                  <label for="email">Email</label>
                  <input type="email" class="form-control" name="username">
                </div>
                <div class="form-group">
                  <label for="password">Password</label>
                  <input type="password" class="form-control" name="password">
                </div>
                <button type="submit" class="btn btn-dark">Register</button>
              </form>
    
            </div>
          </div>
        </div>
    
        <div class="col-sm-4">
          <div class="card social-block">
            <div class="card-body">
              <a class="btn btn-block" href="/auth/google" role="button">
                <i class="fab fa-google"></i>
                Sign Up with Google
              </a>
            </div>
          </div>
        </div>
    
      </div>
    </div>
    
    <%- include('partials/header') %>
    

    home.ejs

    Add the following code in home.ejs

    <%- include('partials/header') %>
    
    
    <div class="jumbotron centered">
      <div class="container">
        <i class="fas fa-key fa-6x"></i>
        <h1 class="display-3">Secrets</h1>
        <p class="lead">Don't keep your secrets, share them anonymously!</p>
        <hr>
        <a class="btn btn-light btn-lg" href="/register" role="button">Register</a>
        <a class="btn btn-dark btn-lg" href="/login" role="button">Login</a>
    
      </div>
    </div>
    
    <%- include('partials/footer') %>

    secrets.ejs

    Add the following code to secrets.ejs file

    <%- include('partials/header') %>
    
    <div class="jumbotron text-center">
      <div class="container">
        <i class="fas fa-key fa-6x"></i>
        <h1 class="display-3">You've Discovered The Secret Page!</h1>
    
        <% usersWithSecrets.forEach(function(user){ %>
          <p class="secret-text"><%= user.secret %></p>
        <% }); %>
    
        
        <hr>
        <a class="btn btn-light btn-lg" href="/logout" role="button">Log Out</a>
        <a class="btn btn-dark btn-lg" href="/submit" role="button">Submit a Secret</a>
      </div>
    </div>
    
    <%- include('partials/footer') %>
    

    submit.ejs

    Add the following code to submit.ejs

    <%- include('partials/header') %>
    
    <div class="container">
      <div class="jumbotron centered">
        <i class="fas fa-key fa-6x"></i>
        <h1 class="display-3">Secrets</h1>
        <p class="secret-text">Don't keep your secrets, share them anonymously!</p>
    
        <form action="/submit" method="POST">
    
          <div class="form-group">
            <input type="text" class="form-control text-center" name="secret" placeholder="What's your secret?">
          </div>
          <button type="submit" class="btn btn-dark">Submit</button>
        </form>
    
    
      </div>
    </div>
    <%- include('partials/footer') %>
    

    Now, create a folder named public in the root directory of the project.

    Inside the public folder, create another folder named css.

    Inside the css folder, create a file named styles.css

    Add the following code to styles.css file

    
    body {
      background-color: #E8ECEF;
    }
    
    .centered {
        padding-top: 200px;
        text-align: center;
    }
    
    .secret-text {
      text-align: center;
      font-size: 2rem;
      color: #fff;
      background-color: #000;
    }
    

    Create a file named app.js in the root directory of the project.

    Add the following code into your app.js file.

    This will import all the required dependencies into the project and will create an app listener on port 3000.

    require("dotenv").config();
    const express = require("express");
    const bodyParser = require("body-parser");
    const ejs = require("ejs");
    const mongoose = require('mongoose');
    const session = require("express-session");
    const passport = require("passport");
    const passportLocalMongoose = require("passport-local-mongoose");
    const GoogleStrategy = require('passport-google-oauth20').Strategy;
    const findOrCreate = require("mongoose-findorcreate");
    
    const app = express();
    app.use(express.static("public"));
    app.set('view engine', 'ejs');
    app.use(bodyParser.urlencoded({
        extended: true
    }));
    
    //code block 1
    
    app.listen(3000, function () {
        console.log("Server started on port 3000.");
    });

    At the //code block 1 area, add the following code,

    This will make the app to use the session plugin will make use of cookies to store the logged user data.

    app.use(session({
        secret: "Dummy Secret",
        resave: false,
        saveUninitialized: false,
    }));
    
    app.use(passport.initialize());
    app.use(passport.session());
    
    //code block 2

    At the //code block 2 area, add the following code,

    This will connect the app to the mongodb server (We are using the local database hosted using the mongodb).

    We are creating a mongoose schema to define the user object which will be stored in the database.

    mongoose.connect("mongodb://127.0.0.1:27017/usersDB");
    
    const userSchema = new mongoose.Schema({
        email: String,
        password: String,
        googleId: String,
        secret: String,
    });
    
    userSchema.plugin(passportLocalMongoose);
    userSchema.plugin(findOrCreate);
    
    const User = new mongoose.model("User", userSchema);
    
    passport.use(User.createStrategy());
    
    passport.serializeUser(function (user, done) {
        done(null, user.id);
    });
    
    passport.deserializeUser(function (id, done) {
        User.findById(id, function (err, user) {
            done(err, user);
        });
    });
    
    //code block 3

    Create a new file in the root directory of your project named .env and add the google oauth2.0 client id and secret over there.

    To get the client id and secret, head over to this blog post.

    Now, in the .env file, add the following code,

    SECRET=dummysecret
    CLIENT_ID=client id
    CLIENT_SECRET=client secret

    In the //code block 3 area, add the following code,

    Please note that you need to give it some time to understand what’s happening here, and which callback URL and other parameters are used.

    passport.use(new GoogleStrategy({
        clientID: process.env.CLIENT_ID,
        clientSecret: process.env.CLIENT_SECRET,
        callbackURL: "http://localhost:3000/auth/google/secrets",
        userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo',
        passReqToCallback: true
    },
        function (request, accessToken, refreshToken, profile, done) {
            User.findOrCreate({ googleId: profile.id }, function (err, user) {
                return done(err, user);
            });
        }
    ));
    
    //code block 4

    At the //code block 4 area, add the following code,

    app.get("/", function (req, res) {
        res.render("home");
    });
    
    app.get("/login", function (req, res) {
        res.render("login");
    });
    
    app.get("/register", function (req, res) {
        res.render("register");
    });
    
    app.get("/secrets", function (req, res) {
        User.find({secret: {$ne: null}}, function (err, foundUsers) { 
            if(err){
                console.log(err);
            }
            else{
                if(foundUsers){
                    res.render("secrets", {usersWithSecrets: foundUsers});
                }
            }
         });
    });
    
    app.get("/logout", function (req, res) {
        req.logout(function (err) {
            if (err) {
                console.log(err);
            }
            else {
                res.redirect("/");
            }
        });
    
    });
    
    app.get("/auth/google", passport.authenticate("google", { scope: ["email", "profile"] }));
    
    app.get('/auth/google/secrets',
        passport.authenticate('google', { failureRedirect: '/login' }),
        function (req, res) {
            // Successful authentication, redirect home.
            res.redirect("/secrets");
        });
    
    app.get("/submit", function (req, res) {
        if (req.isAuthenticated()) {
            res.render("submit");
        }
        else {
            res.redirect("/login");
        }
    });
    
    app.post("/register", function (req, res) {
    
        User.register({ username: req.body.username }, req.body.password, function (err, user) {
            if (err) {
                console.log(err);
                res.redirect("/register");
            }
            else {
                passport.authenticate("local")(req, res, function () {
                    res.redirect("/secrets");
                });
            }
        });
    
    });
    
    app.post("/login", function (req, res) {
    
        const user = new User({
            username: req.body.username,
            password: req.body.password,
        });
    
        req.login(user, function (err) {
            if (err) {
                console.log(err);
            }
            else {
                passport.authenticate("local")(req, res, function () {
                    res.redirect("/secrets");
                });
            }
        });
    
    });
    
    app.post("/submit", function (req, res) {
        const submittedSecret = req.body.secret;
        User.findById(req.user.id, function (err, foundUser) {
            if (err) {
                console.log(err);
            }
            else {
                if (foundUser) {
                    foundUser.secret = submittedSecret;
                    foundUser.save(function () {
                        res.redirect("/secrets");
                    });
                }
            }
        });
    });

    Your app is ready to be launched.

    Give it a go by starting the mongodb server by typing the command,

    $ mongod

    Open a new terminal window, and enter the following command (Don’t close the previous terminal window, where you started the mongod server)

    $ node app.js

    Head over to 127.0.0.1:3000 into your browser and check if the app’s working smoothly.

    Checkout this project on GitHub.


    Well, there’s an alternate way to do all these things, and bypassing the mongodb database, and session management using the Auth0. Check out: Authentication Strategy for User Centric Applications for that.

    Sponsored

    LEAVE A REPLY

    Please enter your comment!
    Please enter your name here

    Subscribe Today

    GET EXCLUSIVE FULL ACCESS TO PREMIUM CONTENT

    Get unlimited access to our EXCLUSIVE Content and our archive of subscriber stories.

    Exclusive content

    Latest article

    More article