Step 9

Adding JWT support

Let's add authentication! A very popular way of implementing server-side authentication is by using JWTs (JSON Web Tokens). When a user creates an account or logs in, the server generates a token and sends it to the user. If the user wants to make a request to the server on a URL that requires authentication, the user will also send that token to the server, where the server can then verify it and proceed with the request or send the user an error. We will be using the Passport and jsonwebtoken libraries, which will handle authentication for us.

Let's first install the necessary packages:

$ npm i -S passport passport-jwt jsonwebtoken

Let's create some middleware:

src/middleware/passport.js

const passport = require('passport');
const { Strategy, ExtractJwt } = require('passport-jwt');
const User = require('../models/user');
const { SECRET } = require('../config');

const extractToken = req =>
	ExtractJwt.fromExtractors([
		ExtractJwt.fromAuthHeaderAsBearerToken(),
		ExtractJwt.fromBodyField('token'),
		ExtractJwt.fromHeader('token'),
		ExtractJwt.fromUrlQueryParameter('token')
	])(req);

module.exports.passportMiddleware = pass =>
	pass.use(
		new Strategy(
			{
				jwtFromRequest: extractToken,
				secretOrKey: SECRET
			},
			async (payload, done) => {
				try {
					const user = await User.findById(payload.id);
					return user ? done(null, user) : done(null, false);
				} catch (error) {
					console.error('Strategy error:', error);
					return done(error, false);
				}
			}
		)
	);

module.exports.extractUser = () => (req, res, next) =>
	passport.authenticate('jwt', { session: false }, (err, data, info) => {
		req.user = data || null;
		next();
	})(req, res, next);

module.exports.auth = () => (req, res, next) =>
	req.user ? next() : res.status(401).send('Unauthorized');

There is a lot going on in this file, so let's break it down function by function.

passportMiddleWare is a function that takes in a passport instance and registers a new strategy for authenticating users. It uses passport-jwt to create a new strategy that attempts to extract a user's token from the HTTP request using an HTTP request header, field in the HTTP body, or query parameter and verifies that with our secret. When it's done with that, it returns the decoded JWT, where we will have an id field that we use to associate with an actual user in the database. If there is a user, we return it and if there isn't we return false.

extractUser is a custom Express.js middleware that will run before all of our route handlers. You can read more about middleware here: https://medium.com/@agoiabeladeyemi/a-simple-explanation-of-express-middleware-c68ea839f498. It is a function that returns a function that looks like our route handlers, except it has a next parameter, which we call if we want to continue down the execution chain of our HTTP route handlers. This particular middleware is meant to run after extractUser runs and only continues to the next middleware if there is a user attached to the current HTTP request.

These don't do anything yet, so let's hook it up to our HTTP server

src/index.js

const express = require('express');
const passport = require('passport');
const { PORT } = require('./config');
const { extractUser, passportMiddleware } = require('./middleware/passport');
const books = require('./routes/books');

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(passportMiddleware(passport).initialize());
app.use(extractUser());

app.use('/api/books', books);

app.listen(PORT, () => {
	console.log(`Listening on port: ${PORT}`);
});

By using app.use, we register our middleware with our HTTP server and our functions will run before any HTTP requests for "/api/books" will be processed.

Last updated

Was this helpful?