Heroku 上的 Apollo Express 服务器和移动浏览器上的刷新令牌 Cookie

Apollo Express Server on Heroku and Refresh Token Cookie on Mobile Browser

visiting/refresh 后,应用会检查 cookie 中的刷新令牌。如果存在有效的访问令牌,Apollo Express 服务器将提供一个访问令牌。这在我的桌面上运行良好,但在 iPhone 上使用 Chrome 或 Safari 时,每次刷新时用户都会被发送到登录页面。

React 应用与 Apollo 客户端

 useEffect(() => {
    fetchUser();
  }, []);

  const fetchUser = async () => {
    const res = await fetch('https://website.com/token', {
      method: 'POST',
      credentials: 'include',
    });
    const { accessToken } = await res.json();
    if (accessToken === '') {
      setIsLoggedIn(false);
    }
    setAccessToken(accessToken);
    setLoading(false);
  };

Apollo 客户端还会检查访问令牌是否有效

const authLink = setContext((_, { headers }) => {
  const token = getAccessToken();

  if (token) {
    const { exp } = jwtDecode(token);

    if (Date.now() <= exp * 1000) {
      return {
        headers: {
          ...headers,
          authorization: token ? `Bearer ${token}` : '',
        },
      };
    }
  }
  fetch('https://website.com/token', {
    method: 'POST',
    credentials: 'include',
  }).then(async (res) => {
    const { accessToken } = await res.json();
    setAccessToken(accessToken);
    return {
      headers: {
        ...headers,
        authorization: accessToken ? `Bearer ${accessToken}` : '',
      },
    };
  });
});

const client = new ApolloClient({
  link: from([authLink.concat(httpLink)]),
  cache: new InMemoryCache(),
  connectToDevTools: true,
});

它处理 Express 服务器上的令牌 link

app.use('/token', cookieParser());
    app.post('/token', async (req, res) => {
      const token = req.cookies.rt;

      if (!token) {
        return res.send({ ok: false, accessToken: '' });
      }

      const user = await getUser(token);

      if (!user) {
        return res.send({ ok: false, accessToken: '' });
      }

      sendRefreshToken(res, createRefreshToken(user));

      return res.send({ ok: true, accessToken: createAccessToken(user) });
    });

以及 cookie 的设置

export const sendRefreshToken = (res, token) => {
  res.cookie('rt', token, {
    httpOnly: true,
    path: '/token',
    sameSite: 'none',
    secure: true,
  });
};

与前端在 Netlify 上的网站 'none' 相同。

经过一天的摆弄和研究,我找到了问题所在,以及使用自定义域时的一个解决方案。

问题是 iOS 将 sameSite 'none' 视为 sameSite 'strict'。我认为 iOS Chrome 会与 Safari 不同,但它似乎不同。

如果您使用托管在 Netlify 上的前端,您自​​然会拥有与 Heroku 应用程序后端不同的域。由于我使用的是自定义域,而且 Netlify 提供了免费的 SSL,所以工作已经完成了一半。

设置 httpOnly cookie 的唯一方法是将 cookie 设置为安全。下一步是将 sameSite 设置为 'none' 但如上所述,这不适用于 iOS.

设置 cookie 的域 属性 也不起作用,因为域 属性 涉及 cookie 的范围而不是 cookie 来源。如果 cookie 来自不同的域(Heroku 后端),那么前端(在 Netlify 上)将无法使用它。

默认情况下,在 Heroku 上,免费的 dyno 会给你一个像 'your-app.herokuapp.com' 这样的域名,这很棒,因为它还包括免费的 SSL。但是,为了让 cookie 起作用,我添加了我在 Netlify 中使用的自定义域。需要明确的是,Netlify 已经使用了我的顶级自定义域,因此我向 Heroku 添加了一个子域 (api.domain.com)。 Cookie 确实适用于具有 sameSite 'strict'.

的同一域和子域

最后一个问题是 Heroku 的自定义域不会自动获得 SSL,这就是为什么我认为值得升级到每月 7 美元的业余爱好测功机以避免手动管理 SSL。我认为这是使用自定义域时唯一的解决方案。

另一方面,对于那些有同样问题并希望获得免费解决方案的人,您可以放弃使用自定义域并将静态前端与后端托管在 Heroku 上。

希望这能为单独部署后端和前端的任何人节省一些时间。