Password-less Authentication — Speed up Sign-in with FaceID/TouchID

Many banking apps have introduced an experience to allow you to login with just a FaceID or TouchID. The experience works as follows:

  1. After you successfully login, the app asks you if you want to use FaceID/TouchID to speed up login in the future, like this TaxCaddy interface:

2. When you consent, a system prompt pops up to confirm FaceID/TouchID enrollment.

3. When you login in the future, a FaceID/TouchID prompt pops up automatically, and if the scan is successful, you are logged in. No more passwords, no more OTP code through SMS.

How is this implemented under the hood? Is it secure? In this post, I will show you some sample code, and explain how it works.

How it works

While the login is a password-less experience, under the hood, it is actually implemented with a password like mechanism.

In step 2 above, when you enroll in FaceID/TouchID, a secret is generated. The secret is both stored locally on your device (guarded by the FaceID/TouchID), and also stored on the server.

In step 3 above, when you scan your FaceID/TouchID, it unlocks the saved secret, then it sends the secret to the server for comparison. If the server confirms that the secret matches, you are allowed in.

Even though it is a password-less experience, and you are not directly aware of the secret, the secret serves the same purpose as a regular password.

Code sample

There are at least a couple of ways of implementing this logic in iOS. You could use the Keychain API to store the secret and attach an access policy generated by the SecAccessControlCreateWithFlags API. Since you can easily find a sample code for that, I will show a different, and yet simpler way to leverage the LARight API, by leveraging the LASecret field that is stored as part of the LAPersistedRight object.

In step 2 above, we first generate a long random secret which is hard for a human to remember. Then, use iOS’s LARight API to save the secret to the Keychain on the device locally, as follows:

// retrieve a Secret from server, and save locally
func saveSecret() async throws -> Data {
    // let server generate a random unique secret
    let secret : Data = getRandomSecretFromServer()
    // initialize LARight
    let right = LARight()
    // in case there were secret generated before, clean up before saving a new secret
    try await LARightStore.shared.removeRight(forIdentifier: keyIdentifier)
    // save a new PersistedRight with the secret
    let persistedRight = try await LARightStore.shared.saveRight(right, identifier: keyIdentifier, secret: secret)
    try await persistedRight.authorize(localizedReason: "Saving secret...")

    // return the secret in case the UI wants to display a hash
    return try await persistedRight.secret.rawData
}

In step 3, when we need to verify the secret with the server, we call the following function:

func verifySecret() -> Bool {
    // retrieve the previously saved secret from Keychain
    let persistedRight = try await LARightStore.shared.right(forIdentifier: keyIdentifier)
    try await persistedRight.authorize(localizedReason: "Authenticating...")
    let localSecret = try await persistedRight.secret.rawData

    // verify with the server
    return verifySecretWithServer(localSecret)
}

Is it secure? Should I implement FIDO2 instead?

This FaceID/TouchID mechanism described above is stronger in its security guarantee than a password. It is phishing-proof because the secret is not humanly readable, thus there is nothing to leak in a social-engineering attack. It can also be considered as 2FA (Two Factor Authentication), because it verifies both something you own (your device where the secret is stored) and something you are (your face or touch scan).

However, this FaceID/TouchID mechanism is weaker than the FIDO2 protocol, because it is not server-hacking proof. The secret stored on the server could be leaked if the server is comprised. In contrast, FIDO2 is server-hacking proof, because it only stores a public key on the server, and a hacker cannot gain any advantage even if they gain access to the public key.

Summary

I have shown you some simple code on how to implement a FaceID/TouchID password-less experience. If you are not ready to adopt FIDO2 yet, this is a simple way to boost your app’s security. Compared to FIDO2, this solution offers a simpler account recovery experience. If the user loses their device, the server still has the secret, which can be used to bootstrap a new device.

Note: this article is also posted at medium as Password-less Authentication — Speed up Sign-in with FaceID/TouchID.

What to consider when adopting Passkey and how to build a great Passkey user experience

Are you considering adopting Passkey? It is not a one line code change to integrate Passkey, due to the immature platform implementation and the user education required. I gave a talk at IdentiVerse 2023 to highlight what you need to consider when you adopt Passkey. If you missed my talk, or could not attend IdentiVerse, here is the audio recording and presentation of my talk.

