Developer guide on passkeys for iOS
Learn how passkeys work and how to implement them in an iOS application.
27 Feb 2023 · 5 min read
Starting with iOS 16, Apple introduced a new security feature called passkeys. Passkeys are credentials built to eliminate security problems like weak or reused passwords, credential leaks and phishing attacks.
In this article, we'll look at how passkeys work and how to support them in an iOS application.

How do passkeys work?
Under the hood, passkeys are built on the WebAuthn standard which is a specification written by the W3C and FIDO with the participation of Google, Microsoft and others. It uses public-private key cryptography and allows servers to register and authenticate users using public key cryptography instead of a password.
A passkey is a digital credential, tied to a user account and a website or application. When a user wants to register for a new account, an identified device, for example an iPhone with Face ID identification, generates a public-private key pair.
The private key is securely retained on the device and synced with other devices, for example via iCloud Keychain, while the public key is shared with the server for a later challenge.
When the user returns to the app later to sign in, the server generates a one-time challenge to prove that their device has the private key associated with the public key. After accepting the challenge, the user's device signs it using the private key and sends the signature back to the server for validation. The server verifies it and in case of success allows the user to sign in.
Steps to support passkeys
The following steps are required to support passkeys in an iOS application:
- Setup associated domains with webcredentials service. Check out this guide on associated domain files to learn more.
- Set the textContentType on text fields to .username to let the system know when to provide passkey suggestions.
- Fetch a challenge from server which is a random string that is needed to prevent so called replay attacks.
- Create an authentication request which presents a sheet to the user asking to create a new credential by using the ASAuthorization API.
- Handle the response of the authentication request.
Let's look at the last two steps in more detail.
Create an authentication request
After obtaining a challenge from the server, we can use the ASAuthorization API to create an authentication request:
let credetialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider("example.com") // 1.let registrationRequest = credetialProvider.createCredentialRegistrationRequest(challenge: challenge, displayName: "tanaschita", userID: "tanaschita") // 2.let authController = ASAuthorizationController([platformKeyRequest]) // 3.authController.delegate = self // 4.authController.presentationContextProvider = self // 4.authController.performRequests() // 5.
Let's go through the code above step by step:
- Initialize a credential provider which accesses public-private key pairs stored in iCloud Keychain for registration or authentication with a relying party.
- Create a registration request to register for a new credential using platform authorization.
- Create a controller that manages authorization requests.
- Set the controller's delegate and presentation context provider to the object that responds to the request.
- Call the performRequests() method which presents a sheet to the user asking to create a new credential.
If a user is successfully registered and wants to login, we can use the code we already used to create new credentials with the only difference, that we now create an assertion request instead of a registration request:
let assertionRequest = credetialProvider.createCredentialAssertionRequestWithChallenge(challenge)
When providing an assertion request to the controller and the user has one or more credentials on the device, the device displays a sheet with the list of credentials to choose from. If there aren't any credentials on the device, we get an error. In this case, we can ask the user to register.
Handle the authentication response
To react to authorization success or errors, we can adopt the ASAuthorizationControllerDelegate which we assigned in the previous example:
func authorizationController(controller: controller, didCompleteWithAuthorization: authorization) {// Authorization completed successfully.}func authorizationController(controller: controller, didCompleteWithError: error) {// Handle the error.}
Based on the credential that is passed into authorizationController(controller:didCompleteWithAuthorization:), we can determine the request type and take further steps.
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {switch authorization.credential {case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration:// Finish registration.case let assertionResponse as ASAuthorizationPlatformPublicKeyCredentialAssertion:// Finish login.default:break}}
Depending on the request type, we can either finish the registration by sending credentials to the server or finish the login by sending the signature to the server for verification.

Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips