We've all been there. It's 9am, you sign into your work account and the dreaded message appears: Your password has expired. Please select a new password. Maybe you're really security-minded and generate a new random password, somehow remembering it until you inevitably have to change it again. More likely, you just add a number to the end (the year, the month, or something similar) and call it a day. Why do we put up with this?
- "all interactions are local" is slightly false, but I think it accurately gets the point across: your password never leaves your browser for this demo. I make a SHA-1 hash of it in your browser and send the first 5 characters of it to the haveibeenpwned API, then locally check your full SHA-1 against the hashes of pwned passwords the API returns
- No, your password is not the same as your old one. It just does that with the first valid password to make it more frustrating :)
Problems with passwords
The issues with passwords have been well-documented, but here's a short list of reasons they don't work well:
- Passwords are frequently compromised in data breaches. This means you should use a different password for each site (most people don't).
- Good passwords are hard to remember passwords. Ideally, all passwords would be randomly generated, but remembering random passwords for every single service is just not possible.
- Password managers are a good solution to the previous problems, but most users will not use one. Even then, there are still cases where you'd need to remember and type in a password, like to unlock your computer or in a situation where your password manager is inaccessible.
- Sites have varying requirements for what passwords can contain. Some sites require a number, symbol, etc. and some even limit you to 12 or 16 characters max. Sites that require a symbol may not allow all symbols.
- Applications that require password rotation (which used to be a security best-practice) encourage users to use predictable passwords.0
- Users can be tricked into entering their password on deceptive web forms (phishing).
A look back
Alternatives to passwords aren't new. You've probably used biometrics to unlock your phone, and you've probably used the chip on your credit card to pay for something.1 In the federal government (as well as at some other organizations), smart cards are used to access computers and online services.
Smart cards are particularly interesting. They look like a cross between a corporate badge and a chipped credit card. Users have a reader (potentially integrated into their laptop) where they insert the card, then enter a PIN to unlock the card. From there, the card can be used to access the laptop or online services like email. They key challenge of smart cards is that you must have a hardware reader to use a smart card, so they haven't seen much adoption out of highly-secure, highly-specialized industries.
Insert your smart card, then press "Sign in" to authenticate.
- This demo doesn't actually "do" anything - it's just to show what the UX of smart card sign in looks like.
- Smart card UX can vary depending on the implementation.
- While the PIN modal in this demo is just built on this website, the modal would normally be presented by your browser or operating system.
Another solution that's seen adoption is public key cryptography. For this method of authentication, users generate a keypair: a public key and a private key. The public key is sent to services that need to verify their identity, while the private key never leaves the user's device.
When the user wants to sign in, the server generates a challenge using the user's public key, which the user's computer can solve using the private key. The user's computer then sends the solution to the server, which verifies it and grants access.
This authentication method has seen wide adoption through SSH, but hasn't broken out of the technical crowd since it's not very ergonomic to use outside of a command line. Generating keypairs requires a small amount of deliberate work, and the user is ultimately responsible for managing every single keypair they generate.
Passkeys bring public key cryptography to the web. When you sign up for a website using a passkey, you can save the passkey to your phone, your laptop, your password manager, or a hardware token like a Yubikey.2 When you sign in to that application, your device automatically selects the appropriate passkey, asks for your face/pin/fingerprint to unlock the passkey on your device, then signs the challenge from the server and sends it back.
Passkeys are much more secure than passwords: your private keys are never sent to the services you authenticate against and services each get their own passkey, by default. Phishing a passkey is nearly impossible: you simply can't use a passkey on a domain different from the one it was generated on.3 If you're using a passkey on a different device, the pairing procedure uses bluetooth to ensure the device you're pairing with is nearby - so unless your roommate is spear phishing you, you're safe.
- This demo is mostly real! If you complete the registration and sign-in flow, you're seeing exactly how it looks to use a passkey on a website.
- That being said, it's still missing some security features. It doesn't check device attestation (i.e. if a passkey claims to be from an iOS device, I don't try to prove that's true) since that's labor-intensive to implement.
- This demo runs entirely in your browser (try turning off your wifi) and stores credentials in localStorage. WebAuthn challenges should normally be generated on your server, not in the browser.
How does this all work?
The above demo uses a "toy" implementation of passwordless authentication, done by hand. It works entirely in your browser, saving the registered credentials to localStorage. The implementation (available on github) was made based on the W3C specification Web Authentication: An API for accessing Public Key Credentials. Let's walk through it step-by-step.
The first thing we need to do is determine the ground rules for the authenticator. We tell the browser the following things:
- Which kinds of public keys are allowed (ECDSA and RSA, 256-bit in both cases),
- How long the user has to complete the registration process (6 minutes)
- What the Relying Party is (this is the website: we provide a friendly name and hostname that must be either the current domain or a subdomain),
- Some information about the user that the authenticator can store (name, display name, ID - this is all optional), and
- A challenge, consisting of 32 cryptographically random 8-bit integers.
This information is provided to the browser, which processes the information and interacts with the device to generate a passkey. There are several places you can store passkeys:
- On the device itself
- On a separate device, paired with the website via a QR code generated by the web browser
- On a hardware security key, like a Yubikey
- In a password manager
Once the passkey is generated, the public key is sent to the server along with the solution to the challenge. The server verifies the solution, then stores the public key and user information in its database. Some other data is included in this response as well: a counter indicating how many times the passkey has been used (to help detect duplication), flags indicating if the passkey can be backed up and if it is backed up, and a flag indicating if the user performed a verification task (such as presenting a biometric or entering a PIN) when generating the passkey.
The server saves the credential's ID, along with its public key and use count (and other flags, if it wants). This information is retrieved once the user wants to sign in.
Now it's time to actually use the passkey. You type your username into the login form and your browser presents a passkey selection modal. You can use a passkey stored on the device, scan a QR code with your phone to use one from there, tap your FIDO security key, or use your password manager to present your passkey.
Once the server knows who's trying to sign in, it sends a list of allowable credential IDs to the browser. The browser tries to find a matching credential stored on the device, and will give the option to use one stored elsewhere. If you try to use the wrong credential, your browser will actually let you know before even sending it to the server for verification since the IDs won't match.
The authenticator signs the challenge, which the server then verifies using the passkey's public key. Verification involves checking the authenticator's work, extracting the values that it signed from the authenticator response and using a library like SubtleCrypto to verify that the signature is correct for the input values. The private key data never leaves the device it's stored on (unless you're syncing it to other devices).
That's really all there is to it! Passkeys are essentially just public key cryptography for the web, and are (in my opinion) a huge upgrade from passwords.
Downsides of passkeys
Passkeys aren't perfect. They're still a new technology, so they do have some issues. Current implementations suffer from poor portability: it's hard (or in some cases impossible) to migrate passkeys off of one device and onto another. While encrypted sync is available with providers like iCloud Keychain and 1Password, neither currently provides a way to move a passkey from their service into another service.
Compared to passwords, passkeys are a lot more challenging to implement on a website. For passwords, you can simply salt and hash user input and store the result in a database. For passkeys, you need to perform several cryptographic operations, decipher values sent from clients, and handle the varying user experiences that appear across device vendors.
Even the above implementation is not complete: it doesn't check for attestation (essentially: testing if the authenticator is one that has legitimately high security) since each vendor has its own attestation process – Passkeys generated on Apple devices use a different attestation process than those on Android devices. This means that the server can't be sure that the authenticator is secure, but overall the implementation works well enough.
I'm excited to see something that looks like it'll reduce our reliance on passwords. Passkeys aren't a perfect solution yet, but there seems to be sufficient momentum from web browsers and device manufacturers to at least get this supported. We'll have to see if web developers actually integrate this into their sites, though Google, GitHub, and Apple (among others) have already integrated Passkeys into their login flows.
It'll take time for users to learn this new process for signing into applications, but overall I think it's a better user experience than password management and can see it really catching on if some of the edges are smoothed out (like cross-platform transfers).
- W3C - Web Authentication: An API for accessing Public Key Credentials (what most of this post is based on)
- FIDO Alliance - Passkeys
- Apple Support - Use passkeys to sign in to apps and websites on iPhone
- Google Help Center - Sign in with a passkey instead of a password
0Password rotation has long been thought to be a security best practice, but NIST guidelines have recently changes to note that "Verifiers SHOULD NOT require memorized secrets to be changed arbitrarily (e.g., periodically)." (section 184.108.40.206). Also notable - NIST advises against arbitrary character requirements like including special characters: "Verifiers SHOULD NOT impose other composition rules (e.g., requiring mixtures of different character types or prohibiting consecutively repeated characters) for memorized secrets."
1Credit card numbers are remarkably similar to passwords. It's a static secret that, once exposed, will completely fuck you over. Magnetic stripes are essentially barcodes that present your card number, along with information like your name, to the payment terminal (the key difference being that you can't just snap a picture of the stripe).
2Yubikeys (or generically, hardware security keys) are small devices that connect to your computer or phone over USB, NFC, or Bluetooth and can be used as an alternative to entering a code for multifactor authentication. They use the same underlying technology as Passkeys, but have typically been used only as a second factor for authentication rather than the primary factor.
3Part of the WebAuthn registration and verification ritual is that the relying party (the website) sends the site's domain to the authenticator. If the domain does not match the site the user is visiting, the browser will reject the request. This means malicious websites can't trick your authenticator into sending back a credential for another site.
Want to chat about this post? Shoot me an email - you can reach me at me@[any of my domains].