Huan’s presentation at IdentiVerse

Leave comments if you want to learn more. Otherwise, see you next year at IdentiVerse, which I highly recommend, as it is the #1 best conference in the Identity industry.

Note: This article is also posted on my medium site at Passkey adoption talk at IdentiVerse.

Passkey: AAL3 Strong Security with a Customizable Interface

Apple introduced Passkey a year ago in WWDC2022. It is ground breaking in that it allows the private key in a FIDO credential to be stored in the iCloud keychain, and facilitates it to be propagated from device to device. While it makes the FIDO credential easy to use, and it solves the bootstrapping problem (someone logging into a website/app from a new device), it comes with a security downside that the private key is no longer stored in the hardware TPM module. Because of this downside, according to NIST, passkey can only be classified as providing an AAL2 level assurance.

The following picture shows a security strength tradeoff among common authentication factors.

Spectrum of security guarantee

To get to the highest security level, we need to leverage a hardware device. Unfortunately, the current Passkey implementations by Apple and Google do not give developers an option to choose a TPM module. 

What if you really want a high security guarantee? You can obviously use a physical security key such as a Yubikey, but that is both costly and hard to use. This post shows you how to leverage the built-in TPM (also called Secure Enclave) using the raw primitives provided by the iOS platform to build a FIDO solution from scratch.

The concept behind FIDO is simple. It leverages private and public key cryptography, where a user uses the private key to sign a nonce from the server, and prove that it possess the private key. In this Post, we will demonstrate how you can achieve the same authentication routine with native iOS APIs. This is not an exact implementation of the complete FIDO protocol, but it focuses only on the private public key portion due to page limit. But you could expand on the example if you choose.

We will use the Local Authentication framework in iOS, which will generate a LAPublicKey LAPrivateKey pair to be stored in the TPM module. Then we will demonstrate how to sign a signature with the LAPrivateKey, and how to validate the signature with the LAPublicKey.

Credential Enrollment during Registration

First, we demonstrate how to generate a key pair during Registration. We leverage LARightStore, which stores a LAPersistedRight backed by a unique key in the Secure Enclave. We create a function generateClientKeys() to capture the full logic. First, we initiate a LARight(), which is “a grouped set of requirements that gate access to a resource or operation”. When we call LARightStore.shared.saveRight(), a key pair is generated, and the keys and the right are persisted, and a LAPersistedRight is returned. We can get a reference to the public key for the newly generated key by calling persistedRight.key.publicKey. This public key is returned so that the caller can persist the public key on the server side for future verification.

// generate a key pair
func generateClientKeys() async throws -> Data {
    let right = LARight()
    // in case there were key generated before, clean up before generate a new key pair
    try await LARightStore.shared.removeRight(forIdentifier: "fido-key")
    // generate a new key pair
    let persistedRight = try await LARightStore.shared.saveRight(right, identifier: keyIdentifier)
    return try await persistedRight.key.publicKey.bytes
}

Note that, we also call LARightStore.shared.removeRight() right before saveRight(). This is to remove any old key if one was saved under the same identifier before. 

Authentication

After registration, when a user comes back to your app again and needs to login, we need to go through the authentication ceremony to verify the user. The following code is a simplified FIDO flow. First, following the FIDO protocol, we need to call the server to retrieve a nonce. A nonce is used to prevent a replay attack. Then, we call the following function to sign the nonce. 

func signServerChallenge(nonce: Data) async throws -> Data {
    let persistedRight = try await LARightStore.shared.right(forIdentifier: "fido-key")
    try await persistedRight.authorize(localizedReason: "Authenticating...")

    // verify we can sign
    guard persistedRight.key.canSign(using: .ecdsaSignatureMessageX962SHA256) else {
        throw NSError(domain: "SampleErrorDomain", code: -1, userInfo: [:])
    }

    return try await persistedRight.key.sign(nonce, algorithm: .ecdsaSignatureMessageX962SHA256)
}

This function first look up the LAPersistedRight under the same identifier. If found, it asks the user for authorization to use the key, then it uses the private key to sign the nonce. The nonce and its signature should be sent to the server for verification. 

