I have a function that creates JWT-tokens and set them as cookies. I use this function in my login controller right before I send a response to a client. The problem is the controller sends a response before set-JWT-as-cookies function is finished.
controller
exports.login = catchAsync(async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username }).select(" password");
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json("wrong_credentials");
}
await jwtHelper.createTokens(user._id, res);
console.log("right before sending a response", Date.now());
res.status(200).json({
status: "success",
data: {
role: user.role,
},
});
});
createTokens function
exports.createTokens = catchAsync(async (userId, res) => {
const accessToken = createAccessToken(userId);
const { tokenId: refreshTokenId, token: refreshToken } = createRefreshToken();
await Token.create({ tokenId: refreshTokenId });
const accessCookieOptions = {
expires: new Date(
Date.now() process.env.JWT_COOKIE_EXPIRES_IN * 60 * 1000
),
httpOnly: true,
secure: true
};
const refreshCookieOptions = {
expires: new Date(
Date.now()
process.env.REFRESH_TOKEN_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
),
httpOnly: true,
secure: true
};
console.log("right before creating cookies", Date.now());
res.cookie("access-token", accessToken, accessCookieOptions);
res.cookie("refresh-token", refreshToken, refreshCookieOptions);
console.log("right after creating cookies", Date.now());
});
And that's what I get in a console
right before sending a response 1631544430242
[1] right before creating cookies 1631544430324
[1] (node:11224) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
How do I set cookies before sending the response?
CodePudding user response:
Because of the catchAsync
, the createTokens
function returns immediately even though it involves the asynchronous Token.create
. After it returns, res.json
is executed, and res.cookie
comes only later, after the Token.create
has also finished. This leads to the error.
The following illustrates this:
const {catchAsync} = require("catch-async-express");
async function f() {
return new Promise(function(resolve, reject) {
setTimeout(resolve, 1000);
}).then(() => console.log(1));
}
async function g() {
await catchAsync(f)();
console.log(2);
}
g();
2
is logged immediately, and 1
is logged after the 1000ms timeout.
Avoid using catchAsync
here.