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.
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.
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.
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 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.
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.
3.144.97.216