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

Capacitor iOS:如何从原生ViewController向JS返回Passkey认证结果

解决Capacitor中Passkey凭证从原生ViewController传回JS的问题

核心问题是你当前在someFunction里同步调用了call.resolve,但Passkey认证是异步操作,必须等凭证获取完成后再执行call.resolve。以下是具体修改步骤:


1. 修改SamplePlugin.swift:保存PluginCall并添加回调逻辑

需要把CAPPluginCall保存为实例变量,同时给SignInViewController添加回调闭包,用于接收凭证数据:

import Foundation
import Capacitor

@objc(SamplePlugin)
public class SamplePlugin: CAPPlugin {
    // 保存当前的PluginCall,等待异步操作完成后处理
    private var currentCall: CAPPluginCall?
   
    @objc func someFunction(_ call: CAPPluginCall) {
        let value = call.getString("value") ?? ""
        let value2 = call.getString("value2") ?? ""
        
        guard let challengeStr = call.getString("challenge"),
              let challenge = challengeStr.data(using: .utf8) else {
            call.reject("Invalid challenge parameter")
            return
        }
        
        // 保存当前call
        currentCall = call
        
        DispatchQueue.main.async {
            let signInViewController = SignInViewController(challenge: challenge)
            // 添加凭证接收回调
            signInViewController.onCredentialReceived = { [weak self] credentialData in
                guard let self = self, let call = self.currentCall else { return }
                signInViewController.dismiss(animated: true)
                // 把凭证和其他参数一起传回JS
                call.resolve([
                    "value": value,
                    "testval": value2,
                    "platform": "ios",
                    "credential": credentialData
                ])
                self.currentCall = nil
            }
            
            // 添加取消操作回调
            signInViewController.onCancel = { [weak self] in
                guard let self = self, let call = self.currentCall else { return }
                signInViewController.dismiss(animated: true)
                call.reject("Sign-in canceled by user")
                self.currentCall = nil
            }
            
            self.bridge?.viewController?.present(signInViewController, animated: true, completion: nil)
        }
    }
}

2. 修改SignInViewController.swift:添加回调闭包并接收凭证数据

给ViewController添加两个回调闭包,分别处理成功获取凭证和取消操作,同时修改通知监听逻辑接收AccountManager传来的凭证:

class SignInViewController: UIViewController {
    var challenge: Data!
    // 成功获取凭证的回调
    var onCredentialReceived: (([String: Any]) -> Void)?
    // 取消操作的回调
    var onCancel: (() -> Void)?
    
    private var signInObserver: NSObjectProtocol?
    private var signInErrorObserver: NSObjectProtocol?
    
    init(challenge: Data) {
        self.challenge = challenge
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // 修改监听,接收通知携带的凭证数据
        signInObserver = NotificationCenter.default.addObserver(forName: .UserSignedIn, object: nil, queue: nil) { [weak self] notification in
            guard let self = self, let credentialData = notification.object as? [String: Any] else { return }
            self.didFinishSignIn(with: credentialData)
        }

        signInErrorObserver = NotificationCenter.default.addObserver(forName: .ModalSignInSheetCanceled, object: nil, queue: nil) { [weak self] _ in
            self?.onCancel?()
        }

        guard let window = self.view.window else { fatalError("The view was not in the app's view hierarchy!") }
        
        let userName = "Name"
        (UIApplication.shared.delegate as? AppDelegate)?.accountManager.signUpWith(userName: userName, challenge: self.challenge, anchor: window)
    }

    func didFinishSignIn(with credentialData: [String: Any]) {
        // 调用回调把凭证传回Plugin
        onCredentialReceived?(credentialData)
    }
    
    // 移除通知监听,避免内存泄漏
    deinit {
        if let observer = signInObserver {
            NotificationCenter.default.removeObserver(observer)
        }
        if let observer = signInErrorObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }
}

3. 修改AccountManager.swift:传递凭证数据到通知

