使用supabase/auth实现用户认证管理

June 12, 2024

Supabase 是一个开源的 Firebase 替代方案,其中包含了用户认证及权限管理模块supabase/auth,个人项目中为了实现用户登录能力,经多方预研后决定基于supabase来实现。原因如下:

  • 开源:开源且支持自部署。官方的free plan目前也足够了。
  • 高灵活性:支持多种接入方式,无论是通过官方的auth UI快速集成,还是使用SDK和API自定义认证页面。
  • 强大功能:不仅支持用户认证和授权,还采用JWT进行认证,基于Supabase的Row Level Security实现细粒度授权。同时,支持主流第三方认证提供商,包括匿名登录。
  • 完善文档与活跃社区:文档齐全,社区活跃,提供丰富的SDK,对Next.js项目特别友好。
  • 安全性:Supabase的认证模块基于Netlify开源的GoTrue项目,安全设计周全,值得信赖。

背景知识

关于认证OAuth几种模式之类的基础知识这里不再赘述,主要关注接入过程中涉及到的知识点。

用户认证

在Supabase中,用户是经过身份验证和授权机制确认的个体,他们具有不同的角色和权限,可以执行相应的操作并访问授权的资源。Supabase支持多种认证方式,包括:

  • 邮件和密码认证
  • 无密码认证:比如OTP(one-time apssword、magiclink)
  • OAUTH:比如google、git等
  • SAML SSO

认证完毕后就可以派发access-token、refresh-token等信息给到使用方进行身份验证。Supabase采用session和JWT结合的方式进行用户身份验证。

Session

用户完成设备认证后,会在Supabase的数据库中生成一条session记录( auth.session table),并作为用户后续登录的凭据。核心字段包括:id(session_id)、user_id、创建/更新时间、ip、UA等。

Loading...

默认情况下session不会过期,使其过期的方法包括以下几种:

  • 登出:调用 supabase.auth.signOut() ,session中的数据会被清理。
  • 用户reset-password。
  • 用户无交互(inactive):可以在auth相关的配置中指定inactive timeout。
  • 最大时长限制:可以在auth相关配置中指定最大时长Time-box user sessions。
  • 用户登入其他设备:可配置session数唯一,仅一台设备使用。

session_id会被SHA的方式加密写入access-token,供后续的用户真实身份校验。

JWT

在Supabase身份验证中,Session由JWT形式的访问令牌(access_token)和刷新令牌(refresh_token)表示,其中刷新令牌是一个唯一的字符串。完成认证后,Supabase SDK会在cookie中写入JWT JSON对象。以邮件密码方式认证为例,JWT格式如下:

Loading...

重点内容:

  • access-token:用户凭证,有效时长可配置,建议时长不要超过1小时。可凭refresh-token定期刷新。
  • refresh-token:不过期,但只允许使用一次(即refresh token rotaion),目的主要是为了防止refresh_token被窃取。同时为避免网络异常、重复渲染等情况造成的不可用,Supabase提供了Refresh Token Reuse Detection机制来保障可靠性。
  • expire:access-token过期时间。

Refresh Token Reuse Detection说明

Refresh Token Reuse Detection 是一种安全机制,用于检测并管理refresh token的重复使用。在OAuth 2.0等认证协议中,refresh token通常用于获取新的access token,以便在用户不必重新登录的情况下继续访问资源。

该机制保护的内容

  1. 防止Token滥用:正常情况下,一个refresh token应该只能使用一次。但是,如果严格按照这个规则执行,可能会导致一些合理场景下的问题,例如网络延迟或请求重试时,客户端可能无意中重复使用了refresh token。Refresh Token Reuse Detection允许在特定时间间隔内重复使用refresh token,从而避免了这些问题。
  2. 保护用户会话:如果没有适当的机制来处理refresh token的重用,可能导致用户的会话意外终止。例如,当客户端尝试使用一个已经被撤销的refresh token时,如果服务器严格遵循“一次使用”规则,它会认为这个请求是无效的,并可能终止用户的会话。Refresh Token Reuse Detection通过允许在特定条件下重用token,减少了这种情况的发生。
  3. 应对网络不可靠性:由于网络问题,客户端可能无法及时收到或处理服务器的响应。在这种情况下,客户端可能尝试再次使用同一个refresh token,如果服务器不支持在一定时间窗口内的重用,这将导致问题。
  4. 安全性:这种机制也增强了安全性,因为它可以减少因误用或滥用refresh token而导致的潜在安全风险,比如token被窃取后的滥用问题。

身份验证

(Web)SDK中提供了两个API用于验证用户身份:

  • supabase.auth.getUser :该API会发起一次到Supabase· server的网络调用,在服务端去解析JWT的有效性。一般用于服务端。
  • supabase.auth.getSession :从localstorage取JWT以及加密session data。用于本地的登录校验。一般用于client端非敏感权限的判断(比如判断用户是否登录)。

接入流程

