Scenario: You have built a web application using the benhall/express-demo project and now want to secure it against common threats. In this guide, we will cover implementing authentication and authorization, encrypting sensitive data, and validating user input.
Solution:
- Authentication and Authorization:
First, let’s secure our application by implementing authentication and authorization using middleware. We will use Passport.js, an Express-compatible authentication middleware for Node.js.
a. Install Passport.js:
Add the following line to your package.json
file under dependencies:
"passport": "^0.4.2"
b. Create a new file app.js
in the routes
directory and set up Passport.js:
const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
// User model
const User = require('../models/user');
// Configure Passport.js
passport.use(new LocalStrategy({ usernameField: 'email' },
async function(email, password, done) {
const user = await User.findOne({ email: email });
if (!user) return done(null, false, { message: 'Incorrect email.' });
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) return done(null, false, { message: 'Incorrect password.' });
return done(null, user);
}
));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(async function(id, done) {
const user = await User.findById(id);
done(null, user);
});
// Create a new Express router
const router = express.Router();
router.use(passport.initialize());
router.use(passport.session());
// Protect routes
router.get('/protected', isAuthenticated, function(req, res) {
res.send('Welcome to the protected section!');
});
// Define middleware
function isAuthenticated(req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect('/login');
}
module.exports = router;
- Encrypting Sensitive Data:
To encrypt sensitive data, we will use the bcryptjs
library. Update your package.json
file under dependencies to include it:
"bcryptjs": "^2.4.3"
Modify the User
model in models/user.js
to hash the password before saving it to the database:
const bcrypt = require('bcryptjs');
// ...
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
validate: {
isEmail: true,
},
},
password: {
type: String,
required: true,
minlength: 6,
},
}, {
timestamps: true,
});
UserSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
module.exports = mongoose.model('User', UserSchema);
- Validating User Input:
To validate user input, we will use Helmet.js, a set of middleware functions to help secure Express.js web applications by setting various HTTP headers. Install Helmet.js by adding it to your package.json
file under dependencies:
"helmet": "^4.1.2"
Update your app.js
file to include Helmet.js:
const helmet = require('helmet');
// ...
app.use(helmet());
Create a new file app.js
in the routes
directory and set up Helmet.js:
const helmet = require('helmet');
// Configure Helmet.js
app.use(helmet());
// Define middleware
function xssFilter() {
return function(req, res, next) {
res.set('X-XSS-Protection', '1; mode=block');
next();
};
}
// Apply middleware
app.use(xssFilter);
Tests:
To verify the security measures implemented, you can perform the following tests:
- Authentication and Authorization:
- Attempt to access a protected route without being authenticated.
- Attempt to access a protected route with incorrect credentials.
- Attempt to access a protected route with valid credentials.
- Encrypting Sensitive Data:
- Attempt to retrieve a user’s password from the database.
- Attempt to modify a user’s password and verify it is encrypted in the database.
- Validating User Input:
- Attempt to access the application with malicious input in the URL or headers.
- Attempt to submit malicious input through forms.
These tests should help ensure that the security measures have been implemented correctly and effectively.