Book Image

MongoDB, Express, Angular, and Node.js Fundamentals

By : Paul Oluyege
Book Image

MongoDB, Express, Angular, and Node.js Fundamentals

By: Paul Oluyege

Overview of this book

MongoDB, Express, Angular and Node.js Fundamentals is a practical guide to the tried-and-true production-ready MEAN stack, with tips and best practices. The book begins by demystifying the MEAN architecture. You’ll take a look at the features of the JavaScript libraries, technologies, and frameworks that make up a MEAN stack. With this book, you'll not only learn how to develop highly scalable, asynchronous, and event-driven APIs quickly with Express and Node.js, but you'll also be able put your full-stack skills to use by building two full-fledged MEAN applications from scratch. You’ll understand how to build a blogging application using the MEAN stack and get to grips with user authentication using MEAN. As you progress through the chapters, you’ll explore some old and new features of Angular, such as pipes, reactive forms, modules and optimizing apps, animations and unit testing, and much more. By the end of the book, you’ll get ready to take control of the MEAN stack and transform into a full-stack JavaScript developer, developing efficient web applications using Javascript technologies.
Table of Contents (9 chapters)
MongoDB, Express, Angular, and Node.js Fundamentals
Preface

Chapter 4: The MEAN Stack Security


Activity 11: Securing the RESTful API

File name: userController.js
  1. Create an admin user model by creating a file named userModel.js inside the api folder and input the following code:

    const mongoose = require("mongoose"), // loading modules 
    bcrypt = require('bcryptjs'),
    Schema = mongoose.Schema;
    
    const UserSchema = new Schema({ // Schema Instance
      fullName: {
        type: String,
        trim: true,
        required: true
      },
      email: {
        type:String,
        unique:true,
        lovercase:true,
        trim:true,
        required:true
      } ,
      hash_password: {
        type:String,
        required:true
      },
      createdOn: {
        type: Date,
        default: Date.now
      }
    });
    
    UserSchema.methods.comparePassword = function(password){  //password confirmation
      return bcrypt.compareSync(password, this.hash_password);
    }
    module.exports = mongoose.model("User", UserSchema); //user model
  2. Create an admin user controller by creating a file called userController.js inside the controllers/api folder input using the following code:

    const User = require("../models/userModel"); // Import userModel,
        jwt = require('jsonwebtoken'), // load jasonwebtoken module
        bcrypt = require('bcryptjs');  // load bcryptjs module for password hashing
    
    exports.register = (req, res) => { // exportable function to register new user
        let newUser = new User(req.body);
        newUser.hash_password = bcrypt.hashSync(req.body.password, 10);
        newUser.save((err, user) => {
            if (err) {
                res.status(500).send({ message: err });
            }
            user.hash_password = undefined;
            res.status(201).json(user);
        });
    };
    
    //[…]
    
    exports.loginRequired = (req, res, next) => {
        if (req.user) {
            res.json({ message: 'Authorized User!'});
            next();
          } else {
             res.status(401).json({ message: 'Unauthorized user!' });
          }
    };
  3. Update the route file (articleListRoutes.js) in the routes directory (server/api/controllers) with the following code:

    'use strict';
    module.exports = function(app) {
      var articleList = require('../controllers/articleListController');
      var userHandlers = require('../controllers/userController');
    
      // articleList Routes
      app
      .route("/articles")
      .get(articleList.listAllArticles)
      .post(userHandlers.loginRequired, articleList.createNewArticle);
    
     app
      .route("/article/:articleid")
      .get(articleList.readArticle)
      .put(articleList.updateArticle)
      .delete(articleList.deleteArticle);
    
      app
      .route("/articles/by/:tag")
      .get(articleList.listTagArticles);
    
      app
      .route("/auth/register")
      .post(userHandlers.register);
    
      app
      .route("/auth/sign_in")
      .post(userHandlers.signIn);
    
    };
  4. Update the server.js (inside the server folder) file with the following code:

    'use strict'
    const express = require("express");
    const bodyParser = require("body-parser");
    
    // db instance connection
    require("./config/db");
    var User = require('./api/models/userModel'),
        jsonwebtoken = require("jsonwebtoken");
    
    const app = express();
    
    const port = process.env.PORT || 3000;
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());
    
    //CORS (Cross-Origin Resource Sharing) headers to support Cross-site HTTP requests
    app.use(function(req, res, next) {
        res.header('Access-Control-Allow-Origin', '*');
        res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS');
        res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With');
        // allow preflight
        if (req.method === 'OPTIONS') {
            res.send(200);
        } else {
            next();
        }
    });
    
    app.use((req, res, next) => { // Verify JWT for user authorization
        if (req.headers && req.headers.authorization && req.headers.authorization.split(' ')[0] === 'JWT') {
            jsonwebtoken.verify(req.headers.authorization.split(' ')[1],  'RESTfulAPIs', (err, decode) => {
                    if (err) req.user = undefined;
                    req.user = decode;
                    next();
                });
        } else {
            req.user = undefined;
            next();
        }
    });
    
    
    // API ENDPOINTS
    var routes = require('./api/routes/articleListRoutes'); //importing route
    routes(app); 
    
    // LISTENING
    app.listen(port, () => {
      console.log('Server running at http://localhost:${port}');
    });
  5. Run the server using node server on the CLI and open Postman for testing.

  6. Test for registration by typing in localhost:3000/auth/register on the address bar. You will obtain the following output:

    Figure 4.15: Screenshot for registration

  7. Attempt the login required path and post request on localhost:3000/articles. You will obtain the following output:

    Figure 4.16: Screenshot for posting articles

  8. Attempt user login by typing in localhost:3000/auth/sign_in on the address bar. You will obtain the following output:

    Figure 4.17: Screenshot for sign in

  9. Set the authentication key on the header and input the value in JWT token format, as shown here:

    Figure 4.18: Screenshot for setting the authentication key

  10. Attempt the login required path and post request on localhost:3000/articles. You will obtain the following output:

    Figure 4.19: Screenshot for articles

