Passkeys: What the Heck and Why?

These things called passkeys sure are making the rounds these days. They were a main attraction at W3C TPAC 2022, gained support in Safari 16, are finding their way into macOS and iOS, and are slated to be the future for password managers like 1Password. They are already supported in Android, and will soon find their way into Chrome OS and Windows in future releases.

Geeky OS security enhancements don’t exactly make big headlines in the front-end community, but it stands to reason that passkeys are going to be a “thing”. And considering how passwords and password apps affect the user experience of things like authentication and form processing, we might want to at least wrap our minds around them, so we know what’s coming.

That’s the point of this article. I’ve been studying and experimenting with passkeys — and the WebAuthn API they are built on top of — for some time now. Let me share what I’ve learned.

Table of contents

Terminology

Here’s the obligatory section of the terminology you’re going to want to know as we dig in. Like most tech, passkeys are wrought with esoteric verbiage and acronyms that are often roadblocks to understanding. I’ll try to de-mystify several for you here.

  • Relying Party: the server you will be authenticating against. We’ll use “server” to imply the Relying Party in this article.
  • Client: in our case, the web browser or operating system.
  • Authenticator: Software and/or hardware devices that allow generation and storage for public key pairs.
  • FIDO: An open standards body that also creates specifications around FIDO credentials.
  • WebAuthn: The underlying protocol for passkeys, Also known as a FIDO2 credential or single-device FIDO credentials.
  • Passkeys: WebAuthn, but with cloud syncing (also called multi-device FIDO credentials, discoverable credentials, or resident credentials).
  • Public Key Cryptography: A generated key pair that includes a private and public key. Depending on the algorithm, it should either be used for signing and verification or encrypting and decrypting. This is also known as asymmetric cryptography.
  • RSA: An acronym of the creators’ names, Rivest Shamir and Adel. RSA is an older, but still useful, family of public key cryptography based on factoring primes.
  • Elliptic Curve Cryptography (ECC): A newer family of cryptography based on elliptic curves.
  • ES256: An elliptic curve public key that uses an ECDSA signing algorithm (PDF) with SHA256 for hashing.
  • RS256: Like ES256, but it uses RSA with RSASSA-PKCS1-v1.5 and SHA256.

What are passkeys?

Before we can talk specifically about passkeys, we need to talk about another protocol called WebAuthn (also known as FIDO2). Passkeys are a specification that is built on top of WebAuthn. WebAuthn allows for public key cryptography to replace passwords. We use some sort of security device, such as a hardware key or Trusted Platform Module (TPM), to create private and public keys.

The public key is for anyone to use. The private key, however, cannot be removed from the device that generated it. This was one of the issues with WebAuthn; if you lose the device, you lose access.

Passkeys solves this by providing a cloud sync of your credentials. In other words, what you generate on your computer can now also be used on your phone (though confusingly, there are single-device credentials too).

Currently, at the time of writing, only iOS, macOS, and Android provide full support for cloud-synced passkeys, and even then, they are limited by the browser being used. Google and Apple provide an interface for syncing via their Google Password Manager and Apple iCloud Keychain services, respectively.

How do passkeys replace passwords?

In public key cryptography, you can perform what is known as signing. Signing takes a piece of data and then runs it through a signing algorithm with the private key, where it can then be verified with the public key.

Anyone can generate a public key pair, and it’s not attributable to any person since any person could have generated it in the first place. What makes it useful is that only data signed with the private key can be verified with the public key. That’s the portion that replaces a password — a server stores the public key, and we sign in by verifying that we have the other half (e.g. private key), by signing a random challenge.

As an added benefit, since we’re storing the user’s public keys within a database, there is no longer concern with password breaches affecting millions of users. This reduces phishing, breaches, and a slew of other security issues that our password-dependent world currently faces. If a database is breached, all that’s stored in the user’s public keys, making it virtually useless to an attacker.

No more forgotten emails and their associated passwords, either! The browser will remember which credentials you used for which website — all you need to do is make a couple of clicks, and you’re logged in. You can provide a secondary means of verification to use the passkey, such as biometrics or a pin, but those are still much faster than the passwords of yesteryear.

More about cryptography

Public key cryptography involves having a private and a public key (known as a key pair). The keys are generated together and have separate uses. For example, the private key is intended to be kept secret, and the public key is intended for whomever you want to exchange messages with.

