使用 Supabase 增强 Sign in with Apple 认证服务
了解如何使用 Supabase 增强“通过 Apple 登陆”服务,包括但不限于持久化用户数据、自动验证用户身份和维持会话。

使用 Sign in with Apple 的难点
在「使用 Sign in with Apple 进行身份认证」这篇文章中讲过,使用 Sign in with Apple 服务,开发者需要处理的工作不仅仅是身份验证,一般包含以下几个步骤:
- 使用 Apple 提供的 Sign in with Apple 完成身份验证
- 开发者需要在服务端验证 identityToken 有效性(通过使用 Apple 提供的 API)
- 开发者需要实现后续会话的维护(例如令牌刷新)
- 开发者需要自行存储用户的 Emial 和 Username(因为只提供一次)
因此,开发者仍然需要至少一台服务器,来处理以上工作。
Supabase 的核心就是简化后续几个步骤的工作,并且无需格外的服务器,直接从 Apple → Supabase。
使用 Supabase 简化工作量
理解 Supabase 与 Sign in with Apple 服务集成原理
整个 Sign in with Apple 与 Supabase 集成的核心流程,分为两个关键步骤:
- Apple 负责用户身份验证
- 仍然使用 Sign in with Apple 提供的
SignInWithAppleButton
组件,以及相关的认证服务和流程 —— 因为这是一套成熟的认证系统。 - 完成认证之后,将获取到的 identity token(身份令牌)提供给 Supabase 提供的
signInWithIdToken()
方法即可。
- Supabase 负责后续身份验证和令牌刷新
- 自动验证令牌 - Supabase 的
signInWithIdToken()
方法会自动负责验证 identityToken 的有效性。 - 自动提取用户信息 - 自动从
identityToken
中解析 userIdentifier、email、username 等。 - 自动存储用户信息。
- 自动维护会话、刷新令牌。
Apple 提供的 identity token 仅用于 Supabase 的初始化验证,验证通过之后,后续的身份认证令牌,都是用 Supabase 自己的令牌。
开始使用 Supabase
安装 Supabase-swift 库
Supabase-swift
在 24 年正式推出了 v2.0 版本,现在可以更加方便、简单的将 Supabase 认证集成到 SwiftUI 应用中。
在 SPM 中安装依赖:
https://github.com/supabase/supabase-swift.git
获取 Project URL 和 Project Key
需要通过传递Project URL
和Project Key
,来初始化SupabaseClient
实例。
可以在Project Settings
→ DataAPI
中找到:

在 Sapubase 网页端启用 Apple 登陆
在 Project -> Authentication -> Sign In/Up 中,启用 Apple 选项:


这里的 Client ID 就是 App 的 Identifier,可以在这里找到:https://developer.apple.com/account/resources/identifiers/list
根据官方文档显示,如果只构建 iOS
端应用,无需填写 Secret Key
:

初始化 Client 实例
基本的实例化方式,参考自:https://supabase.com/docs/reference/swift/auth-api
import Supabase
let client = SupabaseClient(
supabaseURL: URL(string: "https://xyzcompany.supabase.co")!,
supabaseKey: "public-anon-key")
SupabaseClient 提供了不同的参数用于实现自定义的初始化。详情可以查询:https://supabase.com/docs/reference/swift/initializing

