Logo for tanaschita.com

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.

Sponsorship logo
Capture HTTP(s) traffic with Proxyman
Proxyman - Your ultimate man-in-the-middle proxy to effortlessly capture, inspect, and manipulate HTTP(s) traffic on macOS, Windows, iOS, and Android devices.
Get started for free

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:

  1. Setup associated domains with webcredentials service. Check out this guide on associated domain files to learn more.
  2. Set the textContentType on text fields to .username to let the system know when to provide passkey suggestions.
  3. Fetch a challenge from server which is a random string that is needed to prevent so called replay attacks.
  4. Create an authentication request which presents a sheet to the user asking to create a new credential by using the ASAuthorization API.
  5. 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:

  1. Initialize a credential provider which accesses public-private key pairs stored in iCloud Keychain for registration or authentication with a relying party.
  2. Create a registration request to register for a new credential using platform authorization.
  3. Create a controller that manages authorization requests.
  4. Set the controller's delegate and presentation context provider to the object that responds to the request.
  5. 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.

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.

Sponsorship logo
Capture HTTP(s) traffic with Proxyman
Proxyman - Your ultimate man-in-the-middle proxy to effortlessly capture, inspect, and manipulate HTTP(s) traffic on macOS, Windows, iOS, and Android devices.
Get started for free


Image of a reading marmot

Like to support my work?

Say hi

Related tags

Articles with related topics




Developer guide on keychain for iOS

Learn how to securely store passwords with Apple's Keychain API.

23 Jan 2023 · 4 min read

Latest articles and tips

© 2023 tanaschita.com

Privacy policy