在同一域上的站点之间共享cookie - 无头/解耦CMS

All*_*ney 7 cookies wordpress express reactjs axios

我挑战的背景

我正在建立一个无头的WordPress/WooCommerce商店.

如果您不熟悉无头CMS的概念,我会通过WordPress/WooCommerce REST API提取商店的内容(产品及其图像,文本).通过这种方式,我可以为我的客户提供CMS仪表板,同时我可以使用现代语言/库进行开发 - 就我而言 - React!

如果可能的话,我想在WordPress/WooCommerce/PHP中保存结账.根据项目,我应用此代码/样板文件,我怀疑我必须切断和更改支付网关,并且在PHP/WordPress中使这种安全和PCI兼容性要容易得多 - 这里有很多插件.

这意味着整个商店/前端将存在于React中,但购物车除外,当用户希望完成订单时,用户将被重定向到CMS前端(WordPress,PHP).

挑战

这使得管理会话的cookie变得非常不直观和非正统.当用户从商店(React站点)重定向到结帐(WooCommerce/PHP站点)时,购物车会话必须在两个站点之间保持不变.

此外,对WooCommerce的请求通过我的React客户端所在的Node/Express服务器进行路由.我这样做是因为我想让WordPress地址变得模糊,所以我可以应用GraphQL来清理我的请求和响应.这个问题是在这个过程中,cookie丢失了,因为我的客户端和我的CMS正在通过中间人(我的节点服务器)进行通信 - 我需要额外的逻辑来手动管理我的cookie.

解决方案建筑学的粗略草图

代码

当我尝试从一个动作创建者(我使用Redux进行状态管理)向购物车添加内容时,我点击了我的Node/Express服务器上的api对应端点:

export const addToCart = (productId, quantity) => async (dispatch) => {
  dispatch({type: ADD_TO_CART});
  try {
    // Manually append cookies somewhere here
    const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
      withCredentials: true
    });
    dispatch(addToSuccess(payload));
  } catch (error) {
    dispatch(addToCartFailure(error));
  }
};
Run Code Online (Sandbox Code Playgroud)

然后在Node/Express服务器上,我向WooCommerce发出请求:

app.get('/api/addtocart', async (req, res) => {
    try {
      // Manually retrieve & append cookies somewhere here
      const productId = parseInt(req.query.productId);
      const quantity = parseInt(req.query.quantity);
      const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
        product_id: productId,
        quantity
      });
      return res.json(response.data);
    } catch (error) {
      // Handle error
      return res.json(error);
    }
});
Run Code Online (Sandbox Code Playgroud)

All*_*ney 5

根据@TarunLalwani(感谢一百万!)在他的评论中给出的线索,我已经设法制定了一个解决方案。

Cookie 域设置

由于我正在使用两个不同的站点,为了使其工作,我必须确保它们都在同一个域中,并且该域已在所有 cookie 中设置。这确保了 cookie 包含在我在 Node/Express 服务器(坐在例如somedomain.com)和 WooCommerce CMS(坐在例如wp.somedomain.com)之间的请求中,而不是wp.somedomain子域独有。这是通过define( 'COOKIE_DOMAIN', 'somedomain.com' );在我wp-config.php的 CMS 上设置来实现的。

手动获取和设置 Cookie

我的代码需要重要的额外逻辑,以便在请求通过我的 Node / Express 服务器通过客户端路由时包含 cookie。

在 React 中,我必须检查 cookie 是否存在,如果存在,我必须在我的 GET 请求的标头中将其发送到 Node/Express 服务器。

import Cookies from 'js-cookie';

export const getSessionData = () => {
  // WooCommerce session cookies are appended with a random hash.
  // Here I am tracking down the key of the session cookie.
  const cookies = Cookies.get();
  if (cookies) {
    const cookieKeys = Object.keys(cookies);
    for (const key of cookieKeys) {
      if (key.includes('wp_woocommerce_session_')) {
        return `${key}=${Cookies.get(key)};`;
      }
    }
  }
  return false;
};

export const addToCart = (productId, quantity) => async (dispatch) => {
  dispatch({type: ADD_TO_CART});
  const sessionData = getSessionData();
  const config = {};
  if (sessionData) config['session-data'] = sessionData;
  console.log('config', config);
  try {
    const payload = await axios.get(`${ROOT_API}/addtocart?productId=${productId}&quantity=${quantity}`, {
      withCredentials: true,
      headers: config
    });
    dispatch(addToSuccess(payload));
  } catch (error) {
    dispatch(addToCartFailure(error));
  }
};
Run Code Online (Sandbox Code Playgroud)

在 Node / Express 服务器上,我必须检查是否包含来自客户端的 cookie(req.headers与密钥一起保存session-data-Cookie在此处用作密钥是非法的),如果我这样做了,请将其附加到我的请求的标题中去我的 CMS。

如果我没有找到附加的 cookie,这意味着这是会话中的第一个请求,所以我必须从我从 CMS 返回的响应中手动获取 cookie 并将其保存到客户端 ( setCookieFunc)。

app.get('/api/addtocart', async (req, res) => {
  try {
    const productId = parseInt(req.query.productId);
    const quantity = parseInt(req.query.quantity);
    const sessionData = req.headers['session-data'];
    const headers = {};
    if (sessionData) headers.Cookie = sessionData;
    const response = await axios.post(`${WP_API}/wc/v2/cart/add`, {
      product_id: productId,
      quantity
    }, { headers });
    if (!sessionData) {
      const cookies = response.headers['set-cookie'];
      const setCookieFunc = (cookie) => {
        const [cookieKeyValue, ...cookieOptionsArr] = cookie.split('; ');
        const cookieKey = cookieKeyValue.split('=')[0];
        const cookieValue = decodeURIComponent(cookieKeyValue.split('=')[1]);
        const cookieOptions = { };
        cookieOptionsArr.forEach(option => (cookieOptions[option.split('=')[0]] = option.split('=')[1]));
        if (cookieOptions.expires) {
          const expires = new Date(cookieOptions.expires);
          cookieOptions.expires = expires;
        }
        res.cookie(cookieKey, cookieValue, cookieOptions);
      };
      cookies.map(cookie => setCookieFunc(cookie));
    }
    return res.json(response.data);
  } catch (error) {
    // Handle error
    return res.json(error);
  }
});
Run Code Online (Sandbox Code Playgroud)

我不确定这是否是该问题最优雅的解决方案,但它对我有用。


笔记

我使用js-cookie库与我的 React 客户端上的 cookie进行交互。


陷阱

如果您试图在您的开发环境(使用 localhost)中进行这项工作,则需要完成一些额外的工作。使用显式域查看本地主机上的 Cookie