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

Jim*_*ane 6 oauth cross-domain firebase google-cloud-functions

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

除非用户 禁用跨域cookie,否则所有方法都工作正常

这是我所做的。

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

如果用户已从其浏览器禁用跨域方Cookie,则在上面的步骤3中,该功能无法读取任何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
Run Code Online (Sandbox Code Playgroud)

请求标题

: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
Run Code Online (Sandbox Code Playgroud)

响应标题

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
Run Code Online (Sandbox Code Playgroud)

之后,一旦用户从服务中返回,他就针对解析cookie的代码(或处理该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(),
      });
    }
  });
});
Run Code Online (Sandbox Code Playgroud)

上面的代码找不到名称为的Cookie 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');
      }
Run Code Online (Sandbox Code Playgroud)

在这里进行更多搜索是更多信息。

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

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

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

ide*_*ral 4

您的响应显示了 Set-Cookie 标头statesignInWithService没有属性的 cookie domain

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
Run Code Online (Sandbox Code Playgroud)

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

除非 cookie 的属性另有指示,否则 cookie 仅返回到源服务器(而不返回到任何子域)...如果服务器省略 Domain 属性,则用户代理将仅将 cookie 返回到源服务器。

当浏览器决定是否接受来自 HTTP 服务的 cookie 时,决策标准之一是 cookie 是第一方还是第三方

  • 第一方 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 功能)都流经应用程序的架构;后者充当函数调用的某种代理。这是在 Firebase 中执行此操作的一种方法。
  3. 假设您的应用和 Firebase 函数确实驻留在不同的域中。在 Javascript 中,您可以创建一小段中间件来调用/authRedirectFB 函数,解析响应(包括通过 Set-Cookie 标头的 cookie),然后通过 .txt 将响应(包括 cookie)写回浏览器document.cookie。在这种情况下,cookie 是第一方的。
  4. 根本不要使用cookie。您正在执行的 oAuth 授权授予流程,cloudapi-oauth.suunto.com因为授权服务器不需要 cookie。您遵循了推荐此流程的Instagram-auth示例

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

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

当授权服务器不支持 PKCE 扩展(不支持)时,对查询参数的检查state基于oAuth 客户端的实现最佳实践:cloudapi-oauth.suunto.com

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

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

  • _第二个函数(在服务身份验证之后)无法读取为同一域设置的第一个函数的 cookie(如果第 3 方 cookie 已关闭)_ - 请用详细信息更新您的问题。显示如何调用第二个函数的代码和/或请求/响应标头 (2认同)