Create secure authentication using HTTP-only cookies in Express.js
First, install bcrypt and jsonwebtoken as dependencies and @types/bcrypt and @types/jsonwebtoken as dev dependencies in your Express.js app. We'll use bcrypt to hash passwords and jsonwebtoken to create secure tokens in our Express server.
Once installed, run openssl rand -base64 32 in your terminal to create a random string, copy the string, and create an environment variable named JWT_SECRET in your project. After that, within your /lib/utils directory, create a setCookie function with the following code:
import { Types } from 'mongoose';
import { Response } from 'express';
import jwt from 'jsonwebtoken';
export function setCookie(res: Response, _id: Types.ObjectId) {
const jwtToken = jwt.sign({ _id }, process.env.JWT_SECRET as string, {
expiresIn: '7d',
});
res.cookie('token', jwtToken, {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 7 24 60 60 1000, // 1 week
secure: process.env.NODE_ENV !== 'development',
});
}
The above function accepts two parameters. The res parameter is the Express Response object, and the _id parameter is the user ID which we'll store in the JSON web token.
Within the function, we create a jwtToken variable by using the jwt.sign method. This method accepts three parameters. The first parameter is the information we want to store in the token, the second parameter is the JWT secret, and the third parameter is an options object. In the options object, we set expiresIn to '7d', which expires the token in a week.
After that, we use the res.cookie method to set the cookie to the response header. This method accepts three parameters. The first parameter is the cookie name, the second parameter is the cookie content, and the third parameter is an options object.
The options object is important as we set the security parameters here. Within this object, we set the value of path to /, which defines the scope of the cookie. Next, we set the value of httpOnly to true, which makes the cookie inaccessible by client-side JavaScript. After that, we set the value of sameSite to strict, which means that the browser only sends the cookie with requests from the cookie's origin server. Next, we set the value of maxAge to seven days to match our token expiration. Finally, we set the value of secure to true when the Node Environment is 'production'.
Now, within your routes directory, create a user.ts file with the following code:
Recommended by LinkedIn
import { Router } from 'express';
const router = Router();
export default router;
Then, navigate to your app.ts file, import the user router, and add it as the middleware like the following code:
import User from './routes/user';
app.use('/users', User);
Now, get back to the user router and create a /register route by adding the following code:
import bcrypt from 'bcrypt';
import User from '../models/user';
import { setCookie } from '../lib/utils';
router.post('/register', async (req, res) => {
const { name, email, password } = req.body;
if (!name || !email || !password) {
console.log('Name, email, or password is missing');
res.status(400);
throw new Error('Name, email, or password is missing');
}
try {
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
const response = await User.create({
name,
email,
password: hashedPassword,
});
const user = response.toObject();
setCookie(res, user._id);
res.status(201).json(user);
} catch (err) {
console.log(err);
throw err;
}
});
In this code, within the controller function, we destructure the name, email, and password from the request body and check if all three fields are provided.
After that, we create a salt which is required to hash the password. Next, we hash the password by using the bcrypt.hash method. After that, we create a user in the database, convert the response to an object, set the cookie to the response header, and send the user with the response.
Enjoying the content? Read the rest of the article here.