When it comes to encrypting and decrypting a message, the recipient’s public key is used to encrypt a message so that only the recipient’s private key can decrypt the message. In security parlance, this is known as “providing confidentiality”. However, this doesn’t provide proof that the sender is who they say they are, as anyone can potentially use a public key to send someone an encrypted message.

There are cases where we need to verify that a message did indeed come from its sender. In these cases, we use signing and signature verification to ensure that the sender is who they say they are (also known as authenticity). In public key (also called asymmetric) cryptography, this is generally done by signing the hash of a message, so that only the public key can correctly verify it. The hash and the sender’s private key produce a signature after running it through an algorithm, and then anyone can verify the message came from the sender with the sender’s public key.

How do we access passkeys?

To access passkeys, we first need to generate and store them somewhere. Some of this functionality can be provided with an authenticator. An authenticator is any hardware or software-backed device that provides the ability for cryptographic key generation. Think of those one-time passwords you get from Google Authenticator1Password, or LastPass, among others.

For example, a software authenticator can use the Trusted Platform Module (TPM) or secure enclave of a device to create credentials. The credentials can be then stored remotely and synced across devices e.g. passkeys. A hardware authenticator would be something like a YubiKey, which can generate and store keys on the device itself.

To access the authenticator, the browser needs to have access to hardware, and for that, we need an interface. The interface we use here is the Client to Authenticator Protocol (CTAP). It allows access to different authenticators over different mechanisms. For example, we can access an authenticator over NFC, USB, and Bluetooth by utilizing CTAP.

One of the more interesting ways to use passkeys is by connecting your phone over Bluetooth to another device that might not support passkeys. When the devices are paired over Bluetooth, I can log into the browser on my computer using my phone as an intermediary!

The difference between passkeys and WebAuthn

Passkeys and WebAuthn keys differ in several ways. First, passkeys are considered multi-device credentials and can be synced across devices. By contrast, WebAuthn keys are single-device credentials — a fancy way of saying you’re bound to one device for verification.

Second, to authenticate to a server, WebAuthn keys need to provide the user handle for login, after which an allowCredentials list is returned to the client from the server, which informs what credentials can be used to log in. Passkeys skip this step and use the server’s domain name to show which keys are already bound to that site. You’re able to select the passkey that is associated with that server, as it’s already known by your system.

Otherwise, the keys are cryptographically the same; they only differ in how they’re stored and what information they use to start the login process.

The process… in a nutshell

The process for generating a WebAuthn or a passkey is very similar: get a challenge from the server and then use the navigator.credentials.create web API to generate a public key pair. Then, send the challenge and the public key back to the server to be stored.

Upon receiving the public key and challenge, the server validates the challenge and the session from which it was created. If that checks out, the public key is stored, as well as any other relevant information like the user identifier or attestation data, in the database.

The user has one more step — retrieve another challenge from the server and use the navigator.credentials.get API to sign the challenge. We send back the signed challenge to the server, and the server verifies the challenge, then logs us in if the signature passes.

There is, of course, quite a bit more to each step. But that is generally how we’d log into a website using WebAuthn or passkeys.

The meat and potatoes

Passkeys are used in two distinct phases: the attestation and assertion phases.

The attestation phase can also be thought of as the registration phase. You’d sign up with an email and password for a new website, however, in this case, we’d be using our passkey.

The assertion phase is similar to how you’d log in to a website after signing up.

Attestation

View full size

The navigator.credentials.create API is the focus of our attestation phase. We’re registered as a new user in the system and need to generate a new public key pair. However, we need to specify what kind of key pair we want to generate. That means we need to provide options to navigator.credentials.create.

// The `challenge` is random and has to come from the server
const publicKey: PublicKeyCredentialCreationOptions = {
  challenge: safeEncode(challenge),
  rp: {
    id: window.location.host,
    name: document.title,
  },
  user: {
    id: new TextEncoder().encode(crypto.randomUUID()), // Why not make it random?
    name: 'Your username',
    displayName: 'Display name in browser',
  },
  pubKeyCredParams: [
    {
      type: 'public-key',
      alg: -7, // ES256
    },
    {
      type: 'public-key',
      alg: -256, // RS256
    },
  ],
  authenticatorSelection: {
    userVerification: 'preferred', // Do you want to use biometrics or a pin?
    residentKey: 'required', // Create a resident key e.g. passkey
  },
  attestation: 'indirect', // indirect, direct, or none
  timeout: 60_000,
};
const pubKeyCredential: PublicKeyCredential = await navigator.credentials.create({
  publicKey
});
const {
  id // the key id a.k.a. kid
} = pubKeyCredential;
const pubKey = pubKeyCredential.response.getPublicKey();
const { clientDataJSON, attestationObject } = pubKeyCredential.response;
const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
// Send data off to the server for registration