Thus, from the preceding outputs, it can be clearly observed that we have successfully secured the RESTful API we developed in the previous exercise. We also managed to provide admin access for creating, updating, and deleting data.

Activity 12: Creating a Login Page to Allow Authentication with Twitter Using Passport Strategies

File name: passport.js
  1. Create a package.json file and install express, body-parser, mongoose, passport-twitter, and passport by running the following code:

    npm init
    npm install express body-parser mongoose passport-twitter passport express-session -save
  2. Create a server.js (using touch server.js on the CLI) file and import express and body-parser using the following code:

    const express = require("express");
    const bodyParser = require("body-parser");
    const session = require('express-session');
  3. Create an Express application, assign a port number, and use the body-parser middleware on the Express application using the following code:

    const app = express();
    const port = process.env.PORT || 4000;
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());
  4. Create a config folder using the following code:

    mkdir config
  5. Create a database by first creating a file named db.js (using touch db.js on the CLI) in the config folder directory and input the following code:

    const mongoose = require("mongoose");
    
    var uri = "mongodb+srv://username:[email protected]/test?retryWrites=true";
    
      const options = {
        reconnectTries: Number.MAX_VALUE,
        poolSize: 10
      };
    // Connect to the database using the following code  
    mongoose.connect(uri, options).then(
        () => {
          console.log("Database connection established!");
        },
        err => {
          console.log("Error connecting Database instance due to: ", err);
        }
      );
  6. Create an api directory and three subfolders named controllers, models, and routes using the following code:

    mkdir api
    mkdir – controllers && mkdir – models && mkdir – routes
  7. Create a model file inside the controller directory (using touch userModel.js on the CLI) and then create the schema and model:

    const mongoose = require("mongoose"),
    const Schema = mongoose.Schema;
    const UserSchema = new Schema({
    twitter         : {
        fullName     : String,
        email        : String,
        createdOn: {
          type: Date,
          default: Date.now
        },
    },
    });
    
    //Create a mongoose model from the Schema
    mongoose.model("User", UserSchema);
  8. Create a passport file inside the config directory (using touch passport.js) and then create a Twitter strategy using the following code:

    const  TwitterStrategy = require("passport-twitter").Strategy;
    const mongoose = require('mongoose'),
    const User = mongoose.model('User');
    
    // Create an exposed function 
    module.exports = function (passport) {}
    serialize the user for the session
        passport.serializeUser(function (user, done) {
            done(null, user.id);
        });
    
     deserialize the user
        passport.deserializeUser(function (id, done) {
            User.findById(id, function (err, user) {
                done(err, user);
            });
        });
    
    //[…]
          } else {
            done(null, user);
          }
        });
      }
    )); 
    }
  9. Create a route file inside the routes directory (using touch route.js) and then create an exposed function as an exportable module that takes in app and passport using the following code:

    module.exports = function(app,passport)  {
    app.get('/auth/twitter',
      passport.authenticate(' twitter ', {scope:"email"}));
    
    app.get('/auth/ twitter /callback',
      passport.authenticate('twitter', { failureRedirect: '/error' }),
      function(req, res) {
        res.redirect('/success');
      });
    
    }
  10. Update the server.js file with the following code:

    //db instance connection
    require("./config/db"); app.get('/success', (req, res) => res.send("You have successfully logged in"));
    app.get('/error', (req, res) => res.send("error logging in"));
  11. Import and initialize passport using the following code:

    const passport = require("passport");
    // Authentication configuration
    app.use(session({
        resave: false,
        saveUninitialized: true,
        secret: 'bla bla bla' 
      }))
    app.use(passport.initialize());
    app.use(passport.session());
  12. Update the API endpoint using the following code:

    var routes = require('./api/routes/route'); //importing route
    routes(app,passport);app.listen(port, () => {
        console.log('Server running at http://localhost:${port}');
    });
  13. Run the server (using node server on the CLI), open a browser, and test this by typing in localhost:4000/auth/twitter on the browser address bar. You will obtain the following output:

    Figure 4.20: Successful authentication using Twitter