Sample code and demo

The FIDO in TPM project is a demo project incorporating the code snippet above to demonstrate the user interface for both the registration and authentication ceremonies. You can also watch this video demo to see the registration and authentication user experience. 

When to use this solution

Why would not you use the native WebAuthn APIs provided by the platform (Authentication Services API on iOS or the Credential Manager API on Android)? There are several reasons to use a home grown solution as outlined in this post:

Strong security (up to AAL3 level)

This solution would allow you to provide a strong security assurance using the built-in platform authenticators (TouchID or FaceID) without the need to buy a separate security key (such as a Yubikey). It is both secure and easy to use, because you do not have to carry an extra piece of hardware. 

More customizable UX and UI

As shown in the demo video, the registration experience is much simplified, where it could even be done silently without a user interaction. The authentication experience is also simpler, with a lot of room to customize the experience. In particular, you could choose to mention “Biometric” or some other more familiar terminology to the end user, since most users have not heard of passkey. This gives you the flexibility if you do not want to market the feature as passkey. 

Not required to be tied to a website.

WebAuthn is a web API following the FIDO standard. To avoid phishing attacks, WebAuthn requires the credential to be bounded to a web domain. When iOS and Android introduced the equivalent native API, they followed this design by requiring your app to be bounded to a web domain through a universal link. But your app may not have a web presence. This solution saves you the hassle of setting up a website and configuring for universal links.

Conclusion

Unlike inside a web browser when you are limited to the WebAuthn API, you have a full range of APIs to leverage in a native app. If you do not want to use the FIDO API given by the platform, you actually can implement the FIDO protocol yourself. Hopefully, this post shows you the basis if you want to venture into more customizations and a stronger security assurance. 

Note: this article is also posted at Passkey: AAL3 Strong Security with a Customizable Interface

Passkey vs. Password

You heard of passkey from somewhere, or you start to see some websites or mobile apps using passkey, but you have no idea what it is. You are not alone! In a recent user research we conducted on our user base, we found that a very small number of people have heard of it, and an even smaller portion actually understands it.  That is why early adopters go out of their way to explain what is a passkey. For example, the following is a screenshot of the Shopify app, where they dedicate a whole page to educate the users.

If you want a more thorough understanding of passkey beyond that single page, read on. 

What is Passkey 

It is best to understand passkey and how it is different from password, through the lens of an analogy. 

Let us start with passwords. Password is a shared secret where both you and the server know about. If you can present the secret to the server, and the server can match it up with what it knows, you are in. This is similar to “Open Sesame” in Aladdin, where you open the cave by shouting out the secret.

“Open Sesame” in Aladdin

Password is easy to use, but it carries a well-known downside — it can be easily eavesdropped. 

Passkey fundamentally works differently. It consists of two keys, one is called a private Key, which is stored on your local device, and another is called a public Key, which is stored on the server. Passkey works as follows:

  • Server sends a unique message to your local device
  • Your device uses the private key to sign the message
  • The server, who already has the corresponding public key, can testify the message is signed by your private key. If testified, you are in. 

This is analogous of how your bank verifies your signature on a check before letting you withdraw money in the physical world.

The analogy is not 100% matching. In the physical world, it is feasible to forge a signature to fool the bank teller. But, in passkey, there is no way to forge a signature, because it is based on the proven private/public key cryptography. Only the person with the private key can produce a signature that can be verified by the public key. 

Why adopt now

If password works perfectly, why try something else? For passkey, there are at least two good reasons to give it a try as soon as possible. 

1. Higher security

Working in a financial services company, I frequently see ATOs (Account Take Overs). Password is easy to fool through social engineering through a technique called phishing. For example, attackers can pretend to be a customer service agent, they will pretend to help you solve an issue, and trick you to give out your password. Passkey is strong because it is phishing-proof. There is no secret that you can be tricked to give out.

2. Easy to use

Passkey is an unique technology where it is both secure and easy-to-use at the same time, so there is not a tradeoff where you have to give up on usability. If implemented correctly, you can skip password and second factor authentication (e.g., a SMS code) altogether with one passkey verification. Passkey verification is also super simple. You can either do a biometric scan or use your phone’s passcode. 

