在同一域上使用 firebase 函数的 oAuth 的跨域状态 cookie 问题

2024-02-16

我正在为 firebase 平台的用户实现 oAuth 登录。

一切正常,除非用户has 禁用跨域 cookie。

这就是我所做的。

  1. 从我的域/应用程序,用户被重定向到云功能。
  2. can 函数设置statecookie 并将用户重定向到 oAuth 提供商。
  3. 用户登录到 oAuth 提供商并被重定向回另一个函数以获取代码等。这就是问题所在

在上面的步骤 3 中,如果用户在浏览器中禁用了跨域方 cookie,则该函数无法读取任何 cookie。 这两个函数都位于同一域中,如下面的屏幕截图所示。

有什么办法可以解决这个问题吗?我的方法有问题吗?

我不明白为什么这两个函数被视为跨域。

更新以包含更多信息

要求:

Request URL: https://europe-west2-quantified-self-io.cloudfunctions.net/authRedirect
Request Method: GET
Status Code: 302 
Remote Address: [2a00:1450:4007:811::200e]:443
Referrer Policy: no-referrer-when-downgrade

请求标头

:authority: europe-west2-quantified-self-io.cloudfunctions.net
:method: GET
:path: /authRedirect
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
accept-encoding: gzip, deflate, br
accept-language: en-GB,en-US;q=0.9,en;q=0.8
cookie: signInWithService=false; state=877798d3672e7d6fa9588b03f1e26794f4ede3a0
dnt: 1
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36

响应头

alt-svc: quic=":443"; ma=2592000; v="46,43,39"
cache-control: private
content-encoding: gzip
content-length: 218
content-type: text/html; charset=utf-8
date: Sat, 03 Aug 2019 08:55:18 GMT
function-execution-id: c8rjc7xnvoy8
location: https://cloudapi-oauth.suunto.com/oauth/authorize?response_type=code&client_id=xxx&redirect_uri=&scope=workout&state=1c8073866d1ffaacf2d4709090ad099872718afa
server: Google Frontend
set-cookie: state=1c8073866d1ffaacf2d4709090ad099872718afa; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure
set-cookie: signInWithService=false; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure
status: 302
vary: Accept
x-cloud-trace-context: 99a93680a17770f848f200a9e729b122;o=1
x-powered-by: Express

之后,一旦用户从服务返回,他就根据解析 cookie 的代码(或处理它的函数)进行身份验证:

export const authToken = functions.region('europe-west2').https.onRequest(async (req, res) => {
  const oauth2 = suuntoAppAuth();
  cookieParser()(req, res, async () => {
    try {
      const currentDate = new Date();
      const signInWithService = req.cookies.signInWithService === 'true';
      console.log('Should sign in:', signInWithService);
      console.log('Received verification state:', req.cookies.state);
      console.log('Received state:', req.query.state);
      if (!req.cookies.state) {
        throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
      } else if (req.cookies.state !== req.query.state) {
        throw new Error('State validation failed');
      }
      console.log('Received auth code:', req.query.code);
      const results = await oauth2.authorizationCode.getToken({
        code: req.query.code,
        redirect_uri: determineRedirectURI(req), // @todo fix,
      });

      // console.log('Auth code exchange result received:', results);

      // We have an access token and the user identity now.
      const accessToken = results.access_token;
      const suuntoAppUserName = results.user;

      // Create a Firebase account and get the Custom Auth Token.
      let firebaseToken;
      if (signInWithService) {
        firebaseToken = await createFirebaseAccount(suuntoAppUserName, accessToken);
      }
      return res.jsonp({
        firebaseAuthToken: firebaseToken,
        serviceAuthResponse: <ServiceTokenInterface>{
          accessToken: results.access_token,
          refreshToken: results.refresh_token,
          tokenType: results.token_type,
          expiresAt: currentDate.getTime() + (results.expires_in * 1000),
          scope: results.scope,
          userName: results.user,
          dateCreated: currentDate.getTime(),
          dateRefreshed: currentDate.getTime(),
        },
        serviceName: ServiceNames.SuuntoApp
      });
    } catch (error) {
      return res.jsonp({
        error: error.toString(),
      });
    }
  });
});

上面的代码没有找到具有该名称的cookiestate

所以这里失败了

if (!req.cookies.state) {
        throw new Error('State cookie not set or expired. Maybe you took too long to authorize. Please try again.');
      } else if (req.cookies.state !== req.query.state) {
        throw new Error('State validation failed');
      }

在这里进行了更多搜索,获得了更多信息。

我基于的例子https://github.com/firebase/functions-samples/tree/master/instagram-auth https://github.com/firebase/functions-samples/tree/master/instagram-auth

看起来其他用户也遇到了同样的问题https://github.com/firebase/functions-samples/issues/569 https://github.com/firebase/functions-samples/issues/569

