You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase Apple Sign-In重新认证失败求助:删除用户遇重复凭证错误

解决Apple登录Firebase重新认证的凭证错误问题

问题根源

你当前的实现存在两个核心错误:

  • 调用user.getIDToken()获取的是Firebase自身的ID Token,并非Apple官方颁发的身份凭证,无法用于生成Apple OAuth认证所需的有效凭据
  • 重新认证Apple登录用户必须触发用户再次通过系统授权弹窗验证身份,不能直接用现有Firebase凭证伪造Apple授权凭证

正确实现方案

重新认证Apple用户需要完整走一遍Apple的授权流程,获取新的Apple ID Token和对应随机nonce,再用这些信息生成有效的OAuth凭证,具体实现如下:

修改后的代码

// 生成随机nonce的工具方法(需和用户首次登录时使用的逻辑一致)
extension String {
    static func randomNonceString(length: Int = 32) -> String {
        precondition(length > 0)
        let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
        var result = ""
        var remainingLength = length
        
        while remainingLength > 0 {
            let randoms: [UInt8] = (0..<16).map { _ in
                var random: UInt8 = 0
                let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
                if errorCode != errSecSuccess {
                    fatalError("生成nonce失败,SecRandomCopyBytes返回错误码:\(errorCode)")
                }
                return random
            }
            
            randoms.forEach { random in
                guard remainingLength > 0, random < charset.count else { return }
                result.append(charset[Int(random)])
                remainingLength -= 1
            }
        }
        
        return result
    }
}

func deleteAccountApple() async throws {
    guard let user = Auth.auth().currentUser else {
        throw AccountDeletionError.notSignedIn
    }
    do {
        try await user.delete()
    } catch {
        let nsError = error as NSError
        let authError = AuthErrorCode.Code(rawValue: nsError.code)

        if authError == .requiresRecentLogin {
            do {
                // 重新触发Apple授权流程获取有效凭证
                let appleCredential = try await getAppleReauthenticationCredential()
                try await user.reauthenticate(with: appleCredential)
                try await user.delete()
            } catch {
                throw AccountDeletionError.failedToDeleteFirebaseAccount(error)
            }
        } else {
            throw AccountDeletionError.failedToDeleteFirebaseAccount(error)
        }
    }
}

private func getAppleReauthenticationCredential() async throws -> OAuthCredential {
    let rawNonce = String.randomNonceString()
    let appleIDProvider = ASAuthorizationAppleIDProvider()
    let request = appleIDProvider.createRequest()
    request.requestedScopes = [.fullName, .email]
    // 必须将原始nonce做SHA256哈希后传给Apple
    request.nonce = sha256(rawNonce)
    
    return try await withCheckedThrowingContinuation { continuation in
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = AuthDelegate(continuation: continuation, rawNonce: rawNonce)
        // 需确保当前类实现ASAuthorizationControllerPresentationContextProviding协议
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
    }
}

// 处理Apple授权结果的代理类
private class AuthDelegate: NSObject, ASAuthorizationControllerDelegate {
    private let continuation: CheckedContinuation<OAuthCredential, Error>
    private let rawNonce: String
    
    init(continuation: CheckedContinuation<OAuthCredential, Error>, rawNonce: String) {
        self.continuation = continuation
        self.rawNonce = rawNonce
        super.init()
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
              let appleIDToken = appleIDCredential.identityToken,
              let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            continuation.resume(throwing: AccountDeletionError.failedToGetAppleCredential)
            return
        }
        
        let credential = OAuthProvider.credential(withProviderID: "apple.com",
                                                  idToken: idTokenString,
                                                  rawNonce: rawNonce)
        continuation.resume(returning: credential)
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        continuation.resume(throwing: error)
    }
}

// SHA256哈希工具方法
private func sha256(_ input: String) -> String {
    let inputData = input.data(using: .utf8)!
    let hashedData = SHA256.hash(data: inputData)
    return hashedData.compactMap { String(format: "%02x", $0) }.joined()
}

// 实现弹窗展示上下文协议(需在你的视图控制器类中添加)
extension YourViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return self.view.window!
    }
}

关键注意事项

  • Nonce一致性:传给Apple的是SHA256哈希后的nonce,生成Firebase凭证时必须使用原始nonce,两者必须严格对应
  • 强制用户授权:Apple要求重新认证必须用户手动确认,无法静默完成,因此必须通过ASAuthorizationController触发系统授权弹窗
  • 凭证来源正确:生成OAuth凭证时必须使用Apple返回的identityToken,而非Firebase的ID Token

内容的提问来源于stack exchange,提问作者Sergey Zaycev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 20:55:40