Manage your passkeys

Now that you have started to use passkey in some apps, it is important to know how passkey differs from password, so that you can manage your passkeys differently. Passkey differs from password in a couple of areas:,

1. One vs. Two

In passkey, it is important to note that there are 2 keys, one private and one public. The private key is stored on your client device, and the public key is stored on the server. If you want to remove a passkey, keep in mind which key you are touching. 

  • If you want to remove a passkey from accessing a website or app, remove the public key stored on the server. No matter where the corresponding private key is stored, it is no longer useable for login, because the server can no longer validate its signature. 

    (An app’s interface to help you manage the public key on the server)
  • If you just want to remove passkey from one device from accessing a website or app, remove the private key on your device. If the private key is replicated on multiple devices, you can still login to the website/app through other devices. 

    (The iOS interface to manage the private key in your phone’s Settings app)
  • If you are a clean freak, remove both keys on the client and server. But keep in mind that it is not necessary, removing one is sufficient to remove access. 

2. One vs. Many

Unlike password, where you only have one password active at any given time, there may be multiple passkeys due to the fragmentation in platform unification (see the technical details in an earlier post). It is important to remember that you may have a separate passkey per platform, or even a separate passkey per device. When you create passkeys, try to give them a different name to differentiate them, if it is offered by the website and app. 

Conclusion 

Passkey is the new kid on the block, but it has a huge potential to solve all problems associated with passwords. I hope this post gives you a good introduction to understand how it is different. Leave a comment if you are still confused, I would be happy to share more insights.

(This article is also posted at Medium Passkey vs. Password)

Passkey Limitations and Adoption Considerations

Apple announced Passkey in WWDC’22 a couple of months ago. Both Microsoft and Google quickly followed suit, announcing their support in the RSA and IdentiVerse conferences. Passkey is promising to potentially replace passwords eventually, but the road to that promise may be long and bumpy. There are at least 3 issues that will prevent a widespread adoption in the short term, which I will elaborate in this post. If you want to adopt Passkey in your app, read on to understand the implications and think through how to overcome those limitations. 

Background

Before I describe its limitations, let us understand what is Passkey first. Passkey builds on FIDO2 set of specifications. FIDO2 specifies a new way to register and sign in to an app or a website, which is also referred to as a RP (Relying Party) in the spec. When a user registers with a RP, a private/public key pair is generated (e.g., by a browser). The private key is stored on the client and it is kept private, and the public key is stored at the RP. When the user subsequently logs in, the RP sends a unique nonce for the client to sign with its private key. When the RP is able to verify that the nonce is signed with the private key corresponding to its public key, the RP is certain that the client is the same user as the one during the registration. This process is illustrated in the figure below. 

How Passkey works

In the current FIDO2 implementation, the private key is typically stored on a hardware module, such as a TPM (Trusted Platform Module), on the user’s device. The private key can be used to sign a nonce, but it can never be exported, so it is secure. 

Passkey extends FIDO2 in two ways. 

  1. Passkey allows the private key to be synced across devices, e.g., through the iCloud keychain. This is actually not part of the FIDO2 spec. FIDO2 does not prescribe how the key should be stored on the client side, however, Passkey makes it explicit that the key may be synced outside of a device. Passkey is a vendor implementation term, and in the FIDO community, it is officially called multi-device credential
  2. Passkey introduces a new protocol to allow a phone to be used as a roaming authenticator. This is the demo Apple showed in WWDC, where the website pops up a QR code, and users use their phones to scan and authenticate. The private key is stored on the phone, and a communication protocol allows the signed nonce to be transmitted from the phone to the app to authenticate the user.

There are a lot of advantages of FIDO2, which are highlighted in the Apple presentation, including:

  • No shared secret like password, so nothing to steal from the server side.
  • Not phishable. Phishing is the #1 vulnerability today. The FIDO2 protocol makes sure that it only signs a nonce corresponding to the right RP. No more tricking people into believing when you visit a fake website.  

Because of these inherent advantages, Passkey will eventually replace password, but there are several challenges along the way. 

Challenge 1: Lower security level