个人项目中使用了Nextjs + @supabase/ssr 接入,简单介绍下使用邮件密码认证的接入流程。

前置准备

  • 创建Supabase项目,申请 NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY 并写入项目环境变量。
  • 配置confirm地址白名单以及邮件模板,指定用户signup、password-reset等场景的confirm邮件内容以及跳转地址。

confirm地址白名单示例:

Loading...

邮件模板示例

Loading...

其他准备条件参考官网demo

接入

分场景介绍几个核心场景的接入流程。

注册(signup)

这里采用需要邮箱注册的方式,且需要邮件确认。

阶段一:在supabase中配置signup注册模板,包括confirm地址。

  1. web请求signup页面,next server下发signup页,里面包含一个含表单的页面。
  2. web:用户输入signup相关字段,web端参数合法性校验,通过next action将form参数提交给next server。
  3. next server action:校验参数合法性,然后调用 supabase.auth.signUp() ,通过supbase 服务端给用户发送confirm邮件。返回web发送成功。
    Loading...

    同时也会下发一个 sb-xxxxxx-auth-token-code-verifier的cookie给到web。具体作何使用(注册流程跟Oauth的PKCE有何关系?)

    注意:Signup需要指定redirect地址为结果页面的地址。因为OTP的第一跳地址为模板中配置的 auth/confirm ,然后才会被redirect到我们指定的redirect地址。

  4. web:通知已发送邮件,需要用户去邮箱确认。

阶段二:用户确认

  1. web:用户通过邮件点击confirm地址,query参数中包括一个一次性的token_hash以及操作类型op_type,以及redirect地址(上面的next参数)。
  2. next.js server action:通用的confirm action,主要校验hash-token的有效性并下发cookie。然后跳转到redirect页。
  3. next.js server page:注册结果展示。

 

问题:

  1. sb-xxxxxx-auth-token-code-verifier 这里的作用是什么?
Loading...

code_verifiercode_challengecode_challenge_method 其实是OAUTH code authorization的扩展模式PKCE所需的参数,主要目的是为了防止authorize过程中code可能被第三方恶意劫持,然后第三方使用code去换取access-token的场景。所以在OAuth的code、token两步中分别加入了额外的验证参数,确保劫持的第三方即使邮件密码认证这里应该没有实际作用。

登录(signin)

  1. web请求登录页,next.js server 登录page返回ssr登录页,里面包含一个含表单的页面。
  2. web: 用户输入sigin相关字段(email、password),web端参数合法性校验,通过next action将form参数提交给next server。
  3. next server action:校验参数合法性,然后调用 supabase.auth.signInWithPassword(formData) 完成登录,返回成功、失败。登录成功SDK会下发auth token到cookie中(key格式为 sb-xxxxxx-auth-token)。
    Loading...
  4. web: 展示响应结果,然后跳转到登录结果页面。
    1. 官网demo中,是在action下发了redirect结果。一样的效果。
    2. 跳转是否必须? 不是,可以不跳转。在action就已经下发了token到cookie,此时已经处于登录状态。

密码重置(reset password)

与signup流程类似,总体分位两个阶段:

阶段一:与signup一致,调用 supabase.auth.resetPasswordForEmail 发送OTP邮件。用户点击邮件后继续完成改密操作。

阶段二:经过通用confirm action的跳转后,来到改密页面。此时用户已经处于登录状态,构造form后调用action中的 supabase.auth.updateUser 即可完成密码的更新。

登出(logout)

在服务端调用signUp即可。

Loading...

其它细节

  • 通过 verifyOtp 获得的session与通过signInWithPassword 获得的登录token/session有何不同?

没有不同,都是认证方式的一种。所以signUp、resetPassword等场景通过OTP方式登录后不需要再跳转登录页再认证一次。

  • 为何OTP邮件的第一跳地址必须是通用confirm(代理),然后再跳转对应场景的结果页?

与next.js的限制有关。因为OTP链接中包含的HashToken需要转换为session,这就会涉及到cookie的写入,而在next.js中page时不允许写入cookie的,只能通过action、api或者在client component中写入。所以为了统一处理,就引入了通用confirm action。当然你也可以在结果页中强制用户手动再登录一次。

Good to know: HTTP does not allow setting cookies after streaming starts, so you must use .set() in a Server Action or Route Handler.
  • 注意在middleware中增加getUser的调用,因为每次页面访问都可能会更新cookie(refreshtoken)。
  • 业务逻辑中可能会有大量的getUser调用。处于性能考虑和频率限制的可能,next项目中建议使用react-cache进行一次封装。
  • 如何查看API文档?参考swagger文档,可以使用petstore查看。

结语

可以看到Supabase不仅提供了一个功能全面、安全可信赖的用户认证解决方案,而且其灵活的接入方式和丰富的社区支持,使其成为现代Web应用开发的优选。随着项目的深入,我们将继续探索Supabase的更多可能性,以期构建更加健壮和用户友好的应用。

参考资料

 

See all postsSee all posts