多因素认证
多因素认证(MFA),有时也叫双重认证(2FA),通过额外的验证步骤确认用户身份,为你的应用增加了一层安全保障。
🌐 Multi-factor authentication (MFA), sometimes called two-factor authentication (2FA), adds an additional layer of security to your application by verifying their identity through additional verification steps.
通常认为在你的应用中使用多因素认证是最佳做法。
🌐 It is considered a best practice to use MFA for your applications.
使用弱密码或被攻破的社交登录账户的用户容易遭遇恶意账户接管。使用多因素认证可以防止这种情况,因为它要求用户提供下面两项的证明:
🌐 Users with weak passwords or compromised social login accounts are prone to malicious account takeovers. These can be prevented with MFA because they require the user to provide proof of both of these:
- 他们知道的东西。密码,或者社交登录账户的访问权限。
- 他们拥有的东西。可以使用认证器应用(也叫TOTP)或手机。
概览 #
🌐 Overview
Supabase Auth 通过两种方式实现多因素认证(MFA):应用验证器使用基于时间的一次性密码,而短信则使用 Supabase Auth 生成的代码。
🌐 Supabase Auth implements MFA via two methods: App Authenticator, which makes use of a Time based-one Time Password, and phone messaging, which makes use of a code generated by Supabase Auth.
使用多因素认证的应用需要两个重要流程:
🌐 Applications using MFA require two important flows:
- 注册流程。 这让用户可以在你的应用中设置和管理多因素认证(MFA)。
- 身份验证流程。 这让用户在常规登录步骤之后,可以使用任何因素进行登录。
Supabase 身份验证提供:
🌐 Supabase Auth provides:
- 注册 API - 构建丰富的用户界面来添加和移除因素。
- 挑战并验证 API - 安全地验证用户是否有权访问某个因素。
- 多因素列表 API - 构建丰富的用户界面,用于通过额外因素进行登录。
你可以通过 Supabase 仪表板控制对注册 API 以及挑战和验证 API 的访问。将设置为 Verification Disabled 将会禁用挑战 API 和验证 API。
🌐 You can control access to the Enrollment API as well as the Challenge and Verify APIs via the Supabase Dashboard. A setting of Verification Disabled will disable both the challenge API and the verification API.
这些 API 集合让你可以控制适合你的多因素认证体验。你可以创建多因素认证流程,让它可选、对所有人强制,或者仅针对特定用户组。
🌐 These sets of APIs let you control the MFA experience that works for you. You can create flows where MFA is optional, mandatory for all, or only specific groups of users.
一旦用户使用某种身份验证方式注册或登录,Supabase Auth 会在用户的访问令牌(JWT)中添加额外的元数据,你的应用可以用这些信息来决定是否允许访问。
🌐 Once users have enrolled or signed-in with a factor, Supabase Auth adds additional metadata to the user's access token (JWT) that your application can use to allow or deny access.
这些信息由 Authenticator Assurance Level 表示,这是一个关于 Supabase Auth 在特定会话中对用户身份保证的标准衡量方法。目前公认有两个等级:
🌐 This information is represented by an Authenticator Assurance Level, a standard measure about the assurance of the user's identity Supabase Auth has for that particular session. There are two levels recognized today:
- 保证级别 1:
aal1意味着用户使用常规登录方式验证了身份,比如邮箱+密码、魔法链接、一次性密码、手机验证或社交登录。 - 保证级别 2:
aal2意味着用户的身份已经通过至少一种第二因素额外验证,例如 TOTP 代码或一次性密码代码。
这个认证级别被编码在与用户关联的 JWT 中的 aal 声明里。通过解码这个值,你可以在前端、后端和数据库中创建自定义授权规则,从而实现适合你应用的 MFA 策略。没有 aal 声明的 JWT 处于 aal1 级别。
🌐 This assurance level is encoded in the aal claim in the JWT associated with the user. By decoding this value you can create custom authorization rules in your frontend, backend, and database that will enforce the MFA policy that works for your application. JWTs without an aal claim are at the aal1 level.
添加到你的应用 #
🌐 Adding to your app
在你的应用中添加多因素认证需要以下四个步骤:
🌐 Adding MFA to your app involves these four steps:
- 添加注册流程。 你需要在你的应用中提供一个用户界面,让用户能够设置多因素认证(MFA)。你可以在注册后立即添加这个功能,或者在应用设置部分作为一个独立的流程提供。
- 添加退订流程。 你需要支持一个界面,让用户可以查看现有设备,并退订那些不再相关的设备。
- 在登录时添加挑战步骤。 如果用户已经设置了多因素认证,你的应用登录流程需要向用户展示一个挑战界面,要求他们证明自己可以访问额外的认证因素。
- 强制执行多因素认证(MFA)登录规则。 一旦你的用户可以注册并使用 MFA 登录,你就需要在整个应用中执行授权规则:无论是在前端、后端、API 服务器还是行级安全策略中。
注册流程和挑战步骤因不同因素而异,并在单独的页面中进行了说明。访问电话或应用认证器页面,了解如何为各自的因素添加流程。你可以将两种流程结合起来,同时允许使用电话和应用认证器因素。
🌐 The enrollment flow and the challenge steps differ by factor and are covered on a separate page. Visit the Phone or App Authenticator pages to see how to add the flows for the respective factors. You can combine both flows and allow for use of both Phone and App Authenticator Factors.
添加退选流程 #
🌐 Add unenroll flow
取消注册的流程对于手机和TOTP因素都是一样的。
🌐 The unenroll process is the same for both Phone and TOTP factors.
退订流程提供了一个界面,让用户管理并取消与其账户关联的因素。大多数应用都是通过一个因素管理页面来实现,用户可以在该页面查看并解绑选定的因素。
🌐 An unenroll flow provides a UI for users to manage and unenroll factors linked to their accounts. Most applications do so via a factor management page where users can view and unlink selected factors.
当用户取消注册某个因素时,使用该因素的 ID 调用 supabase.auth.mfa.unenroll()。例如,调用:
🌐 When a user unenrolls a factor, call supabase.auth.mfa.unenroll() with the ID of the factor. For example, call:
1import { createClient } from '@supabase/supabase-js'23const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')45// ---cut---6supabase.auth.mfa.unenroll({ factorId: 'd30fd651-184e-4748-a928-0a4b9be1d429' })取消注册ID为 d30fd651-184e-4748-a928-0a4b9be1d429 的因子。
🌐 to unenroll a factor with ID d30fd651-184e-4748-a928-0a4b9be1d429.
强制多因素登录规则 #
🌐 Enforce rules for MFA logins
在你的应用界面上添加多因素认证本身并不会直接给用户提供更高的安全性。你还需要在应用的数据库、API 和服务器端渲染中执行多因素认证规则。
🌐 Adding MFA to your app's UI does not in-and-of-itself offer a higher level of security to your users. You also need to enforce the MFA rules in your application's database, APIs, and server-side rendering.
根据你应用的需求,你可以选择三种方式来强制多因素认证(MFA)。
🌐 Depending on your application's needs, there are three ways you can choose to enforce MFA.
- 对所有用户(新用户和现有用户)强制执行。 任何用户账户都必须注册多因素认证(MFA)才能继续使用你的应用。未经多因素认证,应用将不允许访问。
- 仅对新用户执行。 只有新用户会被强制注册多因素认证(MFA),老用户则会被鼓励注册。新用户如果没有先完成多因素认证,将无法访问应用。
- 仅对已选择加入的用户强制执行。 想要使用多因素认证的用户可以注册,应用在未先通过多因素认证的情况下将不允许访问。
示例:React #
🌐 Example: React
下面是一个示例,它创建了一个新的 UnenrollMFA 组件,展示了多因素认证(MFA)注册流程的重要部分。请注意,用户只有在完成注册流程并获得 aal2 JWT 声明后,才能取消注册某个因素。这里有一些需要注意的点:
🌐 Below is an example that creates a new UnenrollMFA component that illustrates the important pieces of the MFA enrollment flow. Note that users can only unenroll a factor after completing the enrollment flow and obtaining an aal2 JWT claim. Here are some points of note:
- 当组件出现在屏幕上时,
supabase.auth.mfa.listFactors()端点会获取所有现有的因素及其详细信息。 - 用户现有的因素显示在表格中。
- 一旦用户选择了要取消注册的因素,他们可以输入
factorId并点击 取消注册,这会生成一个确认提示框。
取消注册一个因素只有在刷新间隔结束后才会将保障级别从 aal2 降低到 aal1。如果想在注册后立即将级别从 aal2 降到 aal1,则需要手动调用 refreshSession()。
🌐 Unenrolling a factor will downgrade the assurance level from aal2 to aal1 only after the refresh interval has lapsed. For an immediate downgrade from aal2 to aal1 after enrolling one will need to manually call refreshSession()
1/**2 * UnenrollMFA shows a table with the list of factors together with a button to unenroll.3 * When a user types in the factorId of the factor that they wish to unenroll and clicks unenroll4 * the corresponding factor will be unenrolled.5 */6export function UnenrollMFA() {7 const [factorId, setFactorId] = useState('')8 const [factors, setFactors] = useState([])9 const [error, setError] = useState('') // holds an error message1011 useEffect(() => {12 ;(async () => {13 const { data, error } = await supabase.auth.mfa.listFactors()14 if (error) {15 throw error16 }1718 setFactors([...data.totp, ...data.phone])19 })()20 }, [])2122 return (23 <>24 {error && <div className="error">{error}</div>}25 <tbody>26 <tr>27 <td>Factor ID</td>28 <td>Friendly Name</td>29 <td>Factor Status</td>30 <td>Phone Number</td>31 </tr>32 {factors.map((factor) => (33 <tr>34 <td>{factor.id}</td>35 <td>{factor.friendly_name}</td>36 <td>{factor.factor_type}</td>37 <td>{factor.status}</td>38 <td>{factor.phone}</td>39 </tr>40 ))}41 </tbody>42 <input type="text" value={verifyCode} onChange={(e) => setFactorId(e.target.value.trim())} />43 <button onClick={() => supabase.auth.mfa.unenroll({ factorId })}>Unenroll</button>44 </>45 )46}数据库 #
🌐 Database
你的应用应该根据用户当前和可能的认证级别,足够地拒绝或允许访问表格或行。
🌐 Your app should sufficiently deny or allow access to tables or rows based on the user's current and possible authenticator levels.
Postgres 有两种策略类型:宽松型和限制型。这个指南使用限制型策略。确保你不要省略 as restrictive 子句。
🌐 Postgres has two types of policies: permissive and restrictive. This guide uses restrictive policies. Make sure you don't omit the as restrictive clause.
对所有用户(新用户和现有用户)强制执行 [#enforce-for-all-users-new-and-existing]
🌐 Enforce for all users (new and existing)
如果你的应用属于这种情况,这是一个可以应用到你所有表的模板行级安全策略:
🌐 If your app falls under this case, this is a template Row Level Security policy you can apply to all your tables:
1create policy "Policy name."2 on table_name3 as restrictive4 to authenticated5 using ((select auth.jwt()->>'aal') = 'aal2');- 这里的策略不会接受任何
aal声明不是aal2的 JWT,aal2是最高的认证保证级别。 - 使用
as restrictive可以确保此策略会限制表上的所有命令,而不管其他策略如何!
仅对新用户执行 [#enforce-for-new-users-only]
🌐 Enforce for new users only
如果你的应用属于这种情况,规则就会变得更复杂。某个时间点之后创建的用户账户必须有 aal2 级别才能访问数据库。
🌐 If your app falls under this case, the rules get more complex. User accounts created past a certain timestamp must have a aal2 level to access the database.
1create policy "Policy name."2 on table_name3 as restrictive -- very important!4 to authenticated5 using6 (array[(select auth.jwt()->>'aal')] <@ (7 select8 case9 when created_at >= '2022-12-12T00:00:00Z' then array['aal2']10 else array['aal1', 'aal2']11 end as aal12 from auth.users13 where (select auth.uid()) = id));- 该政策将接受
aal1和aal2,适用于在 2022 年 12 月 12 日 00:00 UTC 之前拥有created_at时间戳的用户,但对于其他所有时间戳,只接受aal2。 <@操作符是 Postgres 的“包含于”操作符。- 使用
as restrictive可以确保此策略会限制表上的所有命令,而不管其他策略如何!
仅对已选择加入的用户强制执行 [#enforce-only-for-users-that-have-opted-in]
🌐 Enforce only for users that have opted-in
已经在账户上注册了多因素认证(MFA)的用户期望,只有在他们完成MFA后,你的应用才会对他们生效。
🌐 Users that have enrolled MFA on their account are expecting that your application only works for them if they've gone through MFA.
1create policy "Policy name."2 on table_name3 as restrictive -- very important!4 to authenticated5 using (6 array[(select auth.jwt()->>'aal')] <@ (7 select8 case9 when count(id) > 0 then array['aal2']10 else array['aal1', 'aal2']11 end as aal12 from auth.mfa_factors13 where ((select auth.uid()) = user_id) and status = 'verified'14 ));- 该策略只会在用户至少验证过一个多因素认证(MFA)因素时接受
aal2。 - 否则,它会同时接受
aal1和aal2。 <@操作符是 Postgres 的“包含于”操作符。- 使用
as restrictive可以确保此策略会限制表上的所有命令,而不管其他策略如何!
服务器端渲染 #
🌐 Server-Side Rendering
在服务器端渲染环境中使用 Supabase JavaScript 库时,确保每次请求都创建一个新的对象!这样可以防止你不小心渲染和提供属于不同用户的内容。
🌐 When using the Supabase JavaScript library in a server-side rendering context, make sure you always create a new object for each request! This will prevent you from accidentally rendering and serving content belonging to different users.
在服务器端渲染层面上,有可能强制使用多因素认证。不过,这要做好是有点棘手的。
🌐 It is possible to enforce MFA on the Server-Side Rendering level. However, this can be tricky do to well.
你可以使用 supabase.auth.mfa.getAuthenticatorAssuranceLevel() 和 supabase.auth.mfa.listFactors() API 来识别会话的 AAL 级别以及启用在用户上的任何因素,就像你在浏览器上使用它们一样。
🌐 You can use the supabase.auth.mfa.getAuthenticatorAssuranceLevel() and supabase.auth.mfa.listFactors() APIs to identify the AAL level of the session and any factors that are enabled for a user, similar to how you would use these on the browser.
不过,在服务器上遇到不同的 AAL 级别可能并不是安全问题。考虑以下可能的情况:
🌐 However, encountering a different AAL level on the server may not be a security problem. Consider these likely scenarios:
- 用户使用常规方法登录,但在多因素认证流程中关闭了标签页。
- 用户忘记了一个标签页开着很长时间。(这种情况比你想象的要常见得多。)
- 用户丢了他们的身份验证器设备,不知道接下来该怎么办。
因此,我们建议你把用户引导到一个可以使用额外身份验证因素进行认证的页面,而不是直接显示 HTTP 401 未授权或 HTTP 403 禁止访问的内容。
🌐 We thus recommend you redirect users to a page where they can authenticate using their additional factor, instead of rendering an HTTP 401 Unauthorized or HTTP 403 Forbidden content.
应用接口 #
🌐 APIs
如果你的应用使用 Supabase 数据库、存储或边缘函数,行级安全策略就能提供足够的保护。如果你还有其他想要保护的 API,可以遵循以下一般指南:
🌐 If your application uses the Supabase Database, Storage or Edge Functions, Row Level Security policies provide sufficient protection. In the event that you have other APIs that you wish to protect, follow these general guidelines:
- 为你的语言使用一个好的 JWT 验证和解析库。 这样你就可以安全地解析 JWT 并提取其中的声明。
- 从 JWT 中获取
aal声明,并根据你的需要比较其值。 如果你遇到了可以提高的 AAL 级别,应该让用户继续登录流程,而不是将他们登出。 - 使用
https://<project-ref>.supabase.co/rest/v1/auth/factorsREST 端点来识别用户是否已注册任何 MFA 因素。 只对verified因素进行操作。
常见问题 #
🌐 Frequently asked questions