In the first article of this authentication series, Create secure authentication using HTTP-only cookies in Express.js, we outlined the process of creating secure authentication using HTTP-only cookies. In this article, we'll walk through a step-by-step guide to creating an auth middleware and using the middleware to protect Express API routes.
First, install cookie-parser
as a dependency and @types/cookie-parser
as a dev dependency in your Express.js app. Next, navigate to your app.ts
file and add the following code:
import cookieParser from 'cookie-parser';
app.use(cookieParser());
In the above code, we've imported cookieParser
and used it as middleware in our Express app, which will help us parse cookies from request headers. Now, within your middleware
directory, create an auth.ts
file with the following code:
import User from '../models/user';
import jwt, { JwtPayload } from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
export default async function handler(
req: Request,
res: Response,
next: NextFunction
) {
if (!req.cookies) {
console.log('Invalid credentials');
res.status(401);
throw new Error('Invalid credentials');
}
const { token } = req.cookies;
if (!token) {
console.log('Invalid credentials');
res.status(401);
throw new Error('Invalid credentials');
}
try {
const decoded = jwt.verify(
token,
process.env.JWT_SECRET as string
) as JwtPayload;
const user = await User.findById(decoded._id)
.select('-__v -password -updatedAt -createdAt')
.lean();
if (!user) {
console.log('Invalid credentials');
res.status(401);
throw new Error('Invalid credentials');
}
req.user = user;
next();
} catch (err) {
console.log(err);
throw err;
}
}
In the above code, we have a handler
function that accepts three parameters. The first and second parameters, req
and res
, are the Express Request
and Response
objects respectively, and the third parameter, next
, is a function that calls the next function/middleware.
Within the handler
function, we check for cookies and throw an error if no cookies are found in the request headers. Otherwise, we destructure req.cookies
and pull the token
out of it. In the first article of this authentication series, we named the cookie as token
, which we are accessing here.
Next, we check for the token
and throw an error if no token
is found in cookies. Otherwise, we create a decoded
variable by decoding the token
using the jwt.verify
method. If the token
is valid, we should have a _id
property in the decoded
object, which we set to the token
in the first article of this authentication series.
Next, we query our database and find the user with the _id
. We throw an error if no user is found; otherwise, we add the user
object in the req
object to access it later in our routes. Finally, we call the next
function to proceed with the next function/middleware.
Now, let's create a protected route using the auth
middleware we just created. So, navigate to the user
router and create a /me
route with the following code:
import auth from '../middleware/auth';
router.get('/me', auth, async (req, res) => {
res.status(200).json(req.user);
});
In the above code, we have a route we can use to get a user's details. This is an example of a route that should be protected and can only be accessible by a logged-in user. So, we add the auth
middleware between the route and the controller function.
When a request is made to this route, the auth
middleware runs before the controller function. The auth
middleware does its things, and if the credentials are valid, it adds the user's details to the req
object. We can then access and return the user's details from the req
object within the controller function.
That's it! You can now use the auth
middleware in any route you want to protect. You can also access the user's data, e.g., role from req.user
, and create role-based authorization. Hope you learned a thing or two from this article. Feel free to share the article with someone who might also find it useful. Cheers!