Capacitor with iOS Passkey how to get registration data

146 views Asked by At

I'm implementing Passkey to my Ionic app and I couldn't find any plugin out there so I'm building my own plugin. Everything works till triggering the passkey on the native iOS, but I couldn't get the registration data from iOS. How can I get the data from the AppDelegate? My code is based on the Apple official documentation example: https://developer.apple.com/documentation/authenticationservices/connecting_to_a_service_with_passkeys.

In the example, it needed the AppDelegate to get the registration data delegate, however I couldn't manage to make my Capacitor Plugin to use the main AppDelegate. As a result, authorizationController does not run after passkey registration. Everything already works fine to get challenge from webview and pass it to native and trigger passkey prompt on the iOS device, just couldn't get the registration result data.

PasskeyPlugin.swift

@objc(PasskeyPlugin)
public class PasskeyPlugin: CAPPlugin {

    private let implementation = Passkey()
    
    @objc func create(_ call: CAPPluginCall) {
        let challenge: Data? = call.getString("challenge")!.data(using: .utf8)
        
        DispatchQueue.main.async {
            let signInViewController = SignInViewController(challenge: challenge!)
            self.bridge?.viewController?.present(signInViewController, animated: true, completion: nil)
        }
            
        call.resolve([
            "platform": "iOS Native Passkey",
        ])
    }
}

SignInViewController.swift

class SignInViewController: UIViewController {
    var challenge: Data!

    init(challenge: Data) {
        self.challenge = challenge
        print(self.challenge! as NSData)
        super.init(nibName: nil, bundle: nil)
    }

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        signInObserver = NotificationCenter.default.addObserver(forName: .UserSignedIn, object: nil, queue: nil) {_ in
            self.didFinishSignIn()
        }

        signInErrorObserver = NotificationCenter.default.addObserver(forName: .ModalSignInSheetCanceled, object: nil, queue: nil) { _ in
            self.showSignInForm()
        }

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

AccountManager.swift

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


class AccountManager: NSObject, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate {
    let domain = "com.domain.my"
    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)
            
            // The userID is the identifier for the user's account.
            // Hard coded for example purposes
            let userID = "d0a4bc91-2def-4567-8983-9188a4ca2048".data(using: .utf8)!

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

            let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ] )
            authController.delegate = self
            authController.presentationContextProvider = self
            authController.performRequests()
            isPerformingModalReqest = true
        } else {
            // Fallback on earlier versions
        }
    }

    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if #available(iOS 14.0, *) {
            let logger = Logger()
            
            if #available(iOS 15.0, *) {
                switch authorization.credential {
                case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
                    logger.log("A new passkey was registered: \(credentialRegistration)")
                    // Verify the attestationObject and clientDataJSON with your service.
                    // The attestationObject contains the user's new public key to store and use for subsequent sign-ins.
                    // let attestationObject = credentialRegistration.rawAttestationObject
                    // let clientDataJSON = credentialRegistration.rawClientDataJSON
                    
                    // After the server verifies the registration and creates the user account, sign in the user with the new account.
                    didFinishSignIn()
                case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
                    logger.log("A passkey was used to sign in: \(credentialAssertion)")
                    // Verify the below signature and clientDataJSON with your service for the given userID.
                    // let signature = credentialAssertion.signature
                    // let clientDataJSON = credentialAssertion.rawClientDataJSON
                    // let userID = credentialAssertion.userID
                    
                    // After the server verifies the assertion, sign in the user.
                    didFinishSignIn()
                case let passwordCredential as ASPasswordCredential:
                    logger.log("A password was provided: \(passwordCredential)")
                    // Verify the userName and password with your service.
                    // let userName = passwordCredential.user
                    // let password = passwordCredential.password
                    
                    // After the server verifies the userName and password, sign in the user.
                    didFinishSignIn()
                default:
                    fatalError("Received unknown authorization type.")
                }
            } else {
                // Fallback on earlier versions
            }
        } else {
            // Fallback on earlier versions
        }

        isPerformingModalReqest = false
    }
}

How can I get authorizationController to run after passkey prompt on the native device? Thank you in advance.

0

There are 0 answers