mcpField
A
authentication

authentication

github.com/dpearson2699安全1.2k3.9k 次安装13 周前更新
代码仓库

关于 authentication

为开发者提供使用 AuthenticationServices 等 API 实现 iOS 账户认证的指导,涵盖 Apple 登录、通行密钥、OAuth、密码自动填充和生物识别再认证,并强调凭据管理与服务端验证。

主要功能

  1. 01Apple 登录与凭据状态检查
  2. 02通行密钥注册和认证流程
  3. 03使用 ASWebAuthenticationSession 进行 OAuth
  4. 04密码自动填充凭据处理
  5. 05使用 LocalAuthentication 进行生物识别
  6. 06令牌验证与 Keychain 存储指导

适用场景

  1. 01为 iOS 登录流程添加 Apple 登录
  2. 02使用服务端挑战支持通行密钥
  3. 03通过第三方 OAuth 提供方进行认证
  4. 04账户敏感操作前重新验证身份

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill authentication

安装后在 Claude Code 中输入/authentication确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
keychain-biometric.md
passkeys.md

详细介绍

身份验证

使用 AuthenticationServices 框架在 iOS 上实现身份验证流程,包括使用 Apple 登录、通行密钥、OAuth/第三方网页 身份验证、密码自动填充和生物识别重新验证。

目录

使用 Apple 登录

使用这些 API 之前,先在 Xcode 中添加“使用 Apple 登录”功能。

UIKit:设置 ASAuthorizationController
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!
    }
}
代理:处理成功和失败
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
    ) {
        switch (error as? ASAuthorizationError)?.code {
        case .canceled, .notInteractive:
            break
        case .failed:
            showError("Authorization failed")
        default:
            showError("Authorization failed: \(error.localizedDescription)")
        }
    }
}

凭据处理

凭据数据必需的处理
user持久化此稳定的团队级标识符,以便检查凭据状态。
email, fullName这些可选值仅在首次授权时提供;请立即缓存。
identityToken, authorizationCode将它们发送到服务器进行验证或交换;切勿将其视为客户端身份验证凭据。

realUserStatus 只能作为防欺诈信号,不能作为身份验证凭据。

凭据状态检查

每次应用启动时都检查凭据状态。用户可以随时通过“设置”>“Apple 账户”>“登录与安全”撤销访问权限。

func checkCredentialState() {
    let provider = ASAuthorizationAppleIDProvider()
    guard let userID = loadSavedUserID() else {
        showLoginScreen()
        return
    }

    provider.getCredentialState(forUserID: userID) { state, _ in
        DispatchQueue.main.async {
            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()
            }
        }
    }
}
凭据撤销通知
NotificationCenter.default.addObserver(
    forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
    object: nil,
    queue: .main
) { _ in
    // Sign out immediately
    AuthManager.shared.signOut()
}

令牌验证

identityToken 是一个 JWT。将其发送到服务器进行验证—— 切勿仅在客户端信任它。

在服务器端,根据 Apple 的公钥 https://appleid.apple.com/auth/keys(JWKS)验证 JWT。确认:iss 为 https://appleid.apple.com,aud 与你的 bundle ID 匹配,且 exp 尚未 过期。在服务器上交换短期有效的授权码,并将生成的应用会话令牌存储在 Keychain 中。

现有帐户设置流程

应用启动时,在显示登录界面之前,静默检查现有的“使用 Apple 登录”凭据和密码 凭据:

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 中或应用启动时调用此方法。如果未找到现有凭据,代理会收到 .notInteractive 错误——请静默处理该错误,并显示常规登录界面。

通行密钥

仅对已配置 webcredentials: 关联域和 AASA 条目的依赖方域使用通行密钥。注册和断言都需要新的服务器质询、ASAuthorizationPlatformPublicKeyCredentialProvider、具有活动展示锚点的授权控制器,以及在发放会话前进行服务器端验证。

加载 references/passkeys.md,查看规范的注册、断言、结果处理、自动填充辅助和实体安全密钥流程。

ASWebAuthenticationSession(OAuth)

使用 ASWebAuthenticationSession 进行 OAuth 和第三方身份验证 (Google、GitHub 等)。切勿使用 WKWebView 处理身份验证流程。

import AuthenticationServices

final class OAuthController: NSObject, ASWebAuthenticationPresentationContextProviding {
    private weak var presentationAnchor: ASPresentationAnchor?

    init(presentationAnchor: ASPresentationAnchor) {
        self.presentationAnchor = presentationAnchor
    }

    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 {
        guard let presentationAnchor else {
            fatalError("ASWebAuthenticationSession needs the active window")
        }
        return presentationAnchor
    }
}