The first challenge for adoption is that Passkey lowers the security promise. Before Passkey, the FIDO2 private key was typically stored on a hardware TPM module if available, which has the nice property that guarantees the key could not be exported. To understand the impact, it is helpful to understand how NIST (National Institute of Standards and Technology) thinks about security levels. NIST defines three Authenticator Assurance Level (AAL):

  • AAL1: You just need to prove you control one authenticator that is bound to the account. The single authenticator could just be a password. 
  • AAL2: Proof of possession and control of two distinct authentication factors are required. This is also often referred to as the Multi-Factor Authentication (MFA). An example is SMS verification in addition to password. 
  • AAL3: It is based on proof of possession of a key through a cryptographic protocol. AAL3 authentication SHALL use a hardware-based authenticator and an authenticator that provides verifier impersonation resistance. In order to authenticate at AAL3, claimants SHALL prove possession and control of two distinct authentication factors through secure authentication protocol(s). A key requirement of AAL3 is that the private key cannot be exported, so that, by proving that you own the private key, you also prove that you possess the hardware device. 
Assurance level trade off between various credentials, from the least secure (left), to the most secure (right)

The figure above (courtesy of Shane’s RSA presentation) illustrates the security tradeoffs between various sign in methods, from the least secure to the left, to the most secure on the right. Prior to Passkey, the default is “Device-bound FIDO credential” when FIDO authentication is used, which is the most secure. By introducing Passkey, we move to “multi-device passkey”. 

The use of Passkey is a platform decision. There is not an option for either the RP or the user to disable Passkey, i.e., it is not possible to choose “device-bound FIDO credential” anymore. 

FIDO alliance is working on a new device-bound-key extension, which allows an RP to request an additional key that is both unique and bounded to the device. If a device-bound-key exists, RP can verify if the device is a known device. Or, if a device-bound-key does not exist, RP will know the authentication is happening in a new device, and it can step up authentication if needed. Device-bound-key provides AAL3 security assurance, but unfortunately, it is an optional extension that may not get widespread adoption by vendors. 

Challenge 2: Sync across ecosystems

Passkey relies on client side synchronization to distribute the keys to users’ devices. The synchronization mechanism is vendor specific. There are three potential layers at which the keys could be synchronized. 

  1. Platform level: This is a capability provided by the OS vendor that is built-in to the OS. An example is the iCloud keychain. The OS vendors manage the users’ identity and their devices, and they propagate the private key across devices the users own. 
  2. Browser level: Browsers also provide an ability for users to login an account, and synchronize their data. For example, in Chrome, you can login to your Google account, and sync data across all browser instances, potentially on multiple devices across OS ecosystems (e.g., between iOS, Windows, Mac and Android). 
  3. App level: A password manager app is used widely to synchronize passwords at the application level, and a similar or same app could do the same thing for passkeys. They may have to use a browser plugin to hook into the sign-in/registration process. To synchronize passkeys, a passkey manager app needs an interface to interact with the authenticators to access the keys. 

The current passkey implementation will sync keys at the platform level, although there might be an option to expose an OS interface to allow app level syncing in the future. 

The current direction of platform level key syncing presents challenges.

  1. First, the platform may not have visibility to all keys in all browsers. MacOS behaves differently than other platforms, such as iOS, Android and Windows. On MacOS, different browsers see a different subset of the platform authenticator, e.g., the TouchID authenticator. When chrome creates a FIDO key, the key is not visible to other browsers, such as Safai and Firefox, or vice versa. Apple platform will only sync keys created by Safari, and because of the key isolation, keys created by other browsers such as Chrome and Safari will not be synced beyond the MacOS device.



    This behavior will be confusing to an end user. Users may create a FIDO2 key in Chrome on MacOS, and assume that the key will be sync’ed by iCloud keychain, as implied by Apple’s passkey annoucement. But, in reality, they will be suprised when the keys are not available on their iOS devices.

    While there is a plan to change this behavior, it will likely take some time, since it requires both OS and browsers to change. 
  2. Second, cross platform experience is confusing to the end users. There are several scenarios:
  • If you are largely in the Apple ecosystem, the experience is ok (except the multi-browser scenario on MacOS as described above). The keys in your iPhone, iPad and Mac all sync through iCloud Keychain. The only challenge is when you decide to switch platform (e.g., to Windows). There is no mechanism to export keys in bulk, so your keys are stuck in the iCloud. 
  • If you use Windows, your experience is much worse. Because Microsoft does not own a mobile platform, your keys are likely spread over multiple platforms, e.g, Windows and Android. The keys you register on your Windows machine are isolated from the keys you register on Android or iOS when you are on the mobile platforms. You have to constantly remember where your keys are stored on what platform when you login to a RP. One possible solution is to make sure all your keys are stored on your mobile platform. Since mobile platforms support QR code scanning, they not only can be used to log you into an RP from your phone, but they can also log you into an RP on Windows. Unfortunately, the QR scanning experience is cumbersome, which involves extra clicks to bring up the QR code, and hunting for the mobile phone to scan and complete the login. 

