Passwordless Authentication With Passkey: How It Works and Why It Matters

Heritage Holdings Tech Blog
11 min readFeb 23, 2024

--

Passwords are the most common method of authenticating users on the web. However, they have several drawbacks. When asked about passwords, many people will express that they are difficult to remember, easy to forget, and often lead to frustration. Users often reuse the same passwords for multiple accounts or choose weak passwords that can be guessed by attackers. Additionally, username and password authentication may require extra steps, such as two-factor authentication, to enhance security but also make the login process more complex for the user.

A red panda struggles to remember a password while a hooded panda holds a smartphone with passkey
A password is a human-readable secret that needs to be remembered, whereas a passkey is a cryptographic secret that can be stored in a device.

To address these issues, the W3C¹ has published WebAuthn, a standard that establishes a universal method for passwordless user authentication using public-key cryptography. The credentials generated through this method, commonly known as passkeys, allow users to authenticate themselves solely using their device and features like biometric recognition. This eliminates the need to remember multiple complex passwords or undergo an additional step for identity confirmation through a second authentication factor.

At Heritage, we have adopted passkeys as a method of authentication. We have experienced the benefits and we want to share our experience through a series of articles.

In this first article, we introduce the concept of a passkey, discuss its advantages, and explain why it is an interesting technology. Additionally, we explore the registration and authentication process between a user and a relying party², providing detailed explanations of these ceremony flows.

In the next articles, we present a practical end-to-end example that demonstrates how to integrate passkey authentication from scratch for a sample service. We delve into the code details, creating a Node.js back-end and a React Native mobile client as examples:

Understanding Passkey Authentication

A passkey is simply a common term used to describe a FIDO2³/WebAuthn discoverable credential that is based on a public/private key pair. It can be used to authenticate a user with a relying party without the need for username and password. The private key and credential ID are stored in the authenticator, while the public key and credential ID are stored by the relying party.

A passkey infographic / cheatsheet illustrating the key points of the synced discoverable credential
Passkey in five steps

Once a new public/private key pair is created and associated with the relying party, the user can authenticate by signing a challenge that is sent by the relying party. This is done using the private key stored in the authenticator. To perform this operation, user identification is required, either through biometrics or a device PIN. The relying party is aware of the public key associated with the client and can use it to verify the validity of the signed challenge.

From the user’s perspective, this means that they can conveniently access a service using biometric recognition or their smartphone PIN, without the hassle of remembering multiple passwords (or relying on a password manager).

An example of passkey login with the Heritage app
An example of passkey login with the Heritage app

In addition, once a passkey has been registered for a service (e.g., on a smartphone), the user can access it using different clients on the same device (web, mobile app) or even on a different device (another smartphone, laptop, etc.).

A passkey can be either device-bound or synced:

  • Device-bound: A passkey that is bound to an authenticator and cannot leave the device.
  • Synced: A passkey that can be synchronized across different user devices.

One of the main advantages of synced passkeys is the ability to synchronize and share them across different user devices. This allows users to restore their passkeys from a backup or use the same passkey to authenticate on multiple platforms. This user-friendly feature alleviates the issues that can arise from using a device-bound authenticator. In that case, synchronization or backup is not possible as the credentials cannot be transferred from the device. This can cause problems for the user if the physical device is lost or replaced.

While this feature is appealing in terms of usability and user experience, it is important to note that it slightly reduces authentication security compared to a device-bound passkey. A device-bound passkey does not allow the created key to be exported outside the device, ensuring that the key remains on the device at all times.

A hooded panda logging in without password via passkey on his laptop
Once a passkey is registered, the user can conveniently log in from all of their devices.

The WebAuthn specification does not provide any protocol for backing up credential private keys or sharing them between authenticators. As a result, the responsibility for implementing backup policies is left to the service providers (such as Apple or Google, in the case of passkeys created on mobile devices). These providers will have to make their own decisions on how to implement backup policies, which will ultimately affect the security level of the passkey. The security level of the passkey directly depends on the security level of the account recovery process used for backing up the passkeys. However, this trade-off is necessary to allow users to restore their registered passkeys from a backup.

How it works

As we have discussed so far, the passkey is a highly effective and user-friendly solution that simplifies user identification for a specific service and enhances its security. Now, let’s explore the technical details of this solution. The main entities involved in this flow are:

  • The user who wants to access a service
  • The client, a website or mobile app that allows the user to access a service provided by the relying party
  • The authenticator, an entity that generates and validates cryptographic keys associated with the user’s passkey, ensuring the integrity of the authentication process.
  • The relying party, a service provider that confirms the authenticity of the user’s identity based on the passkey, deciding access to the requested service.

