Skip to content
Advertisement

HttpOnly Cookies not found in Web Inspector

I am working on user authentication for a website built using the MERN stack and I have decided to use JWT tokens stored as HttpOnly cookies. The cookie was sent in a “Set-Cookie” field in response header when I used Postman to make the request but not in the Safari Web Inspector as shown in the image below. There are no cookies found in the storage tab either.

enter image description here

I have simplified my React login form to a button that submits the username and password of the user for the sake of debugging

import React from "react";

const sendRequest = async (event) => {
  event.preventDefault();
  let response;

  try {
    response = await fetch("http://localhost:5000/api/user/login", {
      method: "POST",
      body: { username: "Joshua", password: "qwerty" },
      mode: "cors",
      // include cookies/ authorization headers
      credentials: "include",
    });
  } catch (err) {
    console.log(err);
  }
  if (response) {
    const responseData = await response.json();
    console.log(responseData);
  }
};

const test = () => {
  return (
    <div>
      <input type="button" onClick={sendRequest} value="send" />
    </div>
  );
};

export default test;

I am using express on the backend and this is my index.js where all incoming requests are first received

const app = express();

app.use(bodyParser.json());

app.use("/images", express.static("images"));
app.use((req, res, next) => {
  res.set({
    "Access-Control-Allow-Origin": req.headers.origin,
    "Access-Control-Allow-Credentials": "true",
    "Access-Control-Allow-Headers": "Content-Type, *",
    "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE",
  });

  next();
});

app.use(cookieParser());

// api requests for user info/ login/signup
app.use("/api/user", userRoutes);

This is the middleware that the login request is eventually directed to

const login = async (req, res, next) => {
  const { username, password } = req.body;
  let existingUser;
  let validCredentials;
  let userID;
  let accessToken;

  try {
    existingUser = await User.findOne({ username });
  } catch (err) {
    return next(new DatabaseError(err.message));
  }

  // if user cannot be found -> username is wrong
  if (!existingUser) {
    validCredentials = false;
  } else {
    let isValidPassword = false;
    try {
      isValidPassword = await bcrypt.compare(password, existingUser.password);
    } catch (err) {
      return next(new DatabaseError(err.message));
    }

    // if password is wrong
    if (!isValidPassword) {
      validCredentials = false;
    } else {
      try {
        await existingUser.save();
      } catch (err) {
        return next(new DatabaseError(err.message));
      }

      userID = existingUser.id;
      validCredentials = true;

      accessToken = jwt.sign({ userID }, SECRET_JWT_HASH);
      res.cookie("access_token", accessToken, {
        maxAge: 3600,
        httpOnly: true,
      });
    }
  }

  res.json({ validCredentials });
};

Extra information

In the login middleware, a validCredentials boolean is set and returned to the client. I was able to retrieve this value on the front end hence I do not think it is a CORS error. Furthermore, no errors were thrown and all other API requests on my web page that do not involve cookies work fine as well.

Another interesting thing is that despite using the same data (A JS object containing {username:”Joshua”, password:”qwerty”}) for both Postman and the React code, validCredentials evaluates to true in Postman and false in the Web Inspector. It is an existing document in my database and I would expect the value returned to be true, which was the case before I added cookies

May I know what I have done wrong or do you have any suggestions on how I can resolve this issue? I am a beginner at web-development

EDIT

With dave’s answer I can receive the “Set-Cookie” header on the frontend. However it does not appear in the Storage tab in the web inspector for some reason.

This is the response header

enter image description here

This is the Storage tab where cookies from the site usually appears

enter image description here

Answer

If you’re trying to send the request as json, you need to set the content type header, and JSON.stringify the object:

response = await fetch("http://localhost:5000/api/user/login", {
  method: "POST",
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ username: "Joshua", password: "qwerty" }),
  mode: "cors",
  // include cookies/ authorization headers
  credentials: "include",
});

Right now you’re probably getting the equivalent of

existingUser = User.findOne({ username: undefined})

and so when you do:

if (!existingUser) {
    validCredentials = false;
} else { /* ... */ }

you get the validCredentials = false block, and the cookie is set in the other block.

Advertisement