We’ll get PublicKeyCredential which contains an AuthenticatorAttestationResponse that comes back after creation. The credential has the generated key pair’s ID.

The response provides a couple of bits of useful information. First, we have our public key in this response, and we need to send that to the server to be stored. Second, we also get back the clientDataJSON property which we can decode, and from there, get back the typechallenge, and origin of the passkey.

For attestation, we want to validate the typechallenge, and origin on the server, as well as store the public key with its identifier, e.g. kid. We can also optionally store the attestationObject if we wish. Another useful property to store is the COSE algorithm, which is defined above in our  PublicKeyCredentialCreationOptions with alg: -7 or alg: -256, in order to easily verify any signed challenges in the assertion phase.

Assertion

View full size

The navigator.credentials.get API will be the focus of the assertion phase. Conceptually, this would be where the user logs in to the web application after signing up.

// The `challenge` is random and has to come from the server
const publicKey: PublicKeyCredentialRequestOptions = {
  challenge: new TextEncoder().encode(challenge),
  rpId: window.location.host,
  timeout: 60_000,
};
const publicKeyCredential: PublicKeyCredential = await navigator.credentials.get({
  publicKey,
  mediation: 'optional',
});
const {
  id // the key id, aka kid
} = pubKeyCredential;
const { clientDataJSON, attestationObject, signature, userHandle } = pubKeyCredential.response;
const { type, challenge, origin } = JSON.parse(new TextDecoder().decode(clientDataJSON));
// Send data off to the server for verification

We’ll again get a PublicKeyCredential with an AuthenticatorAssertionResponse this time. The credential again includes the key identifier.

We also get the typechallenge, and origin from the clientDataJSON again. The signature is now included in the response, as well as the authenticatorData. We’ll need those and the clientDataJSON to verify that this was signed with the private key.

The authenticatorData includes some properties that are worth tracking First is the SHA256 hash of the origin you’re using, located within the first 32 bytes, which is useful for verifying that request comes from the same origin server. Second is the signCount, which is from byte 33 to 37. This is generated from the authenticator and should be compared to its previous value to ensure that nothing fishy is going on with the key. The value should always 0 when it’s a multi-device passkey and should be randomly larger than the previous signCount when it’s a single-device passkey.

Once you’ve asserted your login, you should be logged in — congratulations! Passkeys is a pretty great protocol, but it does come with some caveats.

Some downsides

There’s a lot of upside to Passkeys, however, there are some issues with it at the time of this writing. For one thing, passkeys is somewhat still early support-wise, with only single-device credentials allowed on Windows and very little support for Linux systems. Passkeys.dev provides a nice table that’s sort of like the Caniuse of this protocol.

Also, Google’s and Apple’s passkeys platforms do not communicate with each other. If you want to get your credentials from your Android phone over to your iPhone… well, you’re out of luck for now. That’s not to say there is no interoperability! You can log in to your computer by using your phone as an authenticator. But it would be much cleaner just to have it built into the operating system and synced without it being locked at the vendor level.

Where are things going?

What does the passkeys protocol of the future look like? It looks pretty good! Once it gains support from more operating systems, there should be an uptake in usage, and you’ll start seeing it used more and more in the wild. Some password managers are even going to support them first-hand.

Passkeys are by no means only supported on the web. Android and iOS will both support native passkeys as first-class citizens. We’re still in the early days of all this, but expect to see it mentioned more and more.

After all, we eliminate the need for passwords, and by doing so, make the world safer for it!

Resources

Here are some more resources if you want to learn more about Passkeys. There’s also a repository and demo I put together for this article.


Passkeys: What the Heck and Why? originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

The Ultimate Guide To Securing Your WordPress Login With Biometric Authentication – For Free!

Defender had already implemented Two-Factor Authentication (2FA) in WordPress for hardened security… now we’ve added Biometrics, too!

It has become increasingly apparent that relying strictly on usernames and passwords for logins no longer offers the highest levels of security.