Microsoft and Google could have built a much better user experience had they chosen to sync the keys at the browser level. Neither of them own both the desktop and mobile platforms, so their users would have to live with the cross-platform clunkiness if they own both desktop and mobile devices. 

Challenge 3: Explicitly manage multiple private/public keys 

With passwords, there is only 1 shared secret to store on the server side. Disabling login is as simple as removing the shared secret on the server. With passkeys, there are multiple private/public key pairs, with at least one pair for each platform. Disabling login requires the user to understand which key is used on each platform, and to make sure deleting the correct key. Removing the wrong public key would mean the user can no longer login on the corresponding platform. 

The user also must be conscious that there are a pair of keys for each credential. Removing the public key on the RP is sufficient to revoke access, but the private key may still be visible on the client side, which may be confusing to the user. 

The transition to passkey is happening. Apple’s implementation will be out shortly, Android’s and Windows’ implementation will quickly follow. If you support WebAuthn/FIDO2 on your RP already, or if you plan to adopt passkey-based login directly, consider these recommendations.

Adoption considerations

  1. Add recovery options. While Apple makes it sounds like passkey is all you need in their announcements, there are many scenarios where you cannot rely on passkey being there. Not only that users may switch platform, but even if a user stays in the Apple ecosystem, they may be using Chrome on their Mac, which would require a separate set of keys from their iCloud passkeys. To make sure a user can still login, you should add other options to allow a user to login, possibly as a recovery mechanism. Potential mechanisms can be Magic Link, or OTP code over SMS, or security questions. When a user uses a recovery mechanism to login on a new platform/device/browser, you can then generate new keypairs to allow users to login with passkeys on the same device in the future.
  2. Detect and enhance security assurance as needed. If your app requires AAL3 level assurance, you can no longer assume the FIDO2 keys are device bound. You have to introduce additional logic to ensure high assurance level, which could be as follows:
    • First, you should detect if Passkey is used. WebAuthn (the Javascript API for FIDO2) enhanced the protocol to offer two additional bits of information: credential backup eligibility (BE) and current backup state (BS) in the authenticator data. BE is set to true when Passkey is used. 
    • Second, detect if device-bound-key extension is supported. device-bound-key is an optional enhancement to FIDO2. It is still under definition, and not all browsers will implement it. But if the browser supports it, you can request device-bound-key to ensure AAL3 security level. 
    • Third, if device-bound-key extension is not available, you have to implement your own device detection, and step up authentication if a new devie is found. For example, you can use a browser cookie to remember if the user has logged in from the browser before, and add step up authentication logic if a new device is used.
  3. Record User Agent. The user could have multiple keys from multiple platforms/browsers, and it becomes confusing to the end user on how to manage the keys. Remembering the user agent during enrollment could help the user distinguish between the different keys. Optionally, during enrollment, you could also ask the user for a unique name to identify the key, and store the unique name along with the key. Additional user education may also be required as they transition from paswords to passkeys.

Conclusion

We are at the early stage of passkey adoption, a lot of user experience quarks need to be worked out, and more user education is needed. The blog highlights some challenges that we need to consider, hopefully it helps you think through solutions to help your users transition successfully.

Note: this article is also posted at Passkey Limitations and Adoption Considerations