Implement Authentication Middleware
We will now create the middleware that will protect selected routes and ensure that a user is authenticated before allowing their requests to go through. Let's start!
Create a new middleware
folder, and an auth.js
file inside it:
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.verify(token, 'RANDOM_TOKEN_SECRET');
const userId = decodedToken.userId;
if (req.body.userId && req.body.userId !== userId) {
throw 'Invalid user ID';
} else {
next();
}
} catch {
res.status(401).json({
error: new Error('Invalid request!')
});
}
};
In this middleware:
Because many things can go wrong, put everything inside a
try...catch
block.Extract the token from the incoming request's
Authorization
header — remember that it will also contain theBearer
keyword, so use the split function to get everything after the space in the header. Any errors thrown here will wind up in thecatch
block.Then use the
verify
function to decode your token. If the token is not valid, this will throw an error.Extract the user ID from your token.
If the request contains a user ID, compare it to the one extracted from the token. If they are not the same, throw an error.
Otherwise, all is well, and the user is authenticated — pass execution along using the
next()
function.
You now need to apply this middleware to your stuff
routes, which are the ones you want to protect. In your stuff
router:
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const stuffCtrl = require('../controllers/stuff');
router.get('/', auth, stuffCtrl.getAllStuff);
router.post('/', auth, stuffCtrl.createThing);
router.get('/:id', auth, stuffCtrl.getOneThing);
router.put('/:id', auth, stuffCtrl.modifyThing);
router.delete('/:id', auth, stuffCtrl.deleteThing);
module.exports = router;
Import your middleware and pass it as an argument to the routes you want to protect.
Now, from the front end, you should be able to log in and use the app normally. To check that unauthorized requests do not work, you can use an app like Postman to pass a request without an Authorization
header — the API will refuse access and send a 401 response.
Congratulations! Your API now implements token-based authentication and is properly secure. Or is it?
Your Turn! Authorization Flaw
In this coming podcast, I present the challenge that awaits you, which is to find the authorization flaw in our API.
It turns out that there is a security vulnerability in the API. One of the routes allows for requests to potentially be made by the wrong person. Can you figure out what the problem is? How can you fix it?
Here is your mission:
Find the route that has this problem: Which route has this security vulnerability?
Fix this vulnerability and find out how to solve this security problem.
Don't hesitate to listen to the challenge again, which comes with a clue to guide you to the solution ;) . And if you can't do it, don't worry, I'll explain the solution right away below.
Solving the Problem
Ready to discover the solution? Let's check it out!
The route with the security issue is indeed the DELETE route. Why? Because the front end doesn't send a user ID when requesting to delete a Thing
. Therefore, you cannot check if the user making the request is the owner of the thing they are trying to delete. This means that, in theory, anyone with a valid token could delete anyone's thing. Quite a glaring security issue!
So how do you fix it? Knowing that you can't change the front-end app, you need to compare the user ID from the token with the userId
field of the Thing
you get from the database. The challenge is that you currently don't have access to the extracted user ID in the DELETE controller. However, there is a simple solution:
Create an auth object on your request object and place the extracted
userId
inside thatauth
object in your authentication middleware:
req.auth = { userId };
In your DELETE controller, retrieve the
Thing
from the database, then check itsuserId
against the ID you extracted from the token — if they match, delete theThing
; if not, return an error.
exports.deleteThing = (req, res, next) => {
Thing.findOne({ _id: req.params.id }).then(
(thing) => {
if (!thing) {
return res.status(404).json({
error: new Error('Objet non trouvé !')
});
}
if (thing.userId !== req.auth.userId) {
return res.status(401).json({
error: new Error('Requête non autorisée !')
});
}
Thing.deleteOne({_id: req.params.id}).then(
() => {
res.status(200).json({
message: 'Deleted!'
});
}
).catch(
(error) => {
res.status(400).json({
error: error
});
}
);
}
);
};
Now you know for certain that only the owner of a Thing
can delete it!
Let's Recap!
jsonwebtoken's
verify()
method lets you check the validity of a token (on an incoming request, for example).Make sure you add authentication middleware in the right order on the right routes.
Keep an eye out for security flaws!
What You've Learned in This Part of the Course
You added a User data model to store user information in your database.
You implemented secure password encryption to safely store user passwords.
You created and sent JSON web tokens to the front end to authenticate requests.
You added authentication middleware to secure routes in your API, meaning that only authenticated requests would be handled.
In the final part of this course, you will learn:
How to capture files coming in from the front end.
How to save them to your server.
How to delete them when they are no longer needed.