How to share Firebase Authentication across subdomains

2025-06-09

如何跨子域共享 Firebase 身份验证

这篇文章适用于已经熟悉 Firebase、Firebase 身份验证以及在其 Web 应用中使用 Firebase 的人们。

如果您在 Web 应用中使用 Firebase 身份验证,可能会遇到 Firebase 仅支持单域名身份验证的问题。这意味着,如果您的应用体验分布在多个子域名,则用户必须分别登录每个子域名。更糟糕的是,用户还必须分别退出每个子域名。

如果您的应用程序在子域名之间共享品牌标识,则可能会带来安全风险。用户期望退出时app1.domain.com也同时退出,这或许是合理的app2.domain.com。许多流行的应用程序在子域名之间共享登录状态,例如 Google 本身。

我花了比预期更长的时间来实现跨子域的单点登录,我写这篇文章是希望下一个人能更轻松地做到这一点。

从高层次来看,这是我们的设置:

  1. 我们在不同的领域有三个应用程序。
    • accounts.domain.com
    • app1.domain.com
    • app2.domain.com
  2. 我们有三个 Firebase 函数
    • ...cloudfunctions.net/users-signin
    • ...cloudfunctions.net/users-checkAuthStatus
    • ...cloudfunctions.net/users-signout

登录方式:

  1. 有人导航到该accounts.domain.com应用程序
  2. 他们提供他们的身份验证信息
  3. 该身份验证信息被发送到我们的/users-signin云功能,该功能会验证信息,如果有效,则设置__session包含用户 UID 的签名 cookie,并向客户端返回成功指示。
  4. 成功后,客户端将调用/users-checkAuthStatus云函数来查找签名的__sessioncookie、提取用户 UID,并使用 UID 和firebase-adminSDK 来创建自定义身份验证令牌并将其返回给客户端。
  5. 当客户端收到此自定义身份验证令牌时,它会使用它通过 firebase javascript SDK 登录。

当用户导航到其他应用(例如)时,app1.domain.com该应用会首先检查用户是否已通过 Firebase Auth 登录。如果没有,它会调用/users-checkAuthStatus云函数,该函数会查找已签名的__sessionCookie,并在适用的情况下向客户端返回自定义身份验证令牌。然后,客户端会使用自定义身份验证令牌(如果有)为用户登录。

如果用户app1.domain.com尚未登录但想要登录,您可以将其发送到,然后在登录完成后将accounts.domain.com其重定向回。app1.domain.com

为了退出登录,客户端会通过调用 来清除本地身份验证状态signOut()firebase-js-sdk并调用...cloudfunctions.net/users-signout来清除__sessionCookie。此外,客户端需要通知所有其他连接的客户端用户已退出登录,以便他们可以signOut()使用 firebase-js-sdk 进行调用。

真正让事情安全运转起来。

这是高层次的概述,但为了真正让它发挥作用,我们需要处理一些事情,比如跨站点脚本、cookie、处理提供商身份验证等。

登录

首先,您需要决定如何在服务器上验证身份验证。

一种可能性是,正常地在客户端上对某人进行身份验证accounts.domain.com(使用 Firebase Auth),然后将他们的 idToken 发送到服务器,在服务器上使用管理 SDK 验证 ID 令牌,验证issuedAtTime与 ID 令牌关联的内容(例如,确保它是在过去 5 分钟内创建的),并验证与 ID 令牌关联的提供程序(例如,确保它不是使用自定义身份验证令牌创建的)。

另一种可能性是,如果有人通过 Facebook 或 Twitter 等提供商进行身份验证,则使用该提供商的 SDK 对其进行身份验证,检索authToken,然后将发送authToken到服务器,然后按照提供商的说明在服务器上验证令牌。

无论如何将凭据传递给服务器,如果服务器确定身份验证有效,则需要设置一个__sessioncookie,该 cookie 等于与用户关联的 Firebase 身份验证 UID,以及设置一个跨站点脚本 cookie,我们将使用它来防止跨站点脚本攻击

Cookie__session应该是signedsecure(表示仅 HTTPS)和httponly(表示 JavaScript 无法访问)。跨站脚本 Cookie(我们称之为 )csst应该是 ,secure但不能是signedhttponly。相反,csst应该使用对jsonwebtoken令牌进行签名并记录令牌主题(即 auth UID)的库来创建 Cookie。与这两个 Cookie 关联的域应该是您应用程序的根域(即domain.com)。这确保 Cookie 在子域之间共享。

浏览器不允许您为其他域设置 Cookie。这是一个问题,因为默认情况下,您的 Firebase 函数与您的应用位于不同的域中。您可以按照这些说明为您的函数使用 Firebase Hosting 和自定义域。另请注意,为您的函数使用 Firebase Hosting 意味着您只能__session在服务器端读取 Cookie(这不会影响我们对csst令牌的使用)。即使您将 Firebase 函数设置为使用您的自定义域,您在开发过程中仍可能会遇到问题:在我最初的设置中,我在本地托管我的应用,但将 Firebase 函数部署到一个特殊的开发 Firebase 项目中。这意味着与函数关联的域不是 localhost(意味着函数无法设置客户端可以看到的 Cookie)。