These entities interact to enable the user to register a new passkey with the relying party. The user can then use this passkey to identify themselves and access the offered services in all subsequent interactions.

Let’s take a closer look at the two main phases: the registration phase and the authentication phase.

Registration

The initial action that a user can perform is to sign up for a service by generating a new passkey. There are two cases worth mentioning:

  1. The user is already registered to the service with a username, password, and 2FA login and wants to add passkey authentication. After logging in using the classic method, they will have the option to add the passkey.
  2. The service enables users to register and associate a new passkey. In this case, the creation of a new user and passkey will happen simultaneously.

While the back-end logic may undergo minor changes in handling these two scenarios, the registration process remains consistent in both cases. This process associates a passkey with a service, enabling subsequent access. Let’s explore the process:

Passkey registration ceremony flow sequence diagram
Passkey registration ceremony
  • The user intends to register for a service using a client, either a mobile app or a website.
  • The client initiates the registration process, requesting the relying party to register a new authenticator.
  • The relying party responds with a JSON (PublicKeyCredentialCreationOptions) that includes a challenge and additional metadata, such as relying party information and accepted authenticator types. Additionally, the relying party stores the challenge for this registration session, as it will need to be verified when the response is received.
{
// A random string generated by the server
"challenge": "uxMtaPAgEMm4JlJNeULcaz057QIcx0VpcS_32TiS9YY",
// Relying Party information
"rp": {
"name": "PasskeyExample",
"id": "test.ngrok-free.app"
},
// User information
"user": {
"id": "Test@test.it",
"name": "Test@test.it",
"displayName": "Test@test.it"
},
// Public key credential parameters, specifying supported algorithms and key types.
"pubKeyCredParams": [
{
"alg": -7, // ES256
"type": "public-key"
},
{
"alg": -257, // RS256
"type": "public-key"
}
],
// The timeout value for the registration process, in milliseconds.
"timeout": 60000,
// Specifies the attestation statement format.
"attestation": "none",
// An array of credentials that should be excluded from the creation process. Empty in this case.
"excludeCredentials": [],
// Authenticator selection criteria
"authenticatorSelection": {
// enforce the creation of a discoverable credential (resident key is the legacy name)
"residentKey": "required",
// user verification is preferred during the creation phase in order to smooth the process
"userVerification": "preferred",
// retained for backwards compatibility with WebAuthn Level 1
"requireResidentKey": true
},
"extensions": {
"credProps": true
}
}
  • The client uses this response to ask the authenticator to generate a new key pair that will be used to authenticate the user to the relying party.
  • The authenticator (which can be an external device or a smartphone) verifies the server payload and requests user identification (via biometrics or device PIN).
  • If all goes well, the authenticator generates a new private and public key pair and returns a JSON (RegistrationResponseJSON) to the client as a result, containing the newly created public key, attestation information, and other relevant details. It should be noted that the private key remains within the authenticator and, in the case of smartphones, it is stored in a separate hardware component known as the secure enclave or Trusted Execution Environment.
{
// A unique identifier for the credential
"id": "wh7ZbXdYJqvLHoUJLhvIZdEpnWY",
"rawId": "wh7ZbXdYJqvLHoUJLhvIZdEpnWY",
"response": {
// Base64 encoded information about the challenge and origin
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidXhNdGFQQWdFTW00SmxKTmVVTGNhejA1N1FJY3gwVnBjU18zMlRpUzlZWSIsIm9yaWdpbiI6Imh0dHBzOi8vdGVzdC5uZ3Jvay1mcmVlLmFwcCJ9",
// CBOR encoded ArrayBuffer containing the new public key, as well as signature over the entire attestationObject with a private key
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYuxv2BAbe0DBOLeqfUpW2uiF51_BxvF7IDjWg9luR8pNdAAAAAPv8MAcVTk7MjAtuAgVX170AFMIe2W13WCaryx6FCS4byGXRKZ1mpQECAyYgASFYID1BIjgJvI0h58_Jnf4cP0SNQ9EZvbh51oCIN8-hVk2eIlgg88fJ2ojioYmNpeG6eCI8FA91T_crlXceNuCQDtK5W2o"
},
"clientExtensionResults": {},
"type": "public-key",
"email": "Test@test.it"
}
  • The client sends this attestation object to the relying party to confirm and verify the new registration.
  • The relying party verifies the received payload, checking the authenticity of the attestation object, and ensures that the user’s registration meets the specified criteria (e.g., correct challenge response, proper attestation format).
  • After verifying the registration payload, the relying party notifies the user of the result. If successful, it stores relevant information about the registered credential that will be used for future authentication.

Authentication