进行认证
进行认证实际上非常简单,我们只需要正常调用 SignInWithAppleButton 组件进行认证,并在回调中将 identityToken
传递给 Supabase 的 signInWithIdToken()
方法即可。
signInWithIdToken
会自动验证 identityToken,并将用户信息存储到 user 表中(这个表在项目初始化时即默认创建)。
官方示例代码:
import SwiftUI
import AuthenticationServices
import Supabase
struct SignInView: View {
let client = SupabaseClient(supabaseURL: URL(string: "your url")!, supabaseKey: "your anon key")
var body: some View {
SignInWithAppleButton { request in
request.requestedScopes = [.email, .fullName]
} onCompletion: { result in
Task {
do {
guard let credential = try result.get().credential as? ASAuthorizationAppleIDCredential
else {
return
}
guard let idToken = credential.identityToken
.flatMap({ String(data: $0, encoding: .utf8) })
else {
return
}
try await client.auth.signInWithIdToken(
credentials: .init(
provider: .apple,
idToken: idToken
)
)
} catch {
dump(error)
}
}
}
.fixedSize()
}
}
上述代码直接在 View 组件中创建 SupabaseClient
实例,并调用 signInWithIdToken
方法。在实际项目中,更推荐创建一个 SupabaseManager 或者 AuthService 类来统一处理,我们会在下面讨论这个主题。
重启 App 后是否需要再次登陆?
以上代码并没有显式的将用户的 Session 持久化。因此,如果用户关闭了 App,如何保证重新打开 App 时,无需再次进行认证?
Supabase Swift SDK 默认会将会话信息(包括访问令牌和刷新令牌)自动存储在 iOS 的 Keychain 中,这些数据即使在 App 完全关闭后也会保留。
当 App 重新启动时,可以通过调用supabase.auth.session()
方法恢复会话。该方法尝试获取现有会话并恢复用户状态,如果找到有效会话,就不需要重新登录。
获取并存储 Apple ID 的用户名
Supabase 会默认创建一个 User 表。
在使用 signInWithIdToken()
方法完成 Sign in with Apple 认证之后,在 User 表中可以正确获取并存储 Email,但是无法自动获取并存储用户名。

相同的问题,其他人也有遇到:
解决方法是:
手动使用 updateUser() 方法来补充这些信息
——updateUser()
是 JS SDK 提供的方法。
在 Swift SDK 中,
- 应当使用
update()
方法,而不是updateUser()
update()
方法并未提供专门的 userName 字段,只能通过 data 自定义字段
try? await supabase.auth.update(
user: UserAttributes(data: ["full_name": .string(fullName)])
)
更详细的可以参考:https://supabase.com/docs/reference/javascript/v1/auth-update
自定义的 data 数据,存储在 user
表的 raw_user_meta_data
列中:

认证后数据处理
如何恢复会话?
Supabase 提供了两个用于刷新会话的方法:
- supabase.auth.session
直接调用supabase.auth.session()
方法,会自动返回会话。如果有必要,会自动刷新它。
try await supabase.auth.session
- supabase.auth.refreshSession()
无论当前会话是否已过期,此方法都会刷新会话。
let session = try await supabase.auth.refreshSession()
let session = try await supabase.auth.refreshSession(refreshToken: "custom-refresh-token")
如何注销登陆?
直接调用 supabase.auth 提供的signOut()
方法即可:

try await supabase.auth.signOut()
如何删除用户账号?
删除账号功能,必须调用 supabase.auth.admin 下的方法。并且,必须使用 serviceRoleKey 创建的 SupabaseClient 实例,
import Supabase
let supabase = SupabaseClient(
supabaseURL: supabaseURL,
supabaseKey: serviceRoleKey
)
// Access auth admin api
let adminAuthClient = supabase.auth.admin
普通用户无权调用删除方法,否则会遇到服务器报错:

根据 Supabase 文档的建议,不应当在客户端直接调用 deleteUser()
方法,应当创建一个中间服务器,并在该服务器上保存 serviceRoleKey 以及调用 deleteUser
方法。
deleteUser()
方法需要用户的 ID,该 ID 映射到auth.users.id
列。
try await supabase.auth.admin.deleteUser(
id: "715ed5db-f090-4b8c-a067-640ecee36aa0"
)

如何查询用户信息?
如何增删改查用户信息?
官方说明:

使用 Supabase 进行权限管理
Supabase 提供的 supabase.auth.userIdentities()
方法,可以返回用户的身份信息:

Supabase 在中国的支持情况
这个部分需要添加后,经过一段时间的实际测试才知道。