在同一域上使用Firebase函数的oAuth的跨域状态Cookie问题 [英] Cross domain state cookie issue for oAuth using firebase functions while on the same domain

查看:62
本文介绍了在同一域上使用Firebase函数的oAuth的跨域状态Cookie问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am implementing a oAuth login for a user for the firebase platform.

除用户具有 禁用跨域Cookie之外,其他所有功能均正常.

这就是我所做的.

  1. 从我的域/应用程序中,用户被重定向到云功能.
  2. can函数可设置 state cookie,并将用户重定向到oAuth提供者.
  3. 用户登录到oAuth提供者,然后重定向回另一个函数以获取代码等.这是问题所在
  1. From my domain/app the user gets redirected to a cloud function.
  2. The could function sets the state cookie and redirects the user to the oAuth provider.
  3. The user signs in to the oAuth provider and gets redirected back to another function to get the code etc. And here is the problem

在上述步骤3中,如果用户已从其浏览器禁用了跨域方cookie,则该功能无法读取任何cookie.这两个功能都位于同一域中,如下面的屏幕快照所示.

On step 3 above the function cannot read any cookie if the user has disabled the cross domain party cookies from his browser. Both functions are on the same domain as seen below in the screenshot.

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

Is there any way I can remedy this issue? Am I doing something wrong in my approach?

我不明白为什么将这两个函数视为跨域.

I cannot understand why the 2 functions are treated as crossdomain.

更新以包含更多信息

请求:

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(或处理该cookie的函数)的代码进行了身份验证:

After that and once the user returns from the service he authenticated against the code that parses the cookies (or the function that handles that) is:

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

The above code does not find a cookie with the name 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');
      }

在此处进行更多搜索是更多信息.

Did a little more search here is some more info.

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

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

Looks like other users suffer from the same issue https://github.com/firebase/functions-samples/issues/569

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

推荐答案

您的响应显示了一个没有域的 state signInWithService cookie的Set-Cookie标头属性:

Your Response shows a Set-Cookie header for state and signInWithService cookies without a domain attribute:

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:

Set-Cookie without a domain means that what happens to the cookie on the way back to the server is browser-dependent. The "default", spec-compliant behavior: the browser will grab the FQDN of the service URL and associate it with the cookie. RFC6265:

除非cookie的属性另有说明,否则cookie为仅返回到原始服务器(而不返回到任何子域)...如果服务器省略了域"属性,则用户代理只会将Cookie返回到原始服务器.

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是第一方还是第三方-派对:

When the browser decides whether to accept the cookie from a HTTP service, one of the decision criteria is if the cookie is first-party or third-party:

  • 第一方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/...
  • First-party cookie: if the resource (web page) you requested that triggered a call to europe-west2-quantified-self-io.cloudfunctions.net/authRedirect resides at https://europe-west2-quantified-self-io.cloudfunctions.net/...
  • Third-party cookie: if the resource (web page) you requested that triggered a call to europe-west2-quantified-self-io.cloudfunctions.net/authRedirect resides at https://some.domain.app.com/...

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

In your case the FQDN of your "parent" app/page is likely different from europe-west2-quantified-self-io.cloudfunctions.net, thus these cookies are labeled as third-party. As you found out, a user can choose to block third-party cookies. As of August 2019, Firefox and Safari block 3rd-party cookies by default. Most (if not all) ad blockers and similar extensions also block them. This would lead the browser to simply ignore Set-Cookie header in the HTTP response from europe-west2-quantified-self-io.cloudfunctions.net/authRedirect. The cookie would not be sent back to 2nd Firebase function at europe-west2-quantified-self-io.cloudfunctions.net/authToken because it doesn't exist on the client.

您的选择:

  1. 在同一域上托管您的应用程序和Firebase功能.
  2. 一种架构,其中所有HTTP请求(应用程序和Firebase函数)均流经该应用程序;后者充当函数调用的某种代理.这是在Firebase中实现的一种方法.
  3. 假设您的应用程序和Firebase函数确实位于不同的域中.在Javascript中,您可以创建一小段中间件,调用/authRedirect FB函数,解析响应(包括通过Set-Cookie标头的cookie),然后将响应(包括cookie)写回通过 document.cookie 连接到浏览器.在这种情况下,Cookie将是第一方的.
  4. 完全不使用Cookie.您针对 cloudapi-oauth.suunto.com 进行的oAuth授权授予流程不需要授权cookie.您遵循了 instagram-auth 示例,该示例推荐了以下流程
  1. Host your app and Firebase functions on the same domain.
  2. An architecture where all HTTP requests (app and Firebase functions) flow through the app; the latter acts as a proxy of sorts for the function calls. Here's one way to do it in Firebase.
  3. Let's say your app and Firebase functions do reside in different domains. In Javascript you can create a small piece of middleware that calls the /authRedirect FB function, parses the response (incl. the cookies via Set-Cookie header), then writes the response (incl. cookies) back to the browser via document.cookie. The cookies in this case would be first-party.
  4. Don't use cookies at all. The oAuth authorization grant flow that you're doing against cloudapi-oauth.suunto.com as the authorization server does not require cookies. You followed an instagram-auth example that recommends this flow

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

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

重定向功能然后将用户重定向到Instagram OAuth2.0同意屏幕(仅第一次),用户将必须授予批准.还在客户端上设置 state cookie, state 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 查询参数的检查基于

The check against state query parameter is based on an implementation best practice for oAuth clients when authorization servers don't support the PKCE extension (cloudapi-oauth.suunto.com doesn't support it):

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

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

关键词已牢固地绑定到用户代理.对于Web应用程序,Cookie是实现此绑定的不错选择,但并非唯一选择.您可以将 state 的值保留在本地或会话存储中,单页应用程序实际上可以做到这一点.如果您想生活在云中,则可以将 state 保留在Cloud Storage或同等存储中...但是您必须制造一个唯一地标识您的客户端的密钥这个特定的HTTP请求.不是不可能,但对于一个简单的场景来说可能会杀伤力大.

The key phrase is securely bound to the user agent. For web apps, a cookie is a decent option of implementing this binding but it's not the only option. You can stick the value of state into local or session storage, single-page apps do exactly that in practice. If you want to live in the cloud, you can stick state in Cloud Storage or equivalent...but you'd have to manufacture a key that uniquely identifies your client and this particular HTTP request. Not impossible but perhaps overkill for a simple scenario.

这篇关于在同一域上使用Firebase函数的oAuth的跨域状态Cookie问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