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

标签 firebase oauth google-cloud-functions cross-domain

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

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

这就是我所做的。

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

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

enter image description here

有什么办法可以解决这个问题吗?我的方法是否做错了什么?

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

更新以包含更多信息

请求:

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

请求 header

: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

响应 header

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 的代码(或处理该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(),
      });
    }
  });
});

上面的代码没有找到名为state的cookie

所以这里失败了

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/issues/569

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

最佳答案

您的响应显示 statesignInWithService cookie 的 Set-Cookie header ,不带 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

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

Unless the cookie's attributes indicate otherwise, the cookie is returned only to the origin server (and not, for example, to any subdomains)...If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.

当浏览器决定是否接受来自 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。大多数(如果不是全部)广告拦截器和类似的扩展程序也会阻止它们。这将导致浏览器简单地忽略来自 europe-west2-quantified-self-io.cloudfunctions.net/authRedirect 的 HTTP 响应中的 Set-Cookie header 。该 Cookie 不会发送回位于 europe-west2-quantified-self-io.cloudfunctions.net/authToken 的第二个 Firebase 函数,因为客户端上不存在该 cookie。

您的选择:

  1. 将您的应用和 Firebase 功能托管在同一域中。
  2. 所有 HTTP 请求(应用和 Firebase 功能)都流经应用的架构;后者充当函数调用的某种代理。这是one way在 Firebase 中执行此操作。
  3. 假设您的应用和 Firebase 函数确实驻留在不同的域中。在 Javascript 中,您可以创建一小段中间件来调用 /authRedirect FB 函数,解析响应(包括通过 Set-Cookie header 的 cookie),然后将响应(包括 cookie)写回通过 document.cookie 发送到浏览器。在这种情况下,cookie 是第一方的。
  4. 根本不要使用 cookie。您针对 cloudapi-oauth.suunto.com 执行的 oAuth 授权授予流程,因为授权服务器不需要 Cookie。您关注了instagram-auth推荐此流程的示例

When clicking the Sign in with Instagram button a popup is shown which redirects users to the redirect Function URL.

The redirect Function then redirects the user to the Instagram OAuth 2.0 consent screen where (the first time only) the user will have to grant approval. Also the state cookie is set on the client with the value of the state URL query parameter to check against later on.

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

Clients MUST prevent CSRF. One-time use CSRF tokens carried in the "state" parameter, which are securely bound to the user agent, SHOULD be used for that purpose. If PKCE [RFC7636] is used by the client and the authorization server supports PKCE, clients MAY opt to not use "state" for CSRF protection, as such protection is provided by PKCE. In this case, "state" MAY be used again for its original purpose, namely transporting data about the application state of the client

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

关于firebase - 在同一域上使用 firebase 函数的 oAuth 的跨域状态 cookie 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57253593/

相关文章:

Firebase 函数配置正在截断我的 api key 字符串

java - 将相机图像保存到 SD 卡,检索并上传到 Firestore 存储

java - 如何从所选项目微调器获取值(Android Studio 到 Firebase)

node.js - 将 Firebase 函数中的 Stripe 响应返回到 Swift 应用程序

ios - 附加 Firebase 聊天消息

Facebook OAuth 突然停止工作

oauth - 如何允许任何域登录我的 Azure Active Directory 应用程序

cookies - 如何使用 OWIN 在 Web API 中组合 OAuth 和 cookie 身份验证?

node.js - Firebase 管理查询 : collection where documentId() IN Array of ids

firebase - 如何在 firebase 托管的 webapp 中获取 firebase 函数 URL?