Abdullah Ch
Abdullah Ch

Reputation: 2151

[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client in Express

The Cookie that I am sending from http://localhost:5000/users/register , is not being set into the header of http://localhost:5000/users/register. I am able to access the cookie in the body but not in the headers.

My App.js code is

require("dotenv").config();
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const fileUpload = require("express-fileupload");
const cookieParser = require("cookie-parser");
const users = require("./routers/users");

const app = express();
app.use(express.json());
app.use(cookieParser());
app.use(cors());
app.use(
  fileUpload({
    useTempFiles: true,
  })
);

// connecting to MongoDB
const mongoURL = process.env.MONGO_URL;

mongoose
  .connect(mongoURL, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useCreateIndex: true,
  })
  .then(() => console.log("DataBase has been connected !"))
  .catch((err) => console.log("Cannot connect to database", err.message));

// routes
app.use("/users", users);

const port = process.env.PORT || 5000;
app.listen(port, () => {
  console.log(`I am listening at ${port}`);
});

My Router's Code


const express = require("express");
const bcrypt = require("bcrypt");
const { Users, userSchema } = require("../models/user");
const jwt = require("jsonwebtoken");

const router = express.Router();

// registering
router.post("/register", async (req, res) => {
  try {
    const { name, email, password } = req.body;
    console.log(name, email, password);
    //console.log(Users);
    //console.log(userSchema);
    let user = await Users.findOne({ email });
    if (user) return res.status(404).json({ msg: "The email already exists" });

    if (password.length < 6)
      return res.status(404).json({ msg: "The password is less than 6" });

    // Hashing password
    const newPassword = await bcrypt.hash(password, 10);

    // creating a user and saving in db
    user = await new Users({ name, email, password: newPassword });
    await user.save();

    // creating JWT token for authenticating the user
    const accessToken = createAccessToken({ id: user._id });
    const refreshToken = createRefreshToken({ id: user._id });


// Cookie is being sent in the headers to the listed "path"
    res.cookie("refreshToken", refreshToken, {
      httpOnly: true,
      path: "/users/refreshtoken",
    });

    res.json({ accessToken });
    //return res.json({ password, newPassword, user, accessToken, refreshToken });
  } catch (err) {
    return res.status(500).json({ msg: err.message });
  }
});

router.get("/refreshtoken", (req, res) => {
  try {
    const rfToken = req.cookies.refreshToken;
    if (!rfToken)
      return res.status(400).json({ msg: "Please login or sign up" });

    // verifying the token that we sent when user registered
    // the token shows ig it's the registered user or not
    jwt.verify(rfToken, process.env.REFRESH_TOKEN, (error, decoded) => {
      if (error)
        return res.status(400).json({ msg: "Please login or sign up" });
      console.log(decoded);
      const accessToken = createAccessToken({ id: decoded.id });
      res.json({ decoded, accessToken });
    });

    res.json({ msg: "DS", rfToken });
  } catch (error) {
    return res.status(500).json({ msg: error.message });
  }
});

const createAccessToken = (userID) => {
  return jwt.sign(userID, process.env.ACCESS_TOKEN, { expiresIn: "1d" });
};

const createRefreshToken = (userID) => {
  return jwt.sign(userID, process.env.REFRESH_TOKEN, { expiresIn: "7d" });
};
module.exports = router;

The Cookie is not being set to the header of the "/users/refreshtoken", it is being sent in the body, I can see the cookie in the body of "/users/refreshtoken" but not in the header. I don't know why.

Upvotes: 1

Views: 297

Answers (1)

jfriend00
jfriend00

Reputation: 707328

This error is usually caused by improper asynchronous sequencing which causes you to have one or more code paths that can attempt to send multiple responses to the same incoming request.

I see an example of this in your router.get("/refreshtoken", ...) route handler. One place this problem can occur is here where it's marked:

router.get("/refreshtoken", (req, res) => {
  try {
    const rfToken = req.cookies.refreshToken;
    if (!rfToken)
      return res.status(400).json({ msg: "Please login or sign up" });

    // verifying the token that we sent when user registered
    // the token shows ig it's the registered user or not
    jwt.verify(rfToken, process.env.REFRESH_TOKEN, (error, decoded) => {
      if (error)
        return res.status(400).json({ msg: "Please login or sign up" });
      console.log(decoded);
      const accessToken = createAccessToken({ id: decoded.id });
=====>
      res.json({ decoded, accessToken });
=====>
    });

=====>
    res.json({ msg: "DS", rfToken });
=====>

  } catch (error) {
    return res.status(500).json({ msg: error.message });
  }
});

This code will launch the asynchronous and non-blocking jwt.verify() function and then will immediately execute:

    res.json({ msg: "DS", rfToken });

Then, some time later it will execute either:

return res.status(400).json({ msg: "Please login or sign up" });

or

const accessToken = createAccessToken({ id: decoded.id });

both of which will try to send a second response. It appears to me that your code in this route has three possible outcomes:

  1. No token is present
  2. Token is present, but not valid
  3. Token is present and valid

That means you should have three non-overlapping responses. But, you have four places you send a response and they overlap, causing duplicate responses. I don't know exactly why you have the res.json({ msg: "DS", rfToken }); line of code as it is always run anytime there is a token present (whether valid or invalid). You need to fix that. Probably you should just remove that line of code entirely or replace one of the other responses with this line of code inside the jwt.verify() callback.

Upvotes: 1

Related Questions