Securing Express

Authentication is perhaps the most important and one of the trickier topics with regard to securing a web application from the front. Certainly, there are lots of different threat vectors.

Helmet

One of the easiest things we can do to secure our Express application is to install and use the security middleware called Helmet. Helmet adds a number of security headers and policies, as well as preventing some attacks, such as clickjacking.

It does most of this under the covers, without the need for configuration on our part. For more detailed information, and to find alternative ways to congigure it.

To get started with Helmet, first install it using npm:

$ npm install helmet --save
[email protected] node_modules/helmet
|- [email protected]
|- [email protected]
|- [email protected]
|- [email protected]
|- [email protected]
|- [email protected]
|- [email protected]
|- [email protected] ([email protected])
|- [email protected] ([email protected])
|- [email protected] ([email protected], [email protected], [email protected])
|_ [email protected] ([email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected])

You can actually see by the names of the submodules what some of the protections include.

Next, we simply need to add the module to our main app.js file:

var express = require('express'); 
var path = require('path'); 
var favicon = require('serve-favicon'); 
var logger = require('morgan'); 
var cookieParser = require('cookie-parser'); 
var bodyParser = require('body-parser'); 
var isJSON = require('./utils/json'); 
var routing = require('resource-routing'); 
var controllers = path.resolve('./controllers'); 
var helmet = require('helmet'); 
 
//Database stuff 
var mongodb = require('mongodb'); 
var monk = require('monk'); 
var db = monk('localhost:27017/giftapp'); 
 
var mongoose = require('mongoose'); 
mongoose.connect('localhost:27017/giftapp'); 
 
var routes = require('./routes/index'); 
var users = require('./routes/users'); 
var dashboard = require('./routes/dashboard'); 
 
var app = express(); 
 
// view engine setup 
app.set('views', path.join(__dirname, 'views')); 
app.set('view engine', 'ejs'); 
 
app.set('x-powered-by', false); 
 
app.locals.appName = "My Gift App"; 
 
// uncomment after placing your favicon in /public 
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 
app.use(logger('dev')); 
app.use(bodyParser.json()); 
app.use(bodyParser.urlencoded({ extended: false })); 
app.use(cookieParser()); 
app.use(express.static(path.join(__dirname, 'public'))); 
app.use(isJSON); 
 
var flash = require('connect-flash'); 
app.use(flash()); 
 
var passport = require('passport'); 
var expressSession = require('express-session'); 
app.use(expressSession({secret: 'mySecretKey'})); 
app.use(passport.initialize()); 
app.use(passport.session()); 
 
var initializePassport = require('./passport/init'); 
initializePassport(passport); 
 
//Database middleware 
app.use(function(req,res,next){ 
    req.db = db; 
    next(); 
}); 
 
app.use('helmet'); 
 
app.use('/', routes); 
app.use('/users', users); 
app.use('/dash', dashboard); 
 
var login = require('./routes/login')(passport); 
app.use('/login', login); 
 
routing.resources(app, controllers, "giftlist"); 
routing.expose_routing_table(app, { at: "/my-routes" }); 
 
// catch 404 and forward to error handler 
app.use(function(req, res, next) { 
  var err = new Error('Not Found'); 
  err.status = 404; 
  next(err); 
}); 
 
// error handlers 
 
// development error handler 
// will print stacktrace 
if (app.get('env') === 'development') { 
  app.use(function(err, req, res, next) { 
    res.status(err.status || 500); 
    res.render('error', { 
      message: err.message, 
      error: err 
    }); 
  }); 
} 
 
// production error handler 
// no stacktraces leaked to user 
app.use(function(err, req, res, next) { 
  res.status(err.status || 500); 
  res.render('error', { 
    message: err.message, 
    error: {} 
  }); 
}); 
 
 
module.exports = app; 

And there you have it. We've mitigated a pile of common web security vulnerabilities.

Please note that app.use('helmet') must come before any of the routes or the protection will not be in place for those routes.

CSRF

One of the most common web attack vectors is the Cross-Site Request Forgery (CSRF). CSRF is an attack where some untrusted source, such as another website or even an e-mail, takes advantage of a user's authenticated status to execute privileged code on another application. You can find more detailed information about CSRF at https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF).

Once again, middleware to the rescue:

$ npm install csurf --save
[email protected] node_modules/csurf
|- [email protected]
|- [email protected]
|- [email protected] ([email protected], [email protected])
|_ [email protected] ([email protected], [email protected], [email protected], [email protected])