WPMU DEV’s solution to addressing this is through the use of the WebAuthn standard, which bypasses vulnerabilities by providing a protocol of public key cryptography as a login authentication method.

Our newest Defender release—both Free and Pro versions—marks the start of our odyssey into the world of biometric authentication; providing the ability to verify the authenticity of a user login by way of a device fingerprint reader or facial recognition software.

The use of this new biometric authentication is similar to the existing 2FA methods already present in Defender, and can be used together with the existing TOTP (Time-based One-Time Password), backup codes, and fallback email authentication methods.

In this article, we’re going to look at how to implement the Biometric Authentication feature, as part of our 2FA WordPress plugin features in Defender.

Continue reading, or jump ahead using these links:

Let’s explore all that Defender has to offer in the form of login protection with the cool new 2FA Biometric feature.

The All-Encompassing Defender

Defender gives you the best in WordPress plugin security, stopping SQL injections, cross-site scripting XSS, brute force login attacks—and other vulnerabilities—with a list of one-click hardening techniques that will instantly add layers of protection to your site.

It also makes safety easier on and for you, taking advantage of the latest in biometric security measures.

By way of a quick overview, here’s how this works in Defender… the user will input their username & password to log in, and if biometric authentication has been configured for that device, said user can verify their identity through their fingerprint scanner or facial recognition software.

Because we’re using the WebAuthn protocol, Defender does not at any point receive any biometric data, only a confirmation or rejection from the user’s device.

I want to interject here with a quick point of interest, shared by one of our techs, Marcel Oudejans (and paraphrased by me)…

The convention of naming a dog “Fido” was popularized by Abraham Lincoln, though its use as a canine pet name dates back to the ancient Romans.

Fido” means “faithful”. FIDO stands for “Fast IDentity Online”. The new Biometric authentication feature uses WebAuthn protocol from FIDO.

So in a lovely, roundabout way, by using the FIDO protocol to implement this feature, one could say we are infusing ‘faithfulness’ into Defender.

Synonyms for faithfulness
Faithful FIDO.

For more technical information on FIDO, check out this article.

Ok, now let’s take an in depth look at this awesome new Biometric feature.

Full Walkthrough on Biometric Authentication

First, make sure you have the Defender plugin installed and activated, and update it to the latest version (at the time of this writing, that’s 3.0.1). Defender versions 3.0 and higher are fully compatible with the recently released WordPress 6.0.

Two important things to note up front:

  1. Configuration of authorized devices is required on a per-user basis, since authentication is linked to individual user accounts.
  2. PHP 7.2 or above is required, as it improves performance and security, while also supporting the new biometric feature.

Enable Biometric

Navigate to the WordPress Dashboard > Defender. If you’ve just now updated, you’ll get the popup modal. Give it a quick read, then click the Got It button.

Defender new version modal
Two F’s = Fingerprint and Facial (recognition).

You’ll be on Defender’s main page now. From the left sidebar, click on the 2FA menu header.

Another popup will appear; click on the Activate button.

Defender activate 2FA
One-click activation in Defender.

Now you’ll see all the section information for Two-Factor Authentication, and all the options we have available here.

From the same Defender 2FA page, under User Roles > Administrator, toggle the button On. Make sure to scroll to the bottom and click on Save Changes.

Toggle on Admin user roles.
Permission to enable 2FA is given through User Roles.

From the Dashboard’s side menu, go to the Users section, and click on your Admin User profile.

Scroll down to the Security section, and toggle ON the button next to Biometric.

User role security, enable biometric
The toggle for enabling the Biometric feature is in the Users > Security section.

Once the Biometric feature is toggled on, you’ll see a suggestion to choose an additional authentication method from these options: TOTP, Backup Codes, and Fallback Email.

In the example below, you’ll see I’ve selected Fallback Email, but you can choose whatever method(s) you prefer. Remember to click the Update Profile button at bottom.

Selecting additional authentication methods
The selection of additional authentication methods available in Defender.

Biometric authentication does not replace your traditional WordPress login (i.e., username & password), instead adds an additional secure layer, like the other authentication options above.

While many browsers and operating systems are compatible with the WebAuthn protocol used to manage the authentication process, some are currently not. Check here to see WebAuthn’s browser and OS compatibility list.

Register Device

With biometric authentication enabled, the Registered Device table will appear, with options to Register Device or Authenticate Device.

Registered device identifiers
Defender keeps a list of Registered Device identifiers.