我也打开了这个问题https://github.com/firebase/firebase-functions/issues/544 https://github.com/firebase/firebase-functions/issues/544


您的响应显示了 Set-Cookie 标头state and signInWithService没有 a 的 cookiedomain属性:

set-cookie: state=1c8073866d1ffaacf2d4709090ad099872718afa; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure
set-cookie: signInWithService=false; Max-Age=3600; Path=/; Expires=Sat, 03 Aug 2019 09:55:18 GMT; HttpOnly; Secure

没有域的 Set-Cookie 意味着 cookie 在返回服务器的过程中发生的情况取决于浏览器。 “默认”、符合规范的行为:浏览器将获取服务 URL 的 FQDN 并将其与 cookie 关联。 RFC6265:

除非 cookie 的属性另有说明,否则 cookie 是 仅返回到原始服务器(例如,不返回到任何 subdomains)...如果服务器省略了 Domain 属性,则用户代理 只会将 cookie 返回到源服务器。

当浏览器决定是否accept来自 HTTP 服务的 cookie,决策标准之一是 cookie 是否是第一方 or 第三者:

  • 第一方 cookie:如果您请求的资源(网页)触发了对europe-west2-quantified-self-io.cloudfunctions.net/authRedirect驻留在https://europe-west2-quantified-self-io.cloudfunctions.net/...
  • 第三方 cookie:如果您请求的资源(网页)触发了对europe-west2-quantified-self-io.cloudfunctions.net/authRedirect驻留在https://some.domain.app.com/...

在您的情况下,您的“父”应用程序/页面的 FQDN 可能与europe-west2-quantified-self-io.cloudfunctions.net,因此这些 cookie 被标记为第三方。正如您所发现的,用户可以选择阻止第三方 cookie。自 2019 年 8 月起,Firefox 和 Safari 默认阻止第 3 方 cookie。大多数(如果不是全部)广告拦截器和类似的扩展程序也会阻止它们。这将导致浏览器简单地忽略 HTTP 响应中的 Set-Cookie 标头europe-west2-quantified-self-io.cloudfunctions.net/authRedirect。 cookie 不会被发送回第二个 Firebase 函数europe-west2-quantified-self-io.cloudfunctions.net/authToken因为客户端上不存在。

您的选择:

  1. 在同一域中托管您的应用和 Firebase 功能。
  2. 所有 HTTP 请求(应用程序和 Firebase 功能)都流经应用程序的架构;后者充当函数调用的某种代理。这是one way https://stackoverflow.com/questions/54788230/firebase-functions-custom-domain-with-single-page-app在 Firebase 中执行此操作。
  3. 假设您的应用和 Firebase 函数确实驻留在不同的域中。在 Javascript 中,您可以创建一小段中间件来调用/authRedirectFB 函数,解析响应(包括通过 Set-Cookie 标头的 cookie),然后通过以下方式将响应(包括 cookie)写回浏览器document.cookie。在这种情况下,cookie 是第一方的。
  4. 根本不要使用cookie。您正在执行的 oAuth 授权授予流程cloudapi-oauth.suunto.com因为授权服务器不需要cookie。您关注了Instagram 身份验证 https://github.com/firebase/functions-samples/tree/master/instagram-auth推荐此流程的示例

单击“使用 Instagram 登录”按钮时,会显示一个弹出窗口,其中 将用户重定向到重定向功能 URL。

然后,重定向功能将用户重定向到 Instagram OAuth 2.0 同意屏幕(仅限第一次)用户必须授予批准。还有statecookie 在客户端上设置为 的值state稍后检查的 URL 查询参数。

支票反对state查询参数基于实施最佳实践 https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-13#section-3.1对于 oAuth 客户端,当授权服务器不支持 PKCE 扩展时(cloudapi-oauth.suunto.com不支持):

客户端必须防止 CSRF。一次性使用携带的CSRF token “state”参数,安全地绑定到用户代理,应该 用于该目的。如果客户端使用 PKCE [RFC7636] 并且 授权服务器支持 PKCE,客户端可以选择不使用 CSRF 保护的“状态”,因为这种保护是由 PKCE 提供的。 在这种情况下,“状态”可以再次用于其原始目的, 即传输有关客户端应用程序状态的数据

关键短语是安全地绑定到用户代理。对于网络应用程序来说,cookie 是实现此绑定的一个不错的选择,但它不是唯一的选择。你可以坚持的价值state在本地或会话存储中,单页应用程序在实践中正是这样做的。如果你想生活在云端,你可以坚持state在云存储或同等设备中...但您必须创建一个唯一标识您的客户端的密钥and这个特定的 HTTP 请求。并非不可能,但对于一个简单的场景来说可能有点过分了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在同一域上使用 firebase 函数的 oAuth 的跨域状态 cookie 问题 的相关文章

随机推荐