认证

Firebase 认证

在您的 Supabase 项目中使用 Firebase 认证


Firebase Auth 可以作为第三方认证提供商与 Supabase Auth 一起使用,也可以独立用于您的 Supabase 项目。

开始使用

  1. 首先需要添加集成来连接您的 Supabase 项目与 Firebase 项目。您需要在 Firebase 控制台获取项目 ID。
  2. 在项目的认证设置中添加新的第三方认证集成。
  3. 如果您在自托管时使用第三方认证,请为 public 模式下的所有表、Storage 和 Realtime 创建并附加限制性 RLS 策略,以防止来自无关 Firebase 项目的未授权访问
  4. 为所有用户分配 role: 'authenticated' 自定义用户声明
  5. 最后在您的应用程序中设置 Supabase 客户端。

设置 Supabase 客户端库

创建 Web 客户端非常简单,只需传递 accessToken 异步函数即可。该函数应返回当前用户的 Firebase Auth JWT(如果找不到用户则返回 null)。

1
2
3
4
5
6
7
import { createClient } from '@supabase/supabase-js'const supabase = createClient('https://<supabase-project>.supabase.co', 'SUPABASE_ANON_KEY', { accessToken: async () => { return (await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null },})

请确保应用程序中的所有用户都设置了 role: 'authenticated' 自定义声明。如果使用 onCreate 云函数为新注册用户添加此自定义声明,由于 onCreate 函数不会同步运行,注册后需要立即调用 getIdToken(/* forceRefresh */ true)

为项目添加新的第三方认证集成

在仪表板中,导航至您项目的认证设置,找到"第三方认证"部分来添加新的集成。

在CLI中,将以下配置添加到您的supabase/config.toml文件:

1
2
3
[auth.third_party.firebase]enabled = trueproject_id = "<id>"

为项目的RLS策略添加额外安全层(仅限自托管)

Firebase Auth对所有项目使用同一组JWT签名密钥。这意味着来自与您项目无关的Firebase项目签发的JWT可能会访问您Supabase项目中的数据。

使用Supabase托管平台时,来自未注册Firebase项目ID的JWT在到达您的数据库之前就会被拒绝。而在自托管时,实现这一机制是您的责任。一个简单的防护方法是为**public模式中的所有表**创建并维护以下RLS策略。您还应将此策略应用于存储桶或实时频道。

建议您使用限制性Postgres行级安全策略

限制性RLS策略与常规(或宽松)策略的不同之处在于,它们在定义时使用as restrictive子句。它们不授予权限,而是限制任何现有或未来的权限。对于像Firebase Auth技术限制与应用程序逻辑分离的情况非常适用。

以下是一个RLS策略示例,它将限制仅允许您项目(用<firebase-project-id>表示)的用户访问,而不允许其他任何Firebase项目访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
create policy "限制仅Supabase Auth和Firebase Auth项目ID <firebase-project-id>可访问" on table_name as restrictive to authenticated using ( (auth.jwt()->>'iss' = 'https://<project-ref>.supabase.co/auth/v1') or ( auth.jwt()->>'iss' = 'https://securetoken.google.com/<firebase-project-id>' and auth.jwt()->>'aud' = '<firebase-project-id>' ) );

如果您的应用中有许多表,或者需要为存储实时管理复杂的RLS策略,定义一个稳定的Postgres函数来执行检查可以减少重复代码。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create function public.is_supabase_or_firebase_project_jwt() returns bool language sql stable returns null on null input return ( (auth.jwt()->>'iss' = 'https://<project-ref>.supabase.co/auth/v1') or ( auth.jwt()->>'iss' = concat('https://securetoken.google.com/<firebase-project-id>') and auth.jwt()->>'aud' = '<firebase-project-id>' ) );

请确保将<project-ref>替换为您的Supabase项目ID,将<firebase-project-id>替换为您的Firebase项目ID。然后所有表、桶和频道上的限制性策略可以简化为:

1
2
3
4
5
create policy "限制仅正确的Supabase和Firebase项目可访问" on table_name as restrictive to authenticated using ((select public.is_supabase_or_firebase_project_jwt()) is true);

分配"role"自定义声明

您的Supabase项目会检查所有发送给它的JWT中存在的role声明,以便在使用数据API、存储或实时功能授权时分配正确的Postgres角色。

默认情况下,Firebase的JWT中不包含role声明。如果您将这样的JWT发送到您的Supabase项目,在执行Postgres查询时将分配anon角色。您应用的大部分逻辑应该由authenticated角色访问。

使用 Firebase 认证函数分配认证角色

根据您的 Firebase 项目配置,有两种设置 Firebase 认证函数的方式:

  1. 最简单方式:使用 Firebase 阻塞式认证函数,但此功能仅适用于使用 Firebase 认证与 Identity Platform 的项目。
  2. 手动方式:使用 admin SDK 为所有用户分配自定义声明,并定义 onCreate Firebase 认证云函数 来为新创建的用户持久化角色。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { beforeUserCreated, beforeUserSignedIn } from 'firebase-functions/v2/identity'export const beforecreated = beforeUserCreated((event) => { return { customClaims: { // Supabase 项目将使用此角色分配 `authenticated` // Postgres 角色 role: 'authenticated', }, }})export const beforesignedin = beforeUserSignedIn((event) => { return { customClaims: { // Supabase 项目将使用此角色分配 `authenticated` // Postgres 角色 role: 'authenticated', }, }})

注意:您可以使用 sessionClaims 替代 customClaims。区别在于 session_claims 不会保存在 Firebase 用户配置文件中,但在用户登录期间保持有效。

最后部署您的函数使更改生效:

1
firebase deploy --only functions

请注意,这些函数仅在用户新注册和登录时调用。现有用户的 ID 令牌中不会包含这些声明。您需要使用 admin SDK 为所有用户分配角色自定义声明。请确保在部署上述 Firebase 阻塞式认证函数后再执行此操作。

使用 Admin SDK 为所有用户分配角色自定义声明

您需要运行一个脚本,为所有现有的 Firebase Authentication 用户分配 role: 'authenticated' 自定义声明。这可以通过结合使用列出用户设置自定义用户声明这两个管理 API 来实现。下面提供了一个示例脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'use strict';const { initializeApp } = require('firebase-admin/app');const { getAuth } = require('firebase-admin/auth');initializeApp();async function setRoleCustomClaim() => { let nextPageToken = undefined do { const listUsersResult = await getAuth().listUsers(1000, nextPageToken) nextPageToken = listUsersResult.pageToken await Promise.all(listUsersResult.users.map(async (userRecord) => { try { await getAuth().setCustomUserClaims(userRecord.id, { role: 'authenticated' }) } catch (error) { console.error('设置用户自定义角色失败', userRecord.id) } }) } while (nextPageToken);};setRoleCustomClaim().then(() => process.exit(0))

当所有用户都获得 role: 'authenticated' 声明后,该声明将出现在用户新颁发的所有 ID 令牌中。