在获取到Passkey凭证后,把凭证转换为可序列化的字典,通过Notification传递给ViewController:

import AuthenticationServices
import Foundation
import os

extension NSNotification.Name {
    static let UserSignedIn = Notification.Name("UserSignedInNotification")
    static let ModalSignInSheetCanceled = Notification.Name("ModalSignInSheetCanceledNotification")
}

class AccountManager: NSObject, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate {
    let domain = "com.domain.app"
    var authenticationAnchor: ASPresentationAnchor?
    var isPerformingModalReqest = false
    
    func signUpWith(userName: String, challenge: Data, anchor: ASPresentationAnchor) {
        self.authenticationAnchor = anchor
        
        if #available(iOS 15.0, *) {
            let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain)
            
            let userID = "d0a4bc91-2def-4567-8983-9188a4ca2048".data(using: .utf8)!

            let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest(challenge: challenge,
                                                                                                      name: "User Fullname", userID: userID)

            let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ] )
            authController.delegate = self
            authController.presentationContextProvider = self
            authController.performRequests()
            isPerformingModalReqest = true
        } else {
            // 早期版本兼容
        }
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        isPerformingModalReqest = false
        
        if #available(iOS 15.0, *) {
            let logger = Logger()
            var credentialData: [String: Any] = [:]
            
            switch authorization.credential {
            case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
                logger.log("A new passkey was registered: \(credentialRegistration)")
                // 将凭证转换为可序列化的字典
                credentialData = [
                    "type": "registration",
                    "userID": credentialRegistration.userID.base64EncodedString(),
                    "rawID": credentialRegistration.rawID.base64EncodedString(),
                    "attestationObject": credentialRegistration.attestationObject.base64EncodedString(),
                    "clientDataJSON": credentialRegistration.clientDataJSON.base64EncodedString()
                ]
            case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
                logger.log("A passkey was used to sign in: \(credentialAssertion)")
                credentialData = [
                    "type": "assertion",
                    "userID": credentialAssertion.userID.base64EncodedString(),
                    "rawID": credentialAssertion.rawID.base64EncodedString(),
                    "authenticatorData": credentialAssertion.authenticatorData.base64EncodedString(),
                    "signature": credentialAssertion.signature.base64EncodedString(),
                    "clientDataJSON": credentialAssertion.clientDataJSON.base64EncodedString()
                ]
            case let passwordCredential as ASPasswordCredential:
                logger.log("A password was provided: \(passwordCredential)")
                credentialData = [
                    "type": "password",
                    "user": passwordCredential.user,
                    "password": passwordCredential.password
                ]
            default:
                fatalError("Received unknown authorization type.")
            }
            
            // 把凭证数据传递给回调
            didFinishSignIn(with: credentialData)
        } else {
            // 早期版本兼容
        }
    }
    
    // 修改方法,接收凭证数据并通过通知传递
    func didFinishSignIn(with credentialData: [String: Any]) {
        print("didFinish")
        NotificationCenter.default.post(name: .UserSignedIn, object: credentialData)
    }
    
    // 处理授权失败的情况
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        isPerformingModalReqest = false
        print("Authorization error: \(error.localizedDescription)")
        NotificationCenter.default.post(name: .ModalSignInSheetCanceled, object: nil)
    }
    
    // 实现presentationContextProvider的方法
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return authenticationAnchor!
    }
}

关键说明

  • 异步处理:Passkey认证是异步操作,必须等待凭证获取完成后再调用call.resolve,不能在presentViewController后立即执行。
  • 闭包回调:通过给ViewController添加回调闭包,实现从ViewController到Plugin的通信,避免全局变量导致的内存泄漏。
  • 数据序列化:把ASAuthorization的凭证对象转换为base64编码的字符串或字典,确保能传递到JS端(JS无法直接接收原生对象)。
  • 内存管理:使用weak self避免循环引用,在ViewController销毁时移除通知监听。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:45:54