admin管理员组

文章数量:1289877

So we have an random issue where every now and then the JWT token which we store in cookies for authentication is not set in the browser. Now 99% of the time a user goes to the login web page enter their details it sends a request to the server which sends back their user details and a JWT token set into the cookies. Now every now and then the cookie does not seem to be set. Its random has happened on nearly all browsers now but with no reason as to why. It happens in both our local, staging and production environments. (I have removed some code for privacy reasons)

The backend auth service is built using Node and ExpressJS it set the token with the following code:

module.exports.signIn = async function(req, res, next) {
  try {
    const { email, password } = req.body;
    if (!email || !password)
      throwBadRequest("Please enter a valid email and password");

    const data = await Users.get(`?email=${email.toLowerCase().trim()}`);

    const { name, val, options } = await Token.generateCookieParams(data);
    res.cookie(name, val, options);

    return res.json(toDTO(data));
  } catch (err) {
    next(err)
  }
};

We are using the middleware cookie parser if that helps. Here is the code that set the token:

async function generateFor(user, expireTime, special = null) {
    const payload = { id: user._id, type: user.type, account: user.account };
    if (user.entity) {
      payload.entity = user.entity;
    }
    if (special) {
      payload.special = special;
    }
    const token = await jwt.sign(payload, config.secret, {
      expiresIn: expireTime
    });
    return token;
  }

async function generateCookieParams(user) {
    const expireTime = 60 * 60 * 12; // 12 hour
    const token = await Token.generateFor(user, expireTime);
    return { name: config.tokenKey, val: token, options: { httpOnly: true } };
  }

We are using the middleware cors for managing cors in the express app and have the option credentials set to true.

Then in the front end, we are using superagent to make all the request from the react app, we also used Axios as well but have the same issues. The base code for the networking looks like this in the front end:

import superagent from "superagent";

const superagentManager = {};

/**
 * POST
 * @param {string} path => the path for the post request
 * @param {object} data => the object you are posting in json format
 */
superagentManager.post = async (path, data) => {
  return await superagent
    .post(path)
    .withCredentials()
    .type("application/json")
    .send(data);
};

/**
 * GET
 * @param {string} path => the path for the get request
 */
superagentManager.get = async path => {
  return await superagent
    .get(path)
    .withCredentials()
    .type("application/json");
};

/**
 * PATCH
 * @param {string} path => the path for the patch request
 * @param {object} data => the object you are posting in json format
 */
superagentManager.patch = async (path, data) => {
  return await superagent
    .patch(path)
    .withCredentials()
    .type("application/json")
    .send(data);
};

/**
 * DELETE
 * @param {string} path => the path for the delete request
 */
superagentManager.delete = async path => {
  return await superagent
    .delete(path)
    .withCredentials()
    .type("application/json");
};

export default superagentManager;

If anyone could help me it would be much appreciated. The system works but every now and then let's say 1 out of every 50 logins it doesn't set the token in the browser. So the user object is returned from the login request but further request that happens straight afterwards throw an error as there is no token in the cookie. With the user base growing the bug is being more and more noticeable.

So we have an random issue where every now and then the JWT token which we store in cookies for authentication is not set in the browser. Now 99% of the time a user goes to the login web page enter their details it sends a request to the server which sends back their user details and a JWT token set into the cookies. Now every now and then the cookie does not seem to be set. Its random has happened on nearly all browsers now but with no reason as to why. It happens in both our local, staging and production environments. (I have removed some code for privacy reasons)

The backend auth service is built using Node and ExpressJS it set the token with the following code:

module.exports.signIn = async function(req, res, next) {
  try {
    const { email, password } = req.body;
    if (!email || !password)
      throwBadRequest("Please enter a valid email and password");

    const data = await Users.get(`?email=${email.toLowerCase().trim()}`);

    const { name, val, options } = await Token.generateCookieParams(data);
    res.cookie(name, val, options);

    return res.json(toDTO(data));
  } catch (err) {
    next(err)
  }
};

We are using the middleware cookie parser if that helps. Here is the code that set the token:

async function generateFor(user, expireTime, special = null) {
    const payload = { id: user._id, type: user.type, account: user.account };
    if (user.entity) {
      payload.entity = user.entity;
    }
    if (special) {
      payload.special = special;
    }
    const token = await jwt.sign(payload, config.secret, {
      expiresIn: expireTime
    });
    return token;
  }