Clicking the Register Device button will start the prompt from your browser to configure the form of biometrics you wish to use, depending on which are available on your device.

Enter any name in the Authenticator Identifier field, then click the Start Registration button.

Register new authenticator
Name your identifiers for easy recognition later.

Note that depending on the device you are using the registration process will differ.

Example 1:

Registering a Windows desktop or laptop will prompt you to enter your Windows Hello PIN, or whatever other authentication method may be enabled on your device.

Windows hello PIN login
The Windows Hello sign in PIN entry.

Example 2:

Registering a mobile device will prompt you to touch the fingerprint sensor, or whatever other authentication method may be enabled on your device.

Verify fingerprint sensor
A sample fingerprint sensor authenticator window.

Back on your Users Profile page, if you scroll to the bottom under Security > Registered Device, you’ll see your device listed here, along with a message beneath it confirming it has indeed been registered.

Registered new authenticator
Confirmation of registering a new authenticator.

The next step is to authenticate the device you just registered.

Authenticate Device

Once the device has been registered, click the Authenticate Device button.

The same authentication method used to register the device will prompt you to confirm the action.

Authenticated device successfully
Success! Confirmation of an authenticated device.

Once done, you’ll see a success message appear. Now you’ll be able to use the registered biometric option as a fast, secure way to login to your site.

Rename or Delete Device

If desired, you can rename or delete any authenticated device.

Navigate to the WordPress Dashboard > Users, and click on your username.

To Rename:

From Profile > Security > Registered device, click on the Rename text in the Action column. Type the new name, and click Save.

Rename or delete registered device
Action options for registered devices.

To Delete:

Same process as above, but click on the Delete text in the Action column, then click OK from the next popup.

Confirm delete action
Confirming the delete of an authentication.

Be advised that the Delete action doesn’t save settings, so if you decide you want to use the Biometric feature from that device again, you will need to go through the full setup process.

Likewise, if you deactivate any biometric functionality on your device, the login will no longer work, and you would need to repeat the process on your device to restore the feature’s functionality.

GDPR Compliance

FIDO Alliance standards were created from the outset with a “privacy by design” approach and are a strong fit for GDPR compliance.

Because FIDO delivers authentication with no third-party involvement or tracking between accounts and services, biometric authentication with FIDO2 compatible devices is fully GDPR compliant.

With FIDO, no personally-identifying information ever leaves your device.

For more information, see the following article on the FIDO website: FIDO Authentication and GDPR.

Enabling Multiple 2FA Methods

If you enable more than one additional authentication method in your profile, each will display as alternate options beneath the method you have set as your default. In the example below, TOTP Authentication is my preferred method.

You can click on any available option in the list, and it will display the selected alternate authentication method.

TOTP authentication
Using a TOTP to authenticate, with alternate methods (per your selection) listed below.

A final note… Biometric authentication requires that the following PHP extensions be enabled on your server: mbstring, GMP, and Sodium. These extensions are enabled by default on all sites hosted by WPMU DEV.

If you are hosting elsewhere and any of them are not enabled on your server, you’ll see an alert like the one below. Reach out to your hosting provider to have them enable the extensions for you so that you can use this feature.

Message alert, requirements not met
If you see this message, don’t panic–you’ll just need some PHP extensions enabled.

Click here for WPMU DEV’s full documentation on Defender’s Biometric authentication feature.

The Complete Package

As protective measures go in WordPress, it’s hard to beat Defender.

Defender has powerful security protocols, including malware scanning, antivirus scans, IP blocking, firewall, activity log, security log, and two-factor authentication (2FA), including the newly added Biometric Authentication.

The latest version of Defender also came with an additional, useful enhancement to Defender’s WP-CLI “scan” command. By using this WP-CLI command and option, if any issues are found, Defender will create a table with results.

Previously, you could only see the results of a malware scan from the back-end of the site (at WP Admin > Defender Pro > Malware scanning), but now you’ll be able to see the completed scan results right in the console.

Coming soon for Defender… we’ll expand on our use of WebAuthn, with our devs currently working on the ability to use hardware authentication devices. Plans are also underway to implement ‘password free’ logins in the best way possible, using the WebAuthn protocol.

You can read about upcoming features for any of our tools and services anytime in our product Roadmap.

If 2FA is the question, Defender is the answer. Handling security in your WordPress sites can be as simple—yet complete—as activating Defender.