0

During development I've been using this mongodb code block to connect to database and it's been working flawlessly, but not when I deploy to Azure.

mongoose
  .connect(
    "mongodb://" +
      process.env.COSMOSDB_HOST +
      ":" +
      process.env.COSMOSDB_PORT +
      "/" +
      process.env.COSMOSDB_DBNAME +
      "?ssl=true&replicaSet=globaldb",
    {
      auth: {
        user: process.env.COSMOSDB_USER,
        password: process.env.COSMOSDB_PASSWORD
      }
    }
  )
  .then(() => console.log("Connection to CosmosDB successful"))
  .catch(err => console.error(err));

I was having trouble getting it to connect when deploying my app to azure so I was given a different mongodb code block to use from azure support

var mongoDBurl = process.env.MONGODB_CONNECTION;
mongoose.connect(mongoDBurl, { useNewUrlParser: true });

var db = mongoose.connection;

db.on('error', console.error.bind(console, 'MongoDB connection error:'));

db.once('open', (callback) => {
    console.log('MongoDB connectted!!');
});


, but when I use this mongodb code instead of the original, then I get Cannot set headers after they are sent to the client when trying to login to my app. Why is that?

login api

router.post("/login", (req, res, next) => {
  let fetchedUser;
  let username;
  User.findOne({ email: req.body.email })
    .then(user => {
      if (!user) {
        return res.status(401).json({
          message: "Auth failed"
        });
      }

      fetchedUser = user;
      username= user.username;
      return bcrypt.compare(req.body.password, user.password);
    })
    .then(result => {
      if (!result) {
        return res.status(401).json({
          message: "Auth failed"
        });
      }
      const token = jwt.sign(
        {
          email: fetchedUser.email,
          userId: fetchedUser._id,
          username: username
        },
        "secret_this_should_be_longer",
        { expiresIn: "1h" }
      );

      res.status(200).json({
        token: token,
        expiresIn: 3600,
        userId: fetchedUser._id,
        username: username
      });
    })
    .catch(err => {
      return res.status(401).json({
        message: "Auth failed"
      });
    });
});

1 Answer 1

3

Reason

That's because the return statement in your promise doesn't end your then's queue. You are responding 401 if you don't find a User, then on the second then, you respond 401 again if there is no result.

Express is telling you that you can't send headers twice, which is true. I think there's nothing MongoDB related issue in your code.

Tip: Try converting your route into try/catch/await code to get a better comprehension of what's going on

Possible solution

Edit: this should work

// You don't need next as you're ending the request here and not calling it anyway
router.post("/login", async (req, res) => {
  const user = await User.findOne({ email: req.body.email });

  if (!user) {
    return res.status(401).json({
      message: "Auth failed"
    });
  }

  const { username } = user;

  const goodPassword = bcrypt.compareSync(req.body.password, user.password);

  if (!goodPassword) {
    return res.status(401).json({
      message: "Auth failed"
    });
  }

  const token = jwt.sign( // Double check if this call is synchronous or not (needs await or not)
    {
      email: user.email,
      userId: user._id,
      username: username
    },
    "secret_this_should_be_longer",
    { expiresIn: "1h" }
  );

  // Maybe store the token in the cookies instead of sending back, as you wish !

  res.status(200).json({
    token: token,
    expiresIn: 3600,
    userId: user._id,
    username: username
  });
});

Note that using await's helps you understand your code !

MongoDB Authentication

Concerning your MongoDB Authentication, you need to use the same credentials as in the first method

const { COSMOSDB_HOST, COSMOSDB_PORT, COSMOSDB_DBNAME, COSMOSDB_USER, COSMOSDB_PASSWORD } = process.env;

mongoose.connect(`mongodb://${COSMOSDB_HOST}:${COSMOSDB_PORT}/${COSMOSDB_DBNAME}?ssl=true&replicaSet=globaldb`,
  {
    auth: {
      user: COSMOSDB_USER,
      password: COSMOSDB_PASSWORD,
    },
  },
);

const db = mongoose.connection;

db.on('error', console.error.bind(console, 'MongoDB connection error:'));

db.once('open', (callback) => {
    console.log('MongoDB connected!!');
});

Sign up to request clarification or add additional context in comments.

5 Comments

Your code works flawless with my old mongodb connection code. Thanks for the demonstration, (async is something I'm trying to work on getting better at) but when I try to login with new mongodb connection code, I get this error: pastebin.com/xyKe5VG0 So it's definitely the mongodb connection code that's the problem
Ok the problem is still not MongoDB, I destructure the User object even if it doesn't exist, you need to access username only after checking if the User is not null. I edited my answer.
When using your updated change. I don't get that error anymore, but also const user is returning null const user = await User.findOne({ email: req.body.email }); console.log(user) return; when connecting to new mongodb code. (returns user successfully with old connection code)
Your mongo.connect is not using the same credentials as your first way, take a look at my edit on my answer
I got it working. I think it was also related to not putting outbound IP addresses from app service properties to cosmosdb Virtual Networks, which was set to selected networks. I added them and now I can login. Thanks for your help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.