充分利用 Sign in with Apple
Get the most out of Sign in with Apple
2020年6月24日
一句话判断
Sign in with Apple 不只是一个登录按钮——如果你只用了它做身份验证,你漏掉了”隐藏邮箱""凭证迁移”和”App Clip 静默登录”这些真正强大的能力。
这场 Session 讲了什么
Sign in with Apple 在 2019 年推出,WWDC 2020 的这场 Session 深入探讨了进阶使用场景。核心内容包括:如何正确处理 ID Token 验证、如何利用”隐藏邮箱”功能为用户提供隐私保护、如何在 Web 端实现 Sign in with Apple、以及如何在 App Clip 中实现无缝登录体验。
Session 首先回顾了 Sign in with Apple 的工作流程:用户点击授权按钮后,系统返回一个 authorization code,你的后端服务器用它向 Apple 验证用户身份,获得 ID Token(JWT 格式)。ID Token 中包含了用户的唯一标识符(sub claim)、邮箱和验证状态。2020 年新增了对 ID Token 撤销监听的支持——当用户在系统设置中停止使用 Sign in with Apple 时,你的服务器会收到通知。
隐藏邮箱(Hide My Email)是 Sign in with Apple 最被低估的功能。用户可以选择生成一个随机的 Apple 专属邮箱地址(如 abc123@privaterelay.appleid.com),所有发送到这个地址的邮件会被 Apple 转发到用户的真实邮箱。这对用户来说意味着不用暴露真实邮箱,对开发者来说需要正确处理这些特殊地址——不能假设它们是普通邮箱。
值得深挖的点
Credential State 与实时同步
ASAuthorizationAppleIDProvider.credentialState 可以检查用户当前的授权状态:.authorized(已授权)、.revoked(已撤销)和 .notFound(未找到)。你应该在每次应用启动时检查这个状态,如果用户撤销了授权,应该立即登出。2020 年新增了 credentialRevoked 通知,让你的应用能实时响应授权状态变化。
App Clip 中的静默 Sign in with Apple
App Clip 可以在用户不知情的情况下自动完成 Sign in with Apple——系统会静默地为用户创建一个临时身份。这意味着用户扫码进入 App Clip 时,你已经知道他是谁了,可以直接展示个性化内容。这是实现”零摩擦”体验的关键技术。
代码片段
import AuthenticationServices
// 检查用户授权状态
func checkCredentialState(for userID: String) {
let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: userID) { state, error in
DispatchQueue.main.async {
switch state {
case .authorized:
// 用户仍然授权,正常使用
print("用户授权有效")
case .revoked:
// 用户已撤销授权,需要登出
print("用户已撤销 Sign in with Apple")
self.handleSignOut()
case .notFound:
// 凭证未找到,需要重新登录
print("凭证未找到")
self.showLoginScreen()
default:
break
}
}
}
}
// 监听授权撤销通知
func observeCredentialRevocation() {
NotificationCenter.default.addObserver(
forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
object: nil, queue: .main
) { _ in
// 用户在系统设置中撤销了 Sign in with Apple
self.handleSignOut()
}
}
// 实现 Sign in with Apple 的完整流程
class SignInManager: NSObject, ASAuthorizationControllerDelegate {
func startSignIn() {
let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.performRequests()
}
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else {
return
}
// 获取用户信息
let userID = credential.user // 唯一标识符
let email = credential.email // 可能是隐藏邮箱
let fullName = credential.fullName // 只在首次授权时提供
// 获取 identity token 和 authorization code
let identityToken = credential.identityToken // JWT token
let authorizationCode = credential.authorizationCode
// 发送到后端验证
if let tokenData = identityToken,
let tokenString = String(data: tokenData, encoding: .utf8) {
sendToBackend(identityToken: tokenString,
authorizationCode: authorizationCode.flatMap {
String(data: $0, encoding: .utf8)
})
}
// 注意:email 可能是 xxx@privaterelay.appleid.com
// 这是隐藏邮箱,需要正确处理
if let email = email {
print("用户邮箱: \(email)")
print("是否为隐藏邮箱: \(email.contains("privaterelay.appleid.com"))")
}
}
func authorizationController(controller: ASAuthorizationController,
didCompleteWithError error: Error) {
print("Sign in with Apple 失败: \(error)")
}
}
// 后端验证 ID Token(伪代码)
/*
后端需要做的事情:
1. 接收客户端传来的 identityToken(JWT 格式)
2. 从 Apple 的公钥端点获取验证密钥
GET https://appleid.apple.com/auth/keys
3. 验证 JWT 签名
4. 检查以下 claims:
- iss: "https://appleid.apple.com"
- aud: 你的 Bundle ID
- exp: token 未过期
- sub: 用户的唯一标识符
5. 从 JWT 中提取用户信息并创建/更新账号
使用 authorizationCode 换取 refresh_token:
POST https://appleid.apple.com/auth/token
参数: client_id, client_secret, code, grant_type="authorization_code"
如果用户撤销授权,后端会收到:
POST 到你注册的 webhook URL
*/
最佳实践
- 每次应用启动时检查
credentialState,确保用户没有在系统设置中撤销授权 - 注册
credentialRevokedNotification通知,实时响应授权变化 - 后端必须验证 ID Token 的签名和 claims,不要盲目信任客户端传来的 token
- 正确处理隐藏邮箱地址,不要假设它和普通邮箱行为一致
- 首次授权时保存用户的姓名和邮箱,后续授权不会再提供这些信息
还有什么值得关注
- Sign in with Apple 现在支持 Web 端,通过 JavaScript API 可以在网页中实现相同的登录体验
- App Clip 的静默登录不需要用户点击任何按钮,系统自动完成身份验证
- 如果你的应用支持第三方登录(Google、Facebook),App Store 审核要求必须同时提供 Sign in with Apple 选项