Jalaj Jain
Jalaj Jain

Reputation: 3

Why am I getting can't set Header after they are sent in this case?

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw")   
     res.json(authData)
  }catch(err){     
    res.status(401);
  }
  next()
};

exports.verifyAccessToken = async (accessToken) => {  
  return new Promise((resolve, reject) => {    
    jwt.verify(accessToken, keys.accessTokenSecret, (err, authData) => {          
      if(err){
        console.log(err)
        return reject(createError.Unauthorized()); 
      }
      console.log(authData, "accesstoken")
     return resolve(authData);   
    });
  })
};

exports.verifyRefreshToken = async (refreshToken, res) => {
  return new Promise((resolve, reject) =>{
    //const refreshToken = req.body.refreshToken;
    
    jwt.verify(refreshToken, keys.refreshTokenSecret, (err, authData) =>{
      if (err) { reject(createError.Unauthorized()); return }      
      const userId = authData.userId
      return resolve(userId)
    })
  })
};

exports.logOut = async (req, res) => {
  try{
      console.log("----------- logout")
      const refreshToken = req.cookies["refresh"];     
      if(!refreshToken) {throw createError.BadRequest();}
      const user =  await this.verifyRefreshToken(refreshToken);     
       res.clearCookie("refresh")
       res.clearCookie("access")

      res.sendStatus(204);
      res.redirect("/");

      return res.send()
  }catch(err){   
    console.log(err)
  }
};

**The routes file has code something like this **

app.get('/api/logout', authService.checkTokenMW,authService.logOut)

I have been trying to tackle this error from a while not exactly sure what header is setting itself multiple times

**Here is the error **

**

>     Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
>         at ServerResponse.setHeader (_http_outgoing.js:561:11)
>         at ServerResponse.header (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:771:10)
>         at ServerResponse.append (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:732:15)
>         at ServerResponse.res.cookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:857:8)
>         at ServerResponse.clearCookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:804:15)
>         at exports.logOut (E:\COURSE-WEBSITE\main-backend\wiz_backend\controllers\auth-controller.js:131:13)
>         at processTicksAndRejections (internal/process/task_queues.js:95:5) {
>       code: 'ERR_HTTP_HEADERS_SENT'
>     }

**

Upvotes: 0

Views: 125

Answers (2)

jfriend00
jfriend00

Reputation: 707218

The specific error you report is caused when your code tries to send more than one response to a given incoming request. You get to send one and only one response per request. So, all code paths, including error code paths have to be very careful to make sure that you are sending exactly one response.

Plus, if you have already sent a response, do not call next() because that will continue routing to other requests and eventually attempt to send some response (a 404 if no other handlers match).

With these in mind, you have several places your code needs fixing.

In your checkTokenWM:

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw")   
     res.json(authData)
  }catch(err){     
    res.status(401);
  }
  next()
};

You are calling res.json(authData) and then also calling next(). But, if the purpose of this is to be middleware that continues routing, then you should not be sending any response here if the token passes.

Then, in the catch block, you're setting a status, but not sending a response - that's not technically wrong, but probably not what you want. I'd suggest fixing both of those like this:

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw");
    // the access token verified so continue routing
    next();
  }catch(err){     
    // token did not verify, send error response
    res.sendStatus(401);
  }
};

In your logout:

exports.logOut = async (req, res) => {
  try{
      console.log("----------- logout")
      const refreshToken = req.cookies["refresh"];     
      if(!refreshToken) {throw createError.BadRequest();}
      const user =  await this.verifyRefreshToken(refreshToken);     
       res.clearCookie("refresh")
       res.clearCookie("access")

      res.sendStatus(204);
      res.redirect("/");

      return res.send()
  }catch(err){   
    console.log(err)
  }
};

You are attempting to send three responses upon successful logout and no responses upon error.

First off, res.sendStatus(204) sets the status AND sends an empty response. res.status(204) would just set the status for a future call that actually sends the response if that's what you meant to do. But, with that status, you can't then do res.redirect().

It's not entirely clear what you're trying to do here. If you want to redirect, then that needs to be a 3xx status so you can't use 204. I'm going to assume you want to do a redirect.

I'd suggest fixing by changing to this:

exports.logOut = async (req, res) => {
  try{
      console.log("----------- logout")
      const refreshToken = req.cookies["refresh"];     
      if(!refreshToken) {throw createError.BadRequest();}
      const user =  await this.verifyRefreshToken(refreshToken);     
      res.clearCookie("refresh");
      res.clearCookie("access");    
      res.redirect("/");

  }catch(err){   
      // can't call logout, if you weren't logged in
      res.sendStatus(401);
  }
};

FYI, most apps won't make it an error to call logout if you weren't logged in. They would just clear the cookies and redirect to home either way as it's really no issue whether they were previously logged in or not. The problem with doing it your way is if the cookies somehow get corrupted, then your code doesn't let the user attempt to clear things up by logging out and logging back in.

So, I'd probably just skip the token check entirely:

exports.logOut = async (req, res) => {
    res.clearCookie("refresh");
    res.clearCookie("access");    
    res.redirect("/");
};

And, also I'd change this:

app.get('/api/logout', authService.checkTokenMW,authService.logOut)

to this:

app.get('/api/logout', authService.logOut);

Upvotes: 0

Eduard
Eduard

Reputation: 1374

The problem is inside the middleware function. Once you check for authData you don't need to send it back to response using res.json(authData). Because after that response will be sent and your next() function will be triggered anyways. Since next() will be called your route handler will try to also send another response which is the conflict you face. At the same time, in catch block, you need to have a return statement, so the function execution will be stopped, and it will not reach till next()

exports.checkTokenMW = async (req, res, next) => {
  try{  
    const token = req.cookies["access"]   
    const authData = await this.verifyAccessToken(token);
    console.log(authData, "checkingTokenmw")   
    // res.json(authData) // <- remove this line
  }catch(err){     
    return res.status(401); // <- here
  }
  next()
};

Upvotes: 1

Related Questions