authentication
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAuthentication
认证
Implement authentication flows on iOS using the AuthenticationServices
framework, including Sign in with Apple, OAuth/third-party web auth,
Password AutoFill, and biometric authentication.
使用AuthenticationServices框架在iOS上实现认证流程,包括使用Apple登录、OAuth/第三方网页认证、密码自动填充和生物识别认证。
Contents
目录
Sign in with Apple
使用Apple登录
Add the "Sign in with Apple" capability in Xcode before using these APIs.
在使用这些API之前,需要在Xcode中添加“使用Apple登录”功能权限。
UIKit: ASAuthorizationController Setup
UIKit: ASAuthorizationController 配置
swift
import AuthenticationServices
final class LoginViewController: UIViewController {
func startSignInWithApple() {
let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
}
extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
view.window!
}
}swift
import AuthenticationServices
final class LoginViewController: UIViewController {
func startSignInWithApple() {
let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
}
extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
view.window!
}
}Delegate: Handling Success and Failure
代理:处理成功与失败
swift
extension LoginViewController: ASAuthorizationControllerDelegate {
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
guard let credential = authorization.credential
as? ASAuthorizationAppleIDCredential else { return }
let userID = credential.user // Stable, unique, per-team identifier
let email = credential.email // nil after first authorization
let fullName = credential.fullName // nil after first authorization
let identityToken = credential.identityToken // JWT for server validation
let authCode = credential.authorizationCode // Short-lived code for server exchange
// Save userID to Keychain for credential state checks
// See references/keychain-biometric.md for Keychain patterns
saveUserID(userID)
// Send identityToken and authCode to your server
authenticateWithServer(identityToken: identityToken, authCode: authCode)
}
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithError error: any Error
) {
let authError = error as? ASAuthorizationError
switch authError?.code {
case .canceled:
break // User dismissed
case .failed:
showError("Authorization failed")
case .invalidResponse:
showError("Invalid response")
case .notHandled:
showError("Not handled")
case .notInteractive:
break // Non-interactive request failed -- expected for silent checks
default:
showError("Unknown error")
}
}
}swift
extension LoginViewController: ASAuthorizationControllerDelegate {
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
guard let credential = authorization.credential
as? ASAuthorizationAppleIDCredential else { return }
let userID = credential.user // Stable, unique, per-team identifier
let email = credential.email // nil after first authorization
let fullName = credential.fullName // nil after first authorization
let identityToken = credential.identityToken // JWT for server validation
let authCode = credential.authorizationCode // Short-lived code for server exchange
// Save userID to Keychain for credential state checks
// See references/keychain-biometric.md for Keychain patterns
saveUserID(userID)
// Send identityToken and authCode to your server
authenticateWithServer(identityToken: identityToken, authCode: authCode)
}
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithError error: any Error
) {
let authError = error as? ASAuthorizationError
switch authError?.code {
case .canceled:
break // User dismissed
case .failed:
showError("Authorization failed")
case .invalidResponse:
showError("Invalid response")
case .notHandled:
showError("Not handled")
case .notInteractive:
break // Non-interactive request failed -- expected for silent checks
default:
showError("Unknown error")
}
}
}Credential Handling
凭证处理
ASAuthorizationAppleIDCredential| Property | Type | First Auth | Subsequent Auth |
|---|---|---|---|
| | Always | Always |
| | Provided if requested | |
| | Provided if requested | |
| | JWT (Base64) | JWT (Base64) |
| | Short-lived code | Short-lived code |
| | | |
Critical: and are provided ONLY on the first
authorization. Cache them immediately during the initial sign-up flow. If the
user later deletes and re-adds the app, these values will not be returned.
emailfullNameswift
func handleCredential(_ credential: ASAuthorizationAppleIDCredential) {
// Always persist the user identifier
let userID = credential.user
// Cache name and email IMMEDIATELY -- only available on first auth
if let fullName = credential.fullName {
let name = PersonNameComponentsFormatter().string(from: fullName)
UserProfile.saveName(name) // Persist to your backend
}
if let email = credential.email {
UserProfile.saveEmail(email) // Persist to your backend
}
}ASAuthorizationAppleIDCredential| 属性 | 类型 | 首次认证 | 后续认证 |
|---|---|---|---|
| | 始终返回 | 始终返回 |
| | 若请求则提供 | |
| | 若请求则提供 | |
| | JWT(Base64编码) | JWT(Base64编码) |
| | 短期有效代码 | 短期有效代码 |
| | | |
关键注意事项:和仅在首次认证时提供。请在初始注册流程中立即缓存这些值。如果用户后续删除并重新安装应用,将不会返回这些值。
emailfullNameswift
func handleCredential(_ credential: ASAuthorizationAppleIDCredential) {
// Always persist the user identifier
let userID = credential.user
// Cache name and email IMMEDIATELY -- only available on first auth
if let fullName = credential.fullName {
let name = PersonNameComponentsFormatter().string(from: fullName)
UserProfile.saveName(name) // Persist to your backend
}
if let email = credential.email {
UserProfile.saveEmail(email) // Persist to your backend
}
}Credential State Checking
凭证状态检查
Check credential state on every app launch. The user may revoke access at
any time via Settings > Apple Account > Sign-In & Security.
swift
func checkCredentialState() async {
let provider = ASAuthorizationAppleIDProvider()
guard let userID = loadSavedUserID() else {
showLoginScreen()
return
}
do {
let state = try await provider.credentialState(forUserID: userID)
switch state {
case .authorized:
proceedToMainApp()
case .revoked:
// User revoked -- sign out and clear local data
signOut()
showLoginScreen()
case .notFound:
showLoginScreen()
case .transferred:
// App transferred to new team -- migrate user identifier
migrateUser()
@unknown default:
showLoginScreen()
}
} catch {
// Network error -- allow offline access or retry
proceedToMainApp()
}
}每次应用启动时检查凭证状态。用户可能随时通过“设置 > Apple账户 > 登录与安全”撤销访问权限。
swift
func checkCredentialState() async {
let provider = ASAuthorizationAppleIDProvider()
guard let userID = loadSavedUserID() else {
showLoginScreen()
return
}
do {
let state = try await provider.credentialState(forUserID: userID)
switch state {
case .authorized:
proceedToMainApp()
case .revoked:
// User revoked -- sign out and clear local data
signOut()
showLoginScreen()
case .notFound:
showLoginScreen()
case .transferred:
// App transferred to new team -- migrate user identifier
migrateUser()
@unknown default:
showLoginScreen()
}
} catch {
// Network error -- allow offline access or retry
proceedToMainApp()
}
}Credential Revocation Notification
凭证撤销通知
swift
NotificationCenter.default.addObserver(
forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
object: nil,
queue: .main
) { _ in
// Sign out immediately
AuthManager.shared.signOut()
}swift
NotificationCenter.default.addObserver(
forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
object: nil,
queue: .main
) { _ in
// Sign out immediately
AuthManager.shared.signOut()
}Token Validation
令牌验证
The is a JWT. Send it to your server for validation --
never trust it client-side alone.
identityTokenswift
func sendTokenToServer(credential: ASAuthorizationAppleIDCredential) async throws {
guard let tokenData = credential.identityToken,
let token = String(data: tokenData, encoding: .utf8),
let authCodeData = credential.authorizationCode,
let authCode = String(data: authCodeData, encoding: .utf8) else {
throw AuthError.missingToken
}
var request = URLRequest(url: URL(string: "https://api.example.com/auth/apple")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(
["identityToken": token, "authorizationCode": authCode]
)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw AuthError.serverValidationFailed
}
let session = try JSONDecoder().decode(SessionResponse.self, from: data)
// Store session token in Keychain -- see references/keychain-biometric.md
try KeychainHelper.save(session.accessToken, forKey: "accessToken")
}Server-side, validate the JWT against Apple's public keys at
(JWKS). Verify: is
, matches your bundle ID, not passed.
https://appleid.apple.com/auth/keysisshttps://appleid.apple.comaudexpidentityTokenswift
func sendTokenToServer(credential: ASAuthorizationAppleIDCredential) async throws {
guard let tokenData = credential.identityToken,
let token = String(data: tokenData, encoding: .utf8),
let authCodeData = credential.authorizationCode,
let authCode = String(data: authCodeData, encoding: .utf8) else {
throw AuthError.missingToken
}
var request = URLRequest(url: URL(string: "https://api.example.com/auth/apple")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(
["identityToken": token, "authorizationCode": authCode]
)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw AuthError.serverValidationFailed
}
let session = try JSONDecoder().decode(SessionResponse.self, from: data)
// Store session token in Keychain -- see references/keychain-biometric.md
try KeychainHelper.save(session.accessToken, forKey: "accessToken")
}在服务器端,请根据Apple在(JWKS)提供的公钥验证JWT。需验证:为,与您的Bundle ID匹配,未过期。
https://appleid.apple.com/auth/keysisshttps://appleid.apple.comaudexpExisting Account Setup Flows
现有账户设置流程
On launch, silently check for existing Sign in with Apple and password
credentials before showing a login screen:
swift
func performExistingAccountSetupFlows() {
let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()
let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(
authorizationRequests: [appleIDRequest, passwordRequest]
)
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests(
options: .preferImmediatelyAvailableCredentials
)
}Call this in or on app launch. If no existing credentials
are found, the delegate receives a error -- handle it
silently and show your normal login UI.
viewDidAppear.notInteractive启动应用时,在显示登录界面之前,先静默检查是否存在已有的Apple登录和密码凭证:
swift
func performExistingAccountSetupFlows() {
let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()
let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(
authorizationRequests: [appleIDRequest, passwordRequest]
)
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests(
options: .preferImmediatelyAvailableCredentials
)
}在或应用启动时调用此方法。如果未找到现有凭证,代理会收到错误——请静默处理并显示常规登录界面。
viewDidAppear.notInteractiveASWebAuthenticationSession (OAuth)
ASWebAuthenticationSession(OAuth)
Use for OAuth and third-party authentication
(Google, GitHub, etc.). Never use for auth flows.
ASWebAuthenticationSessionWKWebViewswift
import AuthenticationServices
final class OAuthController: NSObject, ASWebAuthenticationPresentationContextProviding {
func startOAuthFlow() {
let authURL = URL(string:
"https://provider.com/oauth/authorize?client_id=YOUR_ID&redirect_uri=myapp://callback&response_type=code"
)!
let session = ASWebAuthenticationSession(
url: authURL, callback: .customScheme("myapp")
) { callbackURL, error in
guard let callbackURL, error == nil,
let code = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?
.queryItems?.first(where: { $0.name == "code" })?.value else { return }
Task { await self.exchangeCodeForTokens(code) }
}
session.presentationContextProvider = self
session.prefersEphemeralWebBrowserSession = true // No shared cookies
session.start()
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
ASPresentationAnchor()
}
}使用处理OAuth和第三方认证(如Google、GitHub等)。永远不要使用处理认证流程。
ASWebAuthenticationSessionWKWebViewswift
import AuthenticationServices
final class OAuthController: NSObject, ASWebAuthenticationPresentationContextProviding {
func startOAuthFlow() {
let authURL = URL(string:
"https://provider.com/oauth/authorize?client_id=YOUR_ID&redirect_uri=myapp://callback&response_type=code"
)!
let session = ASWebAuthenticationSession(
url: authURL, callback: .customScheme("myapp")
) { callbackURL, error in
guard let callbackURL, error == nil,
let code = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?
.queryItems?.first(where: { $0.name == "code" })?.value else { return }
Task { await self.exchangeCodeForTokens(code) }
}
session.presentationContextProvider = self
session.prefersEphemeralWebBrowserSession = true // No shared cookies
session.start()
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
ASPresentationAnchor()
}
}SwiftUI WebAuthenticationSession
SwiftUI WebAuthenticationSession
swift
struct OAuthLoginView: View {
@Environment(\.webAuthenticationSession) private var webAuthSession
var body: some View {
Button("Sign in with Provider") {
Task {
let url = URL(string: "https://provider.com/oauth/authorize?client_id=YOUR_ID")!
let callbackURL = try await webAuthSession.authenticate(
using: url, callback: .customScheme("myapp")
)
// Extract authorization code from callbackURL
}
}
}
}Callback types: for URL scheme redirects;
for universal link redirects (preferred).
.customScheme("myapp").https(host:path:)swift
struct OAuthLoginView: View {
@Environment(\.webAuthenticationSession) private var webAuthSession
var body: some View {
Button("Sign in with Provider") {
Task {
let url = URL(string: "https://provider.com/oauth/authorize?client_id=YOUR_ID")!
let callbackURL = try await webAuthSession.authenticate(
using: url, callback: .customScheme("myapp")
)
// Extract authorization code from callbackURL
}
}
}
}回调类型:用于URL Scheme重定向;用于通用链接重定向(推荐)。
.customScheme("myapp").https(host:path:)Password AutoFill Credentials
密码自动填充凭证
Use to offer saved keychain credentials
alongside Sign in with Apple:
ASAuthorizationPasswordProviderswift
func performSignIn() {
let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()
appleIDRequest.requestedScopes = [.fullName, .email]
let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(
authorizationRequests: [appleIDRequest, passwordRequest]
)
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
// In delegate:
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
handleAppleIDLogin(appleIDCredential)
case let passwordCredential as ASPasswordCredential:
// User selected a saved password from keychain
signInWithPassword(
username: passwordCredential.user,
password: passwordCredential.password
)
default:
break
}
}Set on text fields for AutoFill to work:
textContentTypeswift
usernameField.textContentType = .username
passwordField.textContentType = .password使用在Apple登录选项旁提供已保存的钥匙串凭证:
ASAuthorizationPasswordProviderswift
func performSignIn() {
let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()
appleIDRequest.requestedScopes = [.fullName, .email]
let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(
authorizationRequests: [appleIDRequest, passwordRequest]
)
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
// In delegate:
func authorizationController(
controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization
) {
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
handleAppleIDLogin(appleIDCredential)
case let passwordCredential as ASPasswordCredential:
// User selected a saved password from keychain
signInWithPassword(
username: passwordCredential.user,
password: passwordCredential.password
)
default:
break
}
}为文本框设置以启用自动填充:
textContentTypeswift
usernameField.textContentType = .username
passwordField.textContentType = .password##生物识别认证
使用LocalAuthentication框架中的实现Face ID / Touch ID作为登录或重新认证机制。如需使用生物识别访问控制(, )保护钥匙串项目,请参考技能。
LAContextSecAccessControl.biometryCurrentSetios-securityswift
import LocalAuthentication
func authenticateWithBiometrics() async throws -> Bool {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics, error: &error
) else {
throw AuthError.biometricsUnavailable
}
return try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Sign in to your account"
)
}必填项:在Info.plist中添加。缺少此键会导致Face ID设备上应用崩溃。
NSFaceIDUsageDescriptionBiometric Authentication
SwiftUI SignInWithAppleButton
Use from LocalAuthentication for Face ID / Touch ID as a
sign-in or re-authentication mechanism. For protecting Keychain items
with biometric access control (, ),
see the skill.
LAContextSecAccessControl.biometryCurrentSetios-securityswift
import LocalAuthentication
func authenticateWithBiometrics() async throws -> Bool {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics, error: &error
) else {
throw AuthError.biometricsUnavailable
}
return try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Sign in to your account"
)
}Required: Add to Info.plist. Missing this
key crashes on Face ID devices.
NSFaceIDUsageDescriptionswift
import AuthenticationServices
struct AppleSignInView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
SignInWithAppleButton(.signIn) { request in
request.requestedScopes = [.fullName, .email]
} onCompletion: { result in
switch result {
case .success(let authorization):
guard let credential = authorization.credential
as? ASAuthorizationAppleIDCredential else { return }
handleCredential(credential)
case .failure(let error):
handleError(error)
}
}
.signInWithAppleButtonStyle(
colorScheme == .dark ? .white : .black
)
.frame(height: 50)
}
}SwiftUI SignInWithAppleButton
常见错误
—
1. 未在应用启动时检查凭证状态
swift
import AuthenticationServices
struct AppleSignInView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
SignInWithAppleButton(.signIn) { request in
request.requestedScopes = [.fullName, .email]
} onCompletion: { result in
switch result {
case .success(let authorization):
guard let credential = authorization.credential
as? ASAuthorizationAppleIDCredential else { return }
handleCredential(credential)
case .failure(let error):
handleError(error)
}
}
.signInWithAppleButtonStyle(
colorScheme == .dark ? .white : .black
)
.frame(height: 50)
}
}swift
// DON'T: Assume the user is still authorized
func appDidLaunch() {
if UserDefaults.standard.bool(forKey: "isLoggedIn") {
showMainApp() // User may have revoked access!
}
}
// DO: Check credential state every launch
func appDidLaunch() async {
await checkCredentialState() // See "Credential State Checking" above
}Common Mistakes
2. 未执行现有账户设置流程
1. Not checking credential state on app launch
—
swift
// DON'T: Assume the user is still authorized
func appDidLaunch() {
if UserDefaults.standard.bool(forKey: "isLoggedIn") {
showMainApp() // User may have revoked access!
}
}
// DO: Check credential state every launch
func appDidLaunch() async {
await checkCredentialState() // See "Credential State Checking" above
}swift
// DON'T: Always show a full login screen on launch
// DO: Call performExistingAccountSetupFlows() first;
// show login UI only if .notInteractive error received2. Not performing existing account setup flows
3. 假设邮箱/姓名始终会被提供
swift
// DON'T: Always show a full login screen on launch
// DO: Call performExistingAccountSetupFlows() first;
// show login UI only if .notInteractive error receivedswift
// DON'T: Force-unwrap email or fullName
let email = credential.email! // Crashes on subsequent logins
// DO: Handle nil gracefully -- only available on first authorization
if let email = credential.email {
saveEmail(email) // Persist immediately
}3. Assuming email/name are always provided
4. 未实现ASAuthorizationControllerPresentationContextProviding
swift
// DON'T: Force-unwrap email or fullName
let email = credential.email! // Crashes on subsequent logins
// DO: Handle nil gracefully -- only available on first authorization
if let email = credential.email {
saveEmail(email) // Persist immediately
}swift
// DON'T: Skip the presentation context provider
controller.delegate = self
controller.performRequests() // May not display UI correctly
// DO: Always set the presentation context provider
controller.delegate = self
controller.presentationContextProvider = self // Required for proper UI
controller.performRequests()4. Not implementing ASAuthorizationControllerPresentationContextProviding
5. 将identityToken存储在UserDefaults中
swift
// DON'T: Skip the presentation context provider
controller.delegate = self
controller.performRequests() // May not display UI correctly
// DO: Always set the presentation context provider
controller.delegate = self
controller.presentationContextProvider = self // Required for proper UI
controller.performRequests()swift
// DON'T: Store tokens in UserDefaults
UserDefaults.standard.set(tokenString, forKey: "identityToken")
// DO: Store in Keychain
// See references/keychain-biometric.md for Keychain patterns
try KeychainHelper.save(tokenData, forKey: "identityToken")5. Storing identityToken in UserDefaults
审核清单
swift
// DON'T: Store tokens in UserDefaults
UserDefaults.standard.set(tokenString, forKey: "identityToken")
// DO: Store in Keychain
// See references/keychain-biometric.md for Keychain patterns
try KeychainHelper.save(tokenData, forKey: "identityToken")- 已在Xcode项目中添加“使用Apple登录”功能权限
- 已实现
ASAuthorizationControllerPresentationContextProviding - 每次应用启动时检查凭证状态()
credentialState(forUserID:) - 已注册观察者,并处理登出逻辑
credentialRevokedNotification - 已在首次认证时缓存和
email(不假设后续会提供)fullName - 已将发送到服务器验证,并非仅在客户端信任它
identityToken - 令牌存储在钥匙串中,而非UserDefaults或文件
- 在显示登录界面前调用了
performExistingAccountSetupFlows - 已处理错误场景:、
.canceled、.failed.notInteractive - 生物识别认证的Info.plist中已添加
NSFaceIDUsageDescription - 使用处理OAuth(而非
ASWebAuthenticationSession)WKWebView - 已为OAuth设置(如适用)
prefersEphemeralWebBrowserSession - 已为用户名/密码文本框设置以启用自动填充
textContentType
Review Checklist
参考资料
- "Sign in with Apple" capability added in Xcode project
- implemented
ASAuthorizationControllerPresentationContextProviding - Credential state checked on every app launch ()
credentialState(forUserID:) - observer registered; sign-out handled
credentialRevokedNotification - and
emailcached on first authorization (not assumed available later)fullName - sent to server for validation, not trusted client-side only
identityToken - Tokens stored in Keychain, not UserDefaults or files
- called before showing login UI
performExistingAccountSetupFlows - Error cases handled: ,
.canceled,.failed.notInteractive - in Info.plist for biometric auth
NSFaceIDUsageDescription - used for OAuth (not
ASWebAuthenticationSession)WKWebView - set for OAuth when appropriate
prefersEphemeralWebBrowserSession - set on username/password fields for AutoFill
textContentType
- 钥匙串与生物识别模式:
references/keychain-biometric.md - AuthenticationServices
- ASAuthorizationAppleIDProvider
- ASAuthorizationAppleIDCredential
- ASAuthorizationController
- ASWebAuthenticationSession
- ASAuthorizationPasswordProvider
- SignInWithAppleButton
- 使用Apple登录实现用户认证
References
—
- Keychain & biometric patterns:
references/keychain-biometric.md - AuthenticationServices
- ASAuthorizationAppleIDProvider
- ASAuthorizationAppleIDCredential
- ASAuthorizationController
- ASWebAuthenticationSession
- ASAuthorizationPasswordProvider
- SignInWithAppleButton
- Implementing User Authentication with Sign in with Apple
—