0

I'm trying to make a simple API with passport-jwt and passport-local-mongoose, I set all the JWT functions up and make some routes, like one to register and one to sign in! One ogf these routes receives a get request to list all documents that exists on the database, however, when I try to make this request the server gives me this error:

DError [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:533:11)
    at ServerResponse.header (/home/mdsp/Documents/Challenges/passportJWTMongoose/node_modules/express/lib/response.js:771:10)
    at ServerResponse.json (/home/mdsp/Documents/Challenges/passportJWTMongoose/node_modules/express/lib/response.js:264:10)
    at /home/mdsp/Documents/Challenges/passportJWTMongoose/src/controllers/Document.ts:9:18
    at step (/home/mdsp/Documents/Challenges/passportJWTMongoose/src/controllers/Document.ts:33:23)
    at Object.next (/home/mdsp/Documents/Challenges/passportJWTMongoose/src/controllers/Document.ts:14:53)
    at fulfilled (/home/mdsp/Documents/Challenges/passportJWTMongoose/src/controllers/Document.ts:5:58)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  code: 'ERR_HTTP_HEADERS_SENT'
}

I searched about it and found that this error usually is caused because a double callback or something like that, but I'm not able to specify where is the error and why it's happening this with my code. Here's the Document controller:

import { Request, Response } from "express";

import DocumentModel from "../database/models/Document";

class Document {
  async index(req: Request, res: Response) {
    try {
      const documents = await DocumentModel.find();
      return res.status(200).json(documents);
    } catch (err) {
      console.log(err);
      return res.status(400).json({ error: err.message });
    }
  }
}

export default new Document();

This is my routes file:

import { Router } from "express";

import auth from "./middleware/auth";
import documentController from "./controllers/Document";

const router = Router();

router.get("/", (req, res) => {
  return res.json({ home: "My homepage" });
});

router.get("/documents", auth.requireJWT, documentController.index);

router.post("/auth/register", auth.register, auth.signJWTForUser);

router.post("/auth", auth.signIn, auth.signJWTForUser);

export default router;

And here's is the auth.ts, where I configured all the passport and JWT functions to refister, sign in and request a JWT token:

import { config } from "dotenv";
config();
import passport from "passport";
import JWT from "jsonwebtoken";
import { Strategy, ExtractJwt } from "passport-jwt";
import { Request, Response, NextFunction } from "express";

import UserModel from "../database/models/User";

interface IRequestUser extends Express.User {
  _id?: string;
  email?: string;
  firstName?: string;
  lastName?: string;
}

const jwtSecret = process.env.JWT_SECRET!;
const jwtAlgorithm = "HS256";
const jwtExpiresIn = "7 days";

passport.use(UserModel.createStrategy());

async function register(req: Request, res: Response, next: NextFunction) {
  try {
    const { email, firstName, lastName, password } = req.body;

    const user = new UserModel({
      email: email,
      firstName: firstName,
      lastName: lastName,
    });

    const registeredUser = await UserModel.register(user, password);

    req.user = registeredUser;

    return next();
  } catch (err) {
    console.log(err.message);
    return next(err);
  }
}

passport.use(
  new Strategy(
    {
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: jwtSecret,
      algorithms: [jwtAlgorithm],
    },
    async (payload, done) => {
      try {
        const foundUser = await UserModel.findById(payload.sub);

        foundUser && done(null, foundUser);

        done(null, false);
      } catch (err) {
        console.log(err.message);
        done(err, false);
      }
    }
  )
);

function signJWTForUser(req: Request, res: Response) {
  if (req.user) {
    const user: IRequestUser = req.user;

    const token = JWT.sign({ email: user.email }, jwtSecret, {
      algorithm: jwtAlgorithm,
      expiresIn: jwtExpiresIn,
      subject: user._id!.toString(),
    });

    return res.json({ token });
  }
}

export default {
  initialize: passport.initialize(),
  register,
  signIn: passport.authenticate("local", { session: false }),
  requireJWT: passport.authenticate("jwt", { session: false }),
  signJWTForUser,
};

What should I improve on my controller and what is causing this error?

Zoey
  • 474
  • 4
  • 17
  • You will have to show us a larger context for this code. In particular, we need to see exactly how it's called from your request handler. This specific error is caused by attempting to send two responses to the same incoming request. This is typically caused by improper handling of asynchronous function calls that cause more than one code path to attempt to send a response. – jfriend00 Jun 25 '20 at 04:34
  • I editted and added a gif of my request and the project repositoty! – Zoey Jun 25 '20 at 04:48
  • Can you please just add the code for the specific request handler that generates this error rather than making us try to find things in your repo. We don't even know what request you're getting this error on. Also, I have no idea what the animated gif has to do with anything here. – jfriend00 Jun 25 '20 at 04:54
  • Uptaded with routes and auth files – Zoey Jun 25 '20 at 04:59
  • I don't know passport well enough to follow the flow here. Hopefully someone else who knows it better can help. I would guess that you're getting an auth error, sending an error response, but still continuing with the request and trying to send another response. – jfriend00 Jun 25 '20 at 05:05
  • i guess the problem is in the passport.authenticate("jwt",{session:false}) because that is the middle ware getting called before doc controller – aravind_reddy Jun 25 '20 at 05:14
  • check if that middleware is returning the response – aravind_reddy Jun 25 '20 at 05:15
  • Does this answer your question? [Error: Can't set headers after they are sent to the client](https://stackoverflow.com/questions/7042340/error-cant-set-headers-after-they-are-sent-to-the-client) – Nimer Awad Jun 25 '20 at 05:27
  • Although this question have the same issue, his problem is different from mine! He was calling the response object twice. My issue could be the same, but I can't find where it's making a double response... – Zoey Jun 25 '20 at 13:45

1 Answers1

2

Your passport strategy setup custom callback is sending two reposes when you find the user.

const foundUser = await UserModel.findById(payload.sub);

foundUser && done(null, foundUser);
done(null, false);    //<<<- HERE

I'm not a Passport.js expert - but you should call this just once as per the documentation.

const foundUser = await UserModel.findById(payload.sub);
done(null, foundUser);
Charlie
  • 22,886
  • 11
  • 59
  • 90
  • I guessed that too but this callback when is called, kill the actual flow contenxt so it's like this function was returned. I tried to make a change returning that function or making a common if statement but fails too. – Zoey Jun 25 '20 at 13:42
  • @Mdsp - That's not true. Calling a function doesn't kill the flow of Javascript (unless it throws an exception). It continues right on after the function call and you call `done()` again. – jfriend00 Jun 25 '20 at 15:07
  • Ok, I explained me in the way, sorry! You're right, I will try to make a return instead – Zoey Jun 25 '20 at 17:12
  • Well, I need to return it to work! It's a simple confusion but on the tutorial wasn't returning and I also found that strange but as I'm trying to learn passport-jwt I didn't changed it. – Zoey Jun 25 '20 at 17:25