我想出了两种解决此情况的方法。

  1. 在开发过程中禁用跨站点脚本检查,并domain__sessionCookie 中移除相关规范。此方法有效,因为__sessionCookie 无论如何都只能由 Firebase Functions 读取,因此即使__sessionCookie 不在子域之间共享也没问题(在这种情况下,与 Cookie 关联的域__session将是您的 Firebase Functions 域)。
  2. 在开发过程中,在本地提供你的功能。Firebase在文档中提供了如何使用其本地模拟器的说明
    • 本地函数模拟器现在可与 Node 8+ 配合使用 本地模拟器的一个问题是它只适用于 nodejs 6(仅供参考,我发现当前版本的 expressjs 在 nodejs6 中不起作用)。
    • 这不再是必要的,因为本地函数模拟器现在可以与 node 8+ 一起使用 另一种选择是构建自己的 Express 应用来托管开发过程中的功能。这是路线……

要设置 Cookie,您需要使用onRequestFirebase Function而不是onCallFirebase Function。您还需要处理CORS以及所有相关功能。另外,我还要指出的是,Google Chrome 浏览器有一个非常出乎意料的怪癖:如果响应头来自不同的域名,它会从响应中删除该头。我讨厌这个怪癖。我花了好几个小时才知道 Cookie 没有被设置,但实际上它被设置了。更多信息请参阅此 SO 问题。另外,执行 CORS 请求时,您需要指定请求“带有凭据”才能发送 Cookie。服务器还需要指定 CORS 请求允许使用凭据,以便接收凭据。set-cookieset-cookie

检查授权状态

无论如何,在客户端设置完__sessioncookie 和cookie 之后,客户端就可以调用端点了。调用时,客户端需要找到cookie,提取其令牌,并将令牌设置在请求的标头中。端点收到请求后,会提取授权标头中包含的令牌,验证令牌上的签名,并确保令牌的主题与签名 cookie 中包含的身份验证 UID 匹配。假设一切正常,您可以使用SDK 生成自定义身份验证令牌并将其发送给客户端。如果无效,请清除客户端上所有旧的 cookie csst/users-checkAuthStatuscsstAuthorization: Bearer ${token}checkAuthStatuscsst__sessionfirebase-admin__sessioncsst

最后,当客户端收到这些自定义身份验证令牌之一时,请确保客户端使用SESSION身份验证持久性进行登录。这意味着身份验证状态将在页面刷新后保留,但当与域关联的每个标签页关闭时,身份验证状态将被清除。每当应用初始化时,您都需要执行以下操作:

  1. 检查此人是否已登录。
  2. 如果没有,则调用/users-checkAuthStatus端点,如果您收到自定义身份验证令牌作为响应,则使用它来登录用户。
    • 如果您没有收到任何信息,则表明该用户尚未登录。

退出

当有人退出时,客户端需要调用/users-signout端点,该端点将清除客户端上的所有__session/ csstcookies,并调用signOut()firebase-js-sdk 的方法。此外,您还需要以某种方式 ping 任何其他打开的应用,以告知它们退出操作——此时,它们应该调用signOut()firebase-js-sdk 的方法退出。提醒一下,如果某个应用已关闭,firebase-js-sdk 的身份验证状态已清除。

为了ping通其他打开的应用,告知它们退出firebase-js-sdk,我发现最简单的方法是监控csstcookie的存在。如果csstcookie消失,则表明该用户已退出,您的应用应该调用signOut()firebase sdk的方法。

总结

好了,我的概述到此结束。让 Firebase 身份验证跨子域工作并非易事,但无需太多工作即可实现遗憾的是,您需要熟悉一些概念,例如 CORS、Cookie、JWT、Firebase 身份验证本身等等。

祝你好运!

编辑(2021年5月14日)

  1. 有几个人要求提供可运行设置的示例(也就是代码)。我当然理解为什么这会很有帮助,但我没有计划这么做(也就是说,我不愿意花时间)。如果有人读到这篇文章,整理了一个示例代码库并在评论区留言给我,我会在这篇文章中更新你的代码库链接(并注明出处),以便其他人也能从中受益。

  2. 另一位开发人员提出了这种方法的变体(他们认为这是一种改进),您可以在这里阅读,跨域 Firebase 身份验证:一种简单的方法。我根本没有测试过这种方法,所以我分享它但不认可它(但有选择总是好的,对吧?)。

  3. 无关地,我最近发现你可以(相当容易地)为 Firebase Firestore 实现一个简单的查询缓存,这可以提高性能并可能降低成本。你可以在这里看到概述:

鏂囩珷鏉ユ簮锛�https://dev.to/johncarroll/how-to-share-firebase-authentication-across-subdomains-1ka8
PREV
初学者掌握 AI 开发所需的 7 个开源工具🧙‍♂️🪄
NEXT
停止关于 JavaScript 框架的争论