Ping SDKs

Authenticate by using a WebAuthn device

After the user’s mobile device has been registered in PingAM, the device can be used as an authenticator with its registered key pair through the WebAuthn Authentication node, which is returned as a WebAuthnAuthenticationCallback by the Ping SDK for iOS.

If the device supports Passkeys, the operating system displays passkeys that can be used:

ios select passkey en
Figure 1. Select a passkey to use for WebAuthn

Note that removing credentials stored on the client device does not remove the associated data from the server. You will need to register the device again after removing credentials from the client.

With WebAuthnAuthenticationCallback, you must implement the following protocol method to handle the authentication process:

public protocol PlatformAuthenticatorAuthenticationDelegate {
    func selectCredential(keyNames: [String], selectionCallback: @escaping WebAuthnCredentialsSelectionCallback)
}

As part of authentication process, the SDK provides the WebAuthnAuthenticationCallback for authenticating the device as a credential.

if let authenticationCallback = callback as? WebAuthnAuthenticationCallback {

    authenticationCallback.delegate = self

    // Note that the `Node` parameter in `.authenticate()` is an optional parameter.
    // If the node is provided, the SDK automatically returns the assertion
    // in the HiddenValueCallback.

    authenticationCallback.authenticate(
        node: node,
        window: UIApplication.shared.windows.first,
        preferImmediatelyAvailableCredentials: false,
        usePasskeysIfAvailable: true
    ) { (assertion) in
        // Authentication is successful
        // Submit the Node using Node.next()
    } onError: { (error) in
        // An error occurred during the authentication process
        // Submit the Node using Node.next()
    }
}

Set the usePasskeysIfAvailable parameter to true to enable passkeys on supported devices.

When passkeys are enabled, the device offers the ability to sign in using passkeys stored an a separate supported device, by first scanning a QR code.

ios passkeys from another device en
Figure 2. Use a stored passkey from another device by first scanning the QR code

To prevent this behavior and only accept passkeys stored on the initial client device, set the preferImmediatelyAvailableCredentials parameter to true.

The WebAuthnAuthenticationCallback.authenticate() method has an optional parameter, Node.

If the current node contains both WebAuthnAuthenticationCallback and HiddenValueCallback callbacks, and this node is passed as a parameter to the WebAuthnAuthenticationCallback.authenticate() method, then the SDK automatically returns the outcome of the authentication process for both success and failure into the designated HiddenValueCallback.

If the node is not provided, the assertion or error outcome must be set manually.

Select credentials

The func selectCredential() method is invoked when Username from device is enabled in the WebAuthn Authentication node. This feature requires that Username to device is enabled in the WebAuthn Registration node as well. With these options enabled, the registered key pair is associated with the username, and the SDK can present a list of registered keys to the user to continue the authentication process without collecting a username.

The keyName is an array of strings constructed as <User’s displayName> <Registered Timestamp>.

You may alter the string value, and present the altered value to the user, but you must return the key name string as it was provided in the original array.

func selectCredential(keyNames: [String], selectionCallback: @escaping WebAuthnCredentialsSelectionCallback) {
    let actionSheet = UIAlertController(title: "Select Credentials", message: nil, preferredStyle: .actionSheet)

    for keyName in keyNames {
        actionSheet.addAction(UIAlertAction(title: keyName, style: .default, handler: { (action) in
            selectionCallback(keyName)
        }))
    }

    actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
        selectionCallback(nil)
    }))

    guard let vc = self.viewController else {
        return
    }

    if actionSheet.popoverPresentationController != nil {
        actionSheet.popoverPresentationController?.sourceView = self
        actionSheet.popoverPresentationController?.sourceRect = self.bounds
    }

    DispatchQueue.main.async {
        viewController.present(actionSheet, animated: true, completion: nil)
    }
}