The csurf middleware is then plugged into our app.js the same way, with Helmet required in and then used. Please note that the app.use('csurf') must come after the cookie parser and express-session middleware, since it uses both.

Next, we edit our login routes file:

var express = require('express'); 
var router = express.Router(); 
 
 
module.exports = function(passport){ 
 
 
    router.get('/', function(req, res) { 
        res.render('login/login', { message: req.flash('message'), csrfToken:
        req.csrfToken() }); 
    }); 
 
 
    router.post('/', passport.authenticate('login', { 
        successRedirect: '/dash', 
        failureRedirect: '/login', 
        failureFlash : true 
    })); 
 
 
    router.get('/signup', function(req, res){ 
        console.log('signing up'); 
        res.render('login/signup',{message: req.flash('message'), csrfToken:
        req.csrfToken()}); 
    }); 
 
 
    router.post('/register', passport.authenticate('signup', { 
        successRedirect: '/dash', 
        failureRedirect: '/login/signup', 
        failureFlash : true 
    })); 
 
    router.get('/signout', function(req, res) { 
        req.logout(); 
        res.redirect('/login'); 
    }); 
 
    return router; 
} 

We add a CSRF token to the data we pass to the login and signup pages.

Next, we add a hidden field to our login and signup pages:

<!DOCTYPE html> 
<html> 
<head > 
    <title>Signup</title> 
 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css"> 
 
</head> 
<body> 
 
<nav class="nav navbar-default"> 
    <div class="container-fluid"> 
        <div class="navbar-header"> 
            <a class="navbar-brand" href="#"> Giftapp Signup</a> 
        </div> 
    </div> 
</nav> 
 
<div class="container"> 
    <% if(message){ %> 
    <div class="row"> 
        <div class="col-md-4 col-md-offset-4" role="alert"> 
            <%= message %> 
        </div> 
    </div> 
    <% } %> 
 
    <div class="row"> 
        <div class="col-md-6 col-md-offset-3"> 
            <form method="post" action="/login/register"> 
                <div class="form-group"> 
                    <label for="username">Username</label> 
                    <input type="text" class="form-control" 
                     id="username" name="username" placeholder="username"> 
                </div> 
                <div class="form-group"> 
                    <label for="passwordd">Password</label> 
                    <input type="password" class="form-control" id="password" 
                      name="password" placeholder="Password"> 
                </div> 
                <div class="form-group"> 
                    <label for="email">Email address</label> 
                    <input type="email" class="form-control" id="email" 
                     name="email" placeholder="Email"> 
                </div> 
                <div class="form-group"> 
                    <label for="firstName">First Name</label> 
                    <input type="text" class="form-control"  
                     id="firstName" name="firstName" placeholder="First Name"> 
                </div> 
 
                <div class="form-group"> 
                    <label for="lastName">Last Name</label> 
                    <input type="text" class="form-control" id="lastName"
                     name="lastName" placeholder="Last Name"> 
                </div> 
           <input type="hidden" name="_csrf" value="<% csrfToken %>"> 
<button type="submit" class="btn btn-default">Submit</button> 
 
            </form> 
        </div> 
    </div 
 
 
</div> 
 
</body> 
 
</html> 

Restarting our server, reloading our login or signup page, and viewing source, you'll see a hidden input tag like the following:

<input type="hidden" name="_csrf" value="UBDtLOuZ-emrxDagDmIjxxsomxFS2pSeXKb4"> 

That hidden field gets passed back with our request and checked for validity.

Taking additional security measures

We've taken some basic, yet powerful, steps to secure our application against some of the biggest threats. A full exploration of the possible threats and security measures is beyond the scope of this book, but there are a few considerations worth mentioning.

Implementing HTTPS

Implementing HTTPS when you deploy to production prevents a number of different man-in-the-middle attacks, and prevents data from being intercepted and modified along the way. We'll explore this in the chapter on deploying to production.

Furthermore, you can set your cookies to be secure. Again, we'll cover this when we talk about deployment.

Avoiding running as root

One of the reasons we use port 3000 locally is that in many environments, running on port 80 (the standard HTTP port) requires running as root. A well-known security policy is that all processes should be run with as few privileges as possible. Again, this is something we'll take of when we deploy—mostly by using a PaaS provider.

Validating user input

Right now, our application does very little input validation—except that username and password are required for signup and login. But we can and should check user input on both the client and server side.

We will add some input validation in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.97.216