According to the express js middleware documentation there are four things middleware can do:
- Execute any code (including async code)
- Mutate or change either the request or response objects.
- End the request/response cycle. I call this short-circuiting the request.
- Call the next middleware function using
next
.
I want to show you an example of each type of middleware function.
Express Middleware adding user to request
This is a simple example of looking up a user from the session key and adding to the request object. This type of functionality is typically handled with passport js authentication.
const authenticateAndFindUser = (req, res, next) => {
// pull out x-session-api-key from request headers.
const { 'x-session-api-key': sessionKey } = req.headers;
// sessionStore has function find user by session. Behind the scene this could be a database
// lookup to MongoDB, PostgreSQL, Redis, etc.
sessionStore.findUserBySession(sessionKey)
.then(user => {
// attach the found user object to the request
// call next to call the next function in the express middleware chain
// where ever this middleware is mounted.
req.user = user;
next();
}, err => {
// Log the error object using the attached logger object
// I'll show you this logger in a future example.
req.logger.error(err);
// Send an unauthorized status
// https://httpstatuses.com/401
res.status(401);
res.send('Unauthorized');
// It is worth noting here that you don't need to call next.
// You are ending the request/response cycle here.
});
}
app.get('/user', authenticateAndFindUser, (req, res) => {
// req.user is available because authenticateAndFindUser attached
// them to the request object.
const { user } = req;
res.send({ user });
});
In this middleware we are using bunyan logger to make
structured log statements. Specifically bunyan logs JSON objects to stdout by default. Our
requestLogger
middleware creates a child logger with a few new properties. A very useful property
is the requestId
. This is a traceable unique identifier that can be greppable across many log
streams.
const { createLogger } = require('bunyan');
const uuid = require('uuid');
// onFinished is a node module that listens for the end of a request cycle to
// fire the registered callback. In our case, we log an info call with the status
// code and how long the request took in seconds.
const onFinished = require('on-finished');
// Microtime is a C++ node module that system calls to get a very precise clock time.
const microtime = require('microtime');
// A simple bunyan logger. Throw whatever properties into this object that make
// sense for your application. Perhaps a version or setting.
const appLogger = createLogger({
name: 'MyExpressApplication',
});
// requestLogger
const requestLogger = (req, res, next) => {
const reqLogger = appLogger.child({
// tag any logs made in this request context with
// the UUID. This makes it simple to grep through streaming logs.
requestId: uuid(),
path: req.path,
method: req.method,
});
req.logger = reqLogger;
const start = microtime.now();
onFinished(res, () => {
req.logger.info({
// in seconds
reqTime: (microtime.now() - start) / 1000000,
status: res.statusCode,
});
});
next();
};
// log all requests
app.use(requestLogger);
// Register a route handler for GET requests made to /hello
// Notice you do need to add requestLogger to this route chain.
app.get('/hello', (req, res) => {
req.logger.info('gonna say hello to the world soon. Right now, just in the console');
req.logger.error('uh uh an error!');
setTimeout(() => {
res.send('hello world');
}, 1000);
});
Express JWT middleware to authenticate and authorize user
In the next example I'm going to show you express JWT middleware. JWT stands for JSON web token. It's functionality is used to support claims between two parties. In the context of an express application the two parties are the client and the server. In a session based system a logged in user has a secret and private token that is sent with each request. The first middleware is an example where the server does a lookup in a database for that token to identify the user. In JWT authentication, the client includes a JSON payload indicating which user they are. The payload could include other data like permissions, roles, anything you want. The server can directly use this information to make operations for the given user. However, the first step is to validate that the server can trust the token. Each JSON web token is a signed payload that the server created with a known secret. The typically encryption algorithm used is HMAC 256.
So the typically flow for a web application is a user signs in with a password. The server responds with a signed JWT that claims the user is user 123. Now user 123 sends the JWT with each future request to the API. On each request, the server decrypts the JWT with the HMAC signature algorithm. If the signature matches then you know that the JWT was signed with the secret key and that we can trust the payload.
I'm going to show you the three steps of using JWT middleware in express.
// Require application dependencies
const express = require('express');
const { urlencoded, json } = express;
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
// app secrets
// In practice store this somewhere in the environment
const JWT_KEY = 'keyboardcat';
// Create our app by calling the express function
const app = express();
// Add in our middleware for ALL routes
app.use(urlencoded({
extended: true,
}));
app.use(json());
app.use(cookieParser());
let users = 0;
// #1 on registration assign a JWT
app.get('/user', (req, res) => {
const { email, password, passwordConfirm } = req.query;
if (password === passwordConfirm) {
// payload is generally what you claim to be true.
// In the example, if we get a verified version of this
// payload, we can trust the email, id, and role from the request.
const payload = {
email,
id: users++,
role: 'user',
};
const signed = jwt.sign(payload, JWT_KEY, {
expiresIn: '1 week',
});
res.setHeader('Set-Cookie', `jwt-session=${signed}`);
res.status(302);
res.setHeader('Location', `/user/${payload.id}`);
res.send();
} else {
res.status(400);
res.send('Passwords do not match');
}
});
// authenticateUser looks for the 'jwt-session' cookie. Earlier
// we added cookie parser. This middleware parses a cookie string
// into a key value object we can use.
// Once we have the jwtSession string we can verify it. The jwt
// library throws an error if the JWT is not verified. If it is
// verified, it returns the decoded object
// {
// email: 'email', id: 1, role: 'user'
// }
// Then we attach to the request object using req.user = decoded;
const authenticateUser = (req, res, next) => {
const {
cookies: {
['jwt-session']: jwtSession,
} = {},
} = req;
try {
const decoded = jwt.verify(jwtSession, JWT_KEY);
req.user = decoded;
next();
} catch (e) {
res.status(401);
res.send('Not Authorized');
}
};
// This middleware function checks that the parsed user matches
// the user id passed in the URL.
const authorizeUser = (req, res, next) => {
if (req.params.id !== req.user.id) {
res.status(403);
res.send('Forbidden');
} else {
next();
}
}
// Finally using both middleware you can authenticate a user by the passed
// in JWT. And you can authorize the user for a particular action. In this
// case the user id in the JWT and in the params much match.
app.get('/user/:id', authenticateUser, authorizeUser, (req, res) => {
res.send(`<h1>Welcome ${req.user.email}!</h1>`);
});
// Get port from environment or default to port 3000.
const port = process.env.PORT || 3000;
// Ask our app to listen on the calculated port.
app.listen(port, () => {
console.log(`Successfully express js server listening on port ${port}`);
});
If you want to see more in depth articles about user authentication, checkout my PassportJs Tutorial.