async function generateCookieParams(user) {
    const expireTime = 60 * 60 * 12; // 12 hour
    const token = await Token.generateFor(user, expireTime);
    return { name: config.tokenKey, val: token, options: { httpOnly: true } };
  }

We are using the middleware cors for managing cors in the express app and have the option credentials set to true.

Then in the front end, we are using superagent to make all the request from the react app, we also used Axios as well but have the same issues. The base code for the networking looks like this in the front end:

import superagent from "superagent";

const superagentManager = {};

/**
 * POST
 * @param {string} path => the path for the post request
 * @param {object} data => the object you are posting in json format
 */
superagentManager.post = async (path, data) => {
  return await superagent
    .post(path)
    .withCredentials()
    .type("application/json")
    .send(data);
};

/**
 * GET
 * @param {string} path => the path for the get request
 */
superagentManager.get = async path => {
  return await superagent
    .get(path)
    .withCredentials()
    .type("application/json");
};

/**
 * PATCH
 * @param {string} path => the path for the patch request
 * @param {object} data => the object you are posting in json format
 */
superagentManager.patch = async (path, data) => {
  return await superagent
    .patch(path)
    .withCredentials()
    .type("application/json")
    .send(data);
};

/**
 * DELETE
 * @param {string} path => the path for the delete request
 */
superagentManager.delete = async path => {
  return await superagent
    .delete(path)
    .withCredentials()
    .type("application/json");
};

export default superagentManager;

If anyone could help me it would be much appreciated. The system works but every now and then let's say 1 out of every 50 logins it doesn't set the token in the browser. So the user object is returned from the login request but further request that happens straight afterwards throw an error as there is no token in the cookie. With the user base growing the bug is being more and more noticeable.

Share Improve this question asked Mar 29, 2020 at 9:45 AlexanderKaranAlexanderKaran 5251 gold badge6 silver badges18 bronze badges 4
  • What is jwt in your code? Just wondering if jwt.sign is swallowing some errors that returning an empty value in some cases. – Jacob Commented Apr 2, 2020 at 3:49
  • Looks like you're maybe using jsonwebtoken? If so, what version? – Jacob Commented Apr 2, 2020 at 3:56
  • Have you looked at the network tab to see if the api call responded with the cookie or not? You need observe what is different when the login doesn't happen – Tarun Lalwani Commented Apr 2, 2020 at 6:17
  • Yes the cookie does e back to the browser, I can see it in the network tab. – AlexanderKaran Commented Apr 2, 2020 at 23:59
Add a ment  | 

3 Answers 3

Reset to default 5 +50

This looks like a cookie issue!

So there are two ways of keeping states in the browser with cookies. Session data is stored on the server and typically use some key to retrieve values related to user states. Cookies are stored client side and are sent in requests to determine user states as well. ExpressJS supports the ability to use both of these. For JWT you want to use the cookie approach of course!

Let's start by taking a look at your cookie options:

// return { name: config.tokenKey, val: token, options: { httpOnly: true } };

const cookieOptions = {
    httpOnly: true
}

So far this looks good. You're following best practices about storing tokens as http only, but to get the cookie stored correctly you may need to add some more to the cookie options.

Here is a link to cookie options in the express docs. Check out the description for "expires":

Expiry date of the cookie in GMT. If not specified or set to 0, creates a session cookie.

Essentially what is happening is that you are not specifying an expiry so your cookie is set as a session cookie. What that means is that the cookie is being destroyed whenever a user closes the browser.

Bonus:

  • If your site uses HTTPS make sure to set the cookie as secure: true

  • You may want to check out the sameSite attribute as well if it applies to your team.

I don't see anything wrong with your code, but some things to look out for that have bit me in the past;

  • ensure your client doesn't start the next call when the login call is still happening. Also ensure correct errorhandling to show the client the login failed.
  • if you have a proxy (like nginx, haproxy) ensure the vary header is configured correctly
  • ensure there is no caching happening on the server AND in the browser by configuring the no-cache and max-age headers

This worked for me.

  • Check in browser if cookie is set.
  • Make sure that front allows to send cookies. When you make http request set credentials to "include".

Here is an example:

const response = await fetch(`${config.backendUrl}/tasks`, {

credentials: "include", // include credentials to send cookies

});

本文标签: javascriptToken not being set in cookiesStack Overflow