Once a passkey is registered, the user can use it to authenticate with the relying party and gain access to a protected resource. Let’s see the procedure that allows the user to access the service:

Passkey authentication ceremony flow sequence diagram
Passkey authentication ceremony
  • The user wants to access a service using a mobile app or a website and needs to identify themselves.
  • The client initiates the authentication process by requesting the relying party to authenticate.
  • The relying party sends a JSON with the authentication options (PublicKeyCredentialRequestOptions) that include a challenge and information about the relying party. Additionally, it stores the challenge for this authentication session, as it will need to be verified when the response is received.
{
// A random string generated by the server
"challenge": "vm5xYs6Lsb5IOY8tsJhGBUYVXmhYENC2VNIlTvRxayw",
// An array of credentials allowed for authentication. Empty in this case, indicating no specific credentials are allowed.
"allowCredentials": [],
// The timeout value for the authentication process, in milliseconds.
"timeout": 60000,
// Specifies the preferred method for user verification.
"userVerification": "preferred",
// The relying party identifier, indicating the entity that is requesting authentication.
"rpId": "test.ngrok-free.app"
}
  • The client sends this JSON to the authenticator and requests it to sign it using one of the passkeys linked to the relying party.
  • If the authenticator has multiple passkeys associated with the relying party, the user is prompted to select a passkey to proceed with the authentication process. Once selected, the user is prompted to provide identification via biometrics or the device PIN in order to sign the challenge with the private key stored within the authenticator.
  • If the operation proceeds successfully, the authenticator returns to the client a JSON (AuthenticationResponseJSON) containing the signed challenge, authenticator details, and other relevant client data.
{
// A unique identifier for the credential
"id": "wh7ZbXdYJqvLHoUJLhvIZdEpnWY",
"rawId": "wh7ZbXdYJqvLHoUJLhvIZdEpnWY",
"response": {
// Base64 encoded information about the challenge and origin
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoidm01eFlzNkxzYjVJT1k4dHNKaEdCVVlWWG1oWUVOQzJWTklsVHZSeGF5dyIsIm9yaWdpbiI6Imh0dHBzOi8vdGVzdC5uZ3Jvay1mcmVlLmFwcCJ9",
// An ArrayBuffer containing information from the authenticator
"authenticatorData": "uxv2BAbe0DBOLeqfUpW2uiF51_BxvF7IDjWg9luR8pMdAAAAAA",
// an ArrayBuffer object which is the signature of the authenticator
"signature": "MEUCICRfk1dw2rRq65C_-eARsrJ5ujxATHxGOPcjy3JmtZITAiEAsRGtTVHp5rdnf1MphEKbxfS9riNGpUtcrWBOhOblt50"
},
"clientExtensionResults": {},
"type": "public-key"
}
  • The client sends this authentication response to the relying party.
  • The relying party first verifies whether the authenticator used is registered. Then, it checks the response’s authenticity and the correctness of the challenge response.
  • After successful verification, the relying party grants access to the user, allowing them to use the service, returning, for example, an authentication token that can be used for the next requests.
  • The user is now authenticated and can use the mobile app or website.

Conclusion

Passkeys are a promising solution for passwordless authentication on the web. They offer a standard, convenient and secure way for users to access a service without having to remember or type a password.

Starting from 2016 with the publication of the first draft of the specification, until today, with the past year witnessing the boom of tech companies that have started to adopt passkey authentication in their services, passkeys could potentially revolutionize the way we access the web. This could make it more secure, convenient, and greatly reduce the need to use passwords.

A timeline with all the most important milestones for the adoption of the passkey, from its specification to its adoption by the most famous service providers

However, passkeys are not without challenges. While the idea of a passwordless future may seem appealing, it represents a significant change that will require time for people to adjust to. Many users may not be aware of this option or how it works, and it may take some time and effort to convince them to switch to passkeys.

In addition to this, there is currently no interoperability between passkeys created on different platforms and, to date, there is no possibility of transferring a passkey generated on an iPhone and synchronized via iCloud to an Android device and vice versa. This forces you to create different passkeys for each ecosystem.

At Heritage, we have embraced passkeys as a way to enhance our user experience. We have integrated passkeys into both our web app and mobile app, and we have seen positive results. Our users can access our services faster and easier, making the login process smoother, especially in a mobile environment.

In the next article, we will provide a practical example of how to implement passkey authentication for your own service. We will guide you through the steps of setting up a Node.js server that acts as a relying party and a simple React Native mobile app that generates and uses passkeys:

Written by Fabrizio Filizola and Heritage Holdings Engineering Team

--

--

Heritage Holdings Tech Blog

Heritage is a private investment platform built by families for families. Our blog shares engineering challenges, culture, values, best practices, and more.