There are really two parts to WebAuthn, 1. The communication between the client (native iOS application or web browser) and the server as described by the WebAuthn specifications and 2. The communication between the client (native iOS application or web browser) and the authenticator (e.g. YubiKey 5Ci) as described by the CTAP2 protocol (Client-To Authenticator Protocol version 2).
This article provides a walkthrough of the communication between a native iOS app and a YubiKey 5Ci key as our authenticator plugged into an iPhone via the Apple Lightning connector.
The Web Authentication API (WebAuthn) enables strong first factor (passwordless) and multi-factor authentication built on public key cryptography using hardware devices like security keys, mobile phones, and other devices with built-in authenticators. Learn more here.
The WebAuthn (Web Authentication API) flow can be separated into two main parts, registration and authentication. I’ll cover each part and dive into the iOS Swift functions responsible for handling this flow, which involves talking to the YubiKey and the WebAuthn server.
In a password-based user registration flow, a server will typically present a form to a user asking for a username and password. The username/password would be sent to the server for storage and the password becomes the user credential. In WebAuthn, a server still binds a user to a credential, but this credential is known as a private-public keypair.
In the WebAuthn scenario, instead of asking for a username and password, the registration process involves the server (also known as a “Relying Party” (RP)) asking the user to create a new keypair as the WebAuthn credential.
Before we get started with Registration and Authentication, it’s important to know about the YubiKitManager and Key Sessions within YubiKit for iOS.
YubiKit (the Yubico SDK for iOS that the demo app is using) provides the majority of its functionality through a single instance called YubiKitManager which is retrieved by accessing the YubiKitManager.shared property. YubiKitManager is a singleton structured to provide a list of sessions, each one of them being dedicated to only one type of communication. For example, in the FIDO2 scenario, we’ll be dealing with keySession.sessionState for tracking the session state of the YubiKey (closed, closing, open, and opening) and keySession.fido2Service.keyState for tracking the state of FIDO2 implementation (idle, processingRequest, and touchKey). The property of the fido2Service.keyState should be access based on the keySession.sessionState of the YubiKey.
Your project will be much easier to complete if you add the two class and .xib file from the
MFIActionSheet folder of the demo to your project as these classes handle all the keySession state and dialog communication (w/ animation) for the user to take action, like ‘touch’ key during the user presence stage.
Once you add those classes to your project, you can then extend those classes from the active view controller to monitor the keySession.
Use Case: The user wants to create a new account with organization X via their mobile app.
Remember earlier, we talked about using WebAuthn for passwordless or multi-factor authentication? In the demo app, we’ll be sending a username + password to create a new account and using the WebAuthn API server (defaults to https://demo.yubico.com) to set up multi-factor authentication using our security key.
Using the Demo App, YubiKit provides the FIDO2 support through a single shared instance of fido2Service (of type YKFKeyFIDO2Service) which is a property of YKFKeySession. The FIDO2 service will receive requests and dispatch asynchronous calls to the YubiKey.
All the action starts in the FIDO2ViewController.swift class and the entire registration phase spans three functions:
Remember: Before we interact with the YubiKey, we first need to start the keySession and monitor the state of the key by extending the MFIKeyInteractionViewController class and implementing the keySessionStateDidChange and the fido2ServiceStateDidChange functions.
User wants to create (aka “Register”) a new account with organization X using basic auth (username/password).
Check out requestRegistration()
This is the callback function (from #1 Request Registration) that handles the response data (as an WebAuthnRegisterBeginResponse object) received from the authentication WebAuthn RP server. The object contains a challenge, information about the user, the Relying Party ID (RP ID), desired type of credential, the authenticator selection criteria (cross-platform, user verification, and resident key requirements) and attestation preference.
The handleRegistration(…) function takes the WebAuthnRegisterBeginResponse object and builds a makeCredentialRequest to be executed by the YubiKey.
Before sending the request, we first need to instantiate the fido2Service handled by the YubiKitManager. The fido2Service manages the key state during the registration and authentication FIDO2 ceremony and determines when to prompt the user to touch the key for user presence and track the request. We can then finally call fido2Service.execute(makeCredentialRequest requesting the YubiKey to create the public keypair.
In this step, we just send back the YubiKey credential object YKFKeyFIDO2MakeCredentialResponse as an Attestation Object to the WebAuthn server for validation. The attestation object contains both authenticator data and an attestation statement.
At this point, the iOS user has basic auth (username + password) and WebAuthn credentials as a form of 2FA and can authenticate with the authentication server using username + password + FIDO2 as a form of two-factor authentication.
Use Case: The same user wants to authenticate with basic auth (username + password) and use their registered YubiKey (we registered this YubiKey in the previous Registration phase) for multi-factor authentication.
Again, we’ll follow along with the Demo app provided by the YubiKit for iOS.
All the action remains in the FIDO2ViewController.swift class and the entire authentication phase also spans three functions:
This step performs basic authentication (username + password) for the user to authenticate with organization X via their iOS app. No keySession state or FIDO2 service for this method as it’s simply doing basic auth.
This function handles the challenge response from the WebAuthn server
after the user successfully authenticated with their username
During FIDO2 authentication, the user now needs to prove that they own the private key they registered by providing an assertion, which is retrieved from the YubiKey.
This function is responsible for getting the Assertion request _WebAuthnAuthenticateBeginResponse (as a PublicKeyCredentialRequestOptions) provided by the WebAuthn server. The WebAuthnAuthenticateBeginResponse contains a [challenge], [allowedCredentials], and a [timeout].
The function then builds a getAssertionRequest based on the WebAuthnAuthenticateBeginResponse and calls fido2Service.execute(getAssertionRequest) to retrieve the credential (from the YubiKey) generated during registration with a signature included.
This function is the callback from fido2Service.execute(getAssertionRequest) which is the response from the YubiKey and passes those results to the WebAuthn server for validation.
Once the server responds OK, we are now authenticated.
In addition to the registration and authentication functionality, the demo also provides examples for handling PIN verification when user verification is required during registration or authentication.
I hope this provides a good starting point for you to implement WebAuthn on iOS for multi-factor authentication.