在 SwiftUI 中,使用 @Environment(\.webAuthenticationSession),并通过 .customScheme("myapp") 或 .https(host:path:) 调用 authenticate(using:callback:preferredBrowserSession:additionalHeaderFields:);只有在提供方流程需要避免使用共享浏览器 Cookie 时,才优先选择 .ephemeral。

密码自动填充凭据

使用 现有帐户设置流程 中的单个控制器,在“使用 Apple 登录”的同时提供 ASAuthorizationPasswordProvider。在该控制器的代理中处理 ASPasswordCredential。

在文本字段上设置 textContentType,以启用自动填充:

usernameField.textContentType = .username
passwordField.textContentType = .password

生物识别认证

使用 LocalAuthentication 中的 LAContext,在显示账户设置或启动敏感操作前进行本地重新认证。不要将返回的 Bool 视为解锁已存储机密信息的凭据;应改用 Keychain 访问控制保护机密信息。请参阅 references/keychain-biometric.md,了解规范的 LAContext、回退、 SecAccessControl 和 .biometryCurrentSet 用法。

**必需:**在 Info.plist 中添加 NSFaceIDUsageDescription。缺少此键会导致 Face ID 设备崩溃。

安全边界

此技能负责面向用户的账户认证:使用 Apple 登录、通行密钥、密码自动填充、ASAuthorizationController、OAuth 会话展示、凭据状态和本地生物识别重新认证。深层安全工作应交由 swift-security 处理:Keychain 架构/迁移、CryptoKit、Secure Enclave、证书固定/信任、Keychain 共享、存储加固,以及 OWASP MASVS/MASTG。此处仅保留最低限度的存储要求:令牌和机密信息应存放在 Keychain 中;单独调用 LAContext.evaluatePolicy 不得释放受保护的机密信息。

SwiftUI SignInWithAppleButton

当登录界面使用 SwiftUI 时,在 SwiftUI 视图中使用 SignInWithAppleButton。请求 .fullName 和 .email,将成功结果向下转型为 ASAuthorizationAppleIDCredential,并传递给共享的 令牌验证流程。使用 .signInWithAppleButtonStyle(...) 设置样式。

常见错误

  • 认为已保存本地会话就表示 Apple ID 凭据仍然有效。在应用启动时检查凭据状态,并处理撤销通知。
  • 未尝试现有账户设置流程就显示完整登录界面。将 .notInteractive 视为正常的“无本地凭据”路径。
  • 对 email 或 fullName 使用强制解包。首次授权时缓存它们,并处理之后出现的 nil。
  • 创建 ASAuthorizationController 时未提供展示上下文代理。授权界面需要当前活动的展示锚点。
  • 将身份令牌、授权码、访问令牌、密码或通行密钥服务器状态存储在 UserDefaults、文件或 Core Data 中。机密信息应存放在 Keychain 中;依赖方的通行密钥验证应保留在服务器端。
  • 添加通行密钥请求时未为依赖方域配置 webcredentials: Associated Domains,或尝试将应用原生通行密钥用于无关的网站。
  • 将认证工作扩展到 CryptoKit、Secure Enclave、证书固定或 OWASP MASVS。应将这些工作交由 swift-security 处理。

审核清单

  • 已在 Xcode 项目中添加“使用 Apple 登录”功能
  • 已实现 ASAuthorizationControllerPresentationContextProviding
  • 每次应用启动时均检查凭据状态(getCredentialState(forUserID:completion:))
  • 已注册 credentialRevokedNotification 观察器;已处理登出
  • 首次授权时已缓存 email 和 fullName(不假设之后仍可获取)
  • 将 identityToken 发送到服务器进行验证,不仅在客户端信任
  • 令牌存储在 Keychain 中,而非 UserDefaults 或文件中
  • 在显示登录界面前已调用 performExistingAccountSetupFlows
  • 已处理错误情况:.canceled、.failed、.notInteractive
  • 生物识别认证所需的 NSFaceIDUsageDescription 已添加到 Info.plist
  • OAuth 使用 ASWebAuthenticationSession(而非 WKWebView)
  • 在适当情况下为 OAuth 设置 prefersEphemeralWebBrowserSession
  • 已为用户名/密码字段设置 textContentType,以支持自动填充
  • 通行密钥依赖方已配置 webcredentials: Associated Domains
  • 通行密钥注册/断言质询由服务器提供,并在服务器端验证
  • 深层 Keychain、CryptoKit、Secure Enclave、证书固定和 MASVS 工作已交由 swift-security 处理

参考资料

评论

支持 Markdown