在阻止第三方访问存储空间的浏览器上使用 signInWithRedirect 的最佳实践

本文档介绍了在阻止第三方 Cookie 的浏览器上使用重定向登录的最佳实践。您必须采用下文所列方法中的一种,这样 signInWithRedirect() 才能在生产环境中的所有浏览器上都正常发挥作用。

概览

为了使您和您的用户顺利执行 signInWithRedirect() 流程,Firebase Authentication JavaScript SDK 会使用一个连接到应用的 Firebase Hosting 网域的跨源 iframe。 但是,此机制不适用于阻止第三方访问存储空间的浏览器。

由于要求用户停用浏览器上的存储空间分区功能不大现实,因此您应该改为根据您的具体使用情形对您的应用采取以下设置方法之一。

  • 如果您使用 Firebase Hosting 在 firebaseapp.com 的子网域上托管应用,将不会受到此问题的影响,无需执行任何操作。
  • 如果您使用 Firebase Hosting 在自定义网域或 web.app 的子网域上托管应用,请使用方法 1
  • 如果您使用 Firebase 以外的服务托管应用,请使用方法 2方法 3方法 4方法 5

方法 1:更新 Firebase 配置以使用自定义网域作为 authDomain

如果您使用 Firebase Hosting 在自定义网域上托管应用,则可以配置 Firebase SDK 以使用自定义网域作为 authDomain。这可确保应用和身份验证 iframe 使用相同的网域,从而防止出现登录问题。(如果您使用的不是 Firebase Hosting,则需选择其他方法。)

如需更新 Firebase 配置以使用自定义网域作为身份验证网域,请执行以下操作:

  1. 配置 Firebase JS SDK 以使用自定义网域作为 authDomain

    const firebaseConfig = {
      apiKey: "<api-key>",
      authDomain: "<the-domain-that-serves-your-app>",
      databaseURL: "<database-url>",
      projectId: "<project-id>",
      appId: "<app-id>"
    };
    
  2. 将新的 authDomain 添加到 OAuth 提供方的已授权重定向 URI 列表中。如何添加将取决于提供方,但一般而言,您可以按照任何提供方(例如 Facebook 提供方)的“准备工作”部分的说明操作。更新后的授权 URI 如下所示:https://<the-domain-that-serves-your-app>/__/auth/handler。末尾的 /__/auth/handler 很重要。

    同样,如果您使用的是 SAML 提供方,请将新的 authDomain 添加到 SAML ACS(断言消费者服务)网址。

方法 2:改用 signInWithPopup()

使用 signInWithPopup() 而非 signInWithRedirect()。应用代码的其余部分保持不变,但 UserCredential 对象的检索方式不同。

Web 模块化 API

  // Before
  // ==============
  signInWithRedirect(auth, new GoogleAuthProvider());
  // After the page redirects back
  const userCred = await getRedirectResult(auth);

  // After
  // ==============
  const userCred = await signInWithPopup(auth, new GoogleAuthProvider());

Web 命名空间型 API

  // Before
  // ==============
  firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());
  // After the page redirects back
  var userCred = await firebase.auth().getRedirectResult();

  // After
  // ==============
  var userCred = await firebase.auth().signInWithPopup(
      new firebase.auth.GoogleAuthProvider());
```

弹出式窗口登录并不是在任何场合都适用,因为此类窗口有时会被设备或平台屏蔽,并且其流程对于移动设备用户来说不太顺畅。如果您的应用不适合使用弹出式窗口,则您需要采用其他方法。

方法 3:通过代理将身份验证请求发送到 firebaseapp.com

signInWithRedirect 流程首先从您的应用网域重定向到 Firebase 配置中的 authDomain 参数指定的网域(默认为“.firebaseapp.com”)。authDomain 将托管用于重定向到身份提供方的登录帮助程序代码,成功验证身份后,该代码会重定向回应用网域。

当身份验证流程返回您的应用网域时,系统会访问登录帮助程序网域的浏览器存储空间。此方法和下面的方法(自行托管代码)可以避免跨源访问存储空间(浏览器会阻止这种访问)。

  1. 在应用服务器上设置反向代理,以便将发送到 https://<app domain>/__/auth/ 的 GET/POST 请求转发到 https://<project>.firebaseapp.com/__/auth/。确保此转发操作对浏览器来说是透明的;此转发操作无法通过 302 重定向来完成。

    如果您使用 nginx 处理自定义网域的请求,则反向代理配置将如下所示:

    # reverse proxy for signin-helpers for popup/redirect sign in.
    location /__/auth {
      proxy_pass https://<project>.firebaseapp.com;
    }
    
  2. 按照方法 1 中的步骤更新已获授权的 redirect_uri、ACS 网址和您的 authDomain。重新部署应用后,应该就不会再发生跨源访问存储空间的现象。

方法 4:在您的网域中自行托管登录帮助程序代码

另一种避免跨源访问存储空间的方法是自行托管 Firebase 登录帮助程序代码。但是,此方法不适用于 Apple 登录或 SAML。仅当方法 3 中的反向代理设置不可行时,才应使用方法 4。

托管帮助程序代码的步骤如下:

  1. 执行以下命令,从 <project>.firebaseapp.com 位置下载要托管的文件:

    mkdir signin_helpers/ && cd signin_helpers
    wget https://<project>.firebaseapp.com/__/auth/handler
    wget https://<project>.firebaseapp.com/__/auth/handler.js
    wget https://<project>.firebaseapp.com/__/auth/experiments.js
    wget https://<project>.firebaseapp.com/__/auth/iframe
    wget https://<project>.firebaseapp.com/__/auth/iframe.js
    
  2. 将上述文件托管在应用网域下。确保您的 Web 服务器可以响应 https://<app domain>/__/auth/<filename>

    此处提供了一个下载和托管文件的服务器实现示例。 建议您定期下载并同步这些文件,以确保用户能及时获取最新的问题修复和功能。

  3. 按照方法 1 中的步骤更新已获授权的 redirect_uri 和您的 authDomain。重新部署应用后,应该就不会再发生跨源访问存储空间的现象。

方法 5:独立处理提供方登录流程

Firebase Authentication SDK 提供了 signInWithPopup()signInWithRedirect() 便捷方法来封装复杂的逻辑并避免涉及其他 SDK。您也可以不使用这两个方法,而是独立登录到提供方,然后通过 signInWithCredential() 使用提供方的凭据换取 Firebase Authentication 凭据。例如,您可以运行以下代码,使用 Google 登录 SDK示例代码来获取 Google 账号凭据,然后将新的 Google 凭据实例化:

Web 模块化 API

  // `googleUser` from the onsuccess Google Sign In callback.
  //  googUser = gapi.auth2.getAuthInstance().currentUser.get();
  const credential = GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token);
  const result = await signInWithCredential(auth, credential);

Web 命名空间型 API

  // `googleUser` from the onsuccess Google Sign In callback.
  const credential = firebase.auth.GoogleAuthProvider.credential(
      googleUser.getAuthResponse().id_token);
  const result = await firebase.auth().signInWithCredential(credential);

调用 signInWithCredential() 后,应用其余部分的行为与以前相同。

如需了解如何获取 Apple 凭据,请点击此处