Passwords Are Actually Dead This Time
We’ve heard it before. Every five years some pundit declares password death from a stage at a security conference, writes a Medium post that goes nowhere, and then — silence. People keep using passwords. Password managers become the default band-aid, and life goes on.
Except this time it’s actually shipping.
Apple, Google, and Microsoft all shipped passkey support in their operating systems. Websites started asking “use a passkey?” and getting actual adoption. 1Password, Bitwarden, and every major auth platform added passkey support. The FIDO Alliance stopped being a weird niche thing that only hardware security companies cared about. And more importantly: passkeys don’t have the annoying friction that killed previous attempts at killing passwords.
No SMS codes. No “verify it’s you on your other device.” No awkward backup codes in a text file. You register your face or your fingerprint once, and then… you just log in. The phone or the hardware key does the work. Your password manager syncs it across your devices if you want. Phishing doesn’t work anymore.
If you’re running self-hosted services — Nextcloud, Gitea, Vaultwarden, Authentik, whatever — this is your moment to stop asking your users (or yourself) to remember passwords. Here’s what’s actually happening under the hood, how to set it up, and why it’s not as scary as the cryptography sounds.
What WebAuthn Actually Is (Yes, the Cryptography Matters)
WebAuthn is a standard. It’s not a product, not a service, not something you pay for. It’s just a collection of specs from the W3C and the FIDO Alliance that say: “here’s how a browser can ask a device to prove you own a specific private key.”
Passkeys are the user-friendly name for the same thing. One lives in specs, the other lives in marketing. They mean the same technology.
The FIDO2 spec sits underneath WebAuthn. FIDO2 = CTAP (Client to Authenticator Protocol) + WebAuthn. Don’t memorize that. The important bit: it’s public-key cryptography bound to a specific website origin. Not a password. Not a code your phone generates and you type in. A cryptographic handshake where both sides prove they know each other.
Here’s the actual flow:
Registration (Your First Time)
- You land on a website and say “add a passkey”
- The server generates a random challenge (just bytes, usually 32 of them)
- The server asks your browser: “Hey, I need this challenge signed by a key the user controls”
- Your browser goes to your OS, which says “hey, is this the same origin you’re already using?” (origin binding — this is the phishing defense)
- Your OS asks your face/fingerprint/whatever
- Your authenticator (phone, security key, TPM) generates a fresh keypair. Keeps the private key. Sends back the public key + a signed attestation (proof that a real device made this)
- The server stores your public key and remembers that you are associated with it
{ "id": "Gvq-YDLQFI6SZ0XvV...", "rawId": "base64-encoded-credential-id", "type": "public-key", "response": { "clientDataJSON": "eyJjaGFsbGVuZ2UiOiI...", "attestationObject": "o2NmbXRmcGFja2..." }}Authentication (Every Login After)
- You land on the login page
- The server generates a new challenge
- The server asks your browser: “Sign this challenge with the key you registered before”
- Your browser asks your OS: “Is this the same origin they registered on?” (yes — phishing defeated)
- Your OS asks your face/fingerprint
- Your authenticator signs the challenge with the same private key
- The server verifies the signature matches the public key you registered
- Done. You’re logged in. No password. No codes. No secrets transmitted.
curl -X POST https://example.com/webauthn/authenticate \ -H "Content-Type: application/json" \ -d '{ "id": "Gvq-YDLQFI6SZ0XvV...", "rawId": "base64...", "type": "public-key", "response": { "clientDataJSON": "eyJjaGFsbGVuZ2UiOiI...", "authenticatorData": "SZYN5OtPvzAVcVFqTrq+EQAAACw...", "signature": "MEQCIBpd3gZ+yj..." } }'That’s it. No passwords. No timing windows. No replay attacks (the challenge is fresh each time). No SIM swaps. No phishing because the browser enforces that you’re talking to the right origin.
The Three Players
When you implement WebAuthn, three actors are involved:
The Relying Party (that’s your service) creates challenges, stores public keys, verifies signatures. This is where the server-side code lives. Authentik, Nextcloud, Vaultwarden — they’re all relying parties.
The Authenticator (your phone, security key, laptop TPM) holds the private key and never, ever sends it anywhere. It signs challenges. That’s its one job. The private key stays on the device where it was created. Full stop.
The Client (the browser, or the mobile app, or whatever) sits between them. It asks the authenticator to sign things, relays the response back to the relying party, and enforces origin binding so you can’t get phished.
You control the relying party. Your users control the authenticator (they pick their phone or their YubiKey). The browser is someone else’s code, but it implements the same standard, so it doesn’t matter if it’s Chrome, Firefox, Safari, or Edge.
Why Passkeys Beat Everything Else
vs. Passwords: Passwords are memory + type-in attacks. Passkeys are public-key crypto + origin binding. No phishing because the browser cryptographically verifies the origin before signing. No credential spraying across sites because each origin has a different key. No database dumps because there’s no password to dump.
vs. SMS 2FA: SMS is out-of-band (a different channel), which sounds secure but isn’t. SIM swaps exist. Phone number takeovers exist. SMS also adds friction — codes expire, users get them wrong, they have to type them. Passkeys: one biometric, done.
vs. TOTP / Authenticator apps: Time-based codes are still vulnerable to phishing if you can trick the user into entering them in your fake login page. Passkeys ask the OS “is this the real origin?” before the authenticator even considers signing. Also, TOTP codes expire and users have to manage them. Passkeys: just your face.
vs. Hardware keys alone: Hardware keys are great but inconvenient if you have five devices. They can be lost or forgotten at home. Passkeys sync (if you want them to) across your devices via your password manager or OS, so your phone has one, your laptop has one, your work computer has one. Device-specific keys are still supported if you want that, but convenience wins.
Phishing resistance is the big one. All the above can be caught by a clever phishing site that looks like the real thing. Passkeys are bound to the origin (the exact domain) at the cryptographic level. The browser will not ask your authenticator to sign a challenge if you’re on phished-example.com instead of example.com. The attack is mathematically impossible.
Platform vs. Cross-Platform Authenticators
Platform authenticators live on your device: Face ID, Windows Hello, Touch ID, your phone’s biometric. They’re convenient because they’re already there. You don’t need to carry anything extra. They’re bound to that device.
Cross-platform authenticators are external: YubiKey, OnlyKey, Titan key, or a synced passkey from your password manager in the cloud. They work from any device (any browser, any laptop). You can lose them, but they’re portable.
In practice, most people use both. Register your face ID as the quick one for daily use. Register a synced passkey from 1Password or Bitwarden as the backup in case you lose your phone. Some people also register a hardware key as a secondary factor. The relying party doesn’t care — it just stores your public keys and uses whichever one you present.
If you’re building the system (as a self-hoster), you can decide how many authenticators each user can register. Usually: unlimited. Let them add their phone, their laptop, a YubiKey, and a synced passkey from their password manager. If they lose one, they still have four others.
The Sync Passkey vs. Device-Bound Debate
This is where opinions get spicy.
Synced passkeys (the default now) live in your password manager or your OS iCloud/Google account. They follow you. You lose your phone, you buy a new one, and your passkeys are there. Apple added this in iOS 16, Google in Android 13, Microsoft is adding it. Convenience winner.
Security-minded people will say: “But now your password manager (or Apple, or Google) becomes a single point of failure. If they get breached, all your passkeys leak.”
Fair point. But: (a) password managers are very well-protected now, (b) a leaked passkey is less bad than a leaked password because the server also has to verify it’s actually you using the origin binding, and (c) most people will choose convenience over theoretical purity every time.
Device-bound passkeys never leave the device. They’re not synced. If you get a new phone, you re-register. If you lose it, it’s gone — but you can’t lose it from somewhere else because it can’t be exfiltrated.
For a self-hosted service where you are the only user, device-bound is fine and arguably better. For a service where your mom is a user, synced passkeys are non-negotiable.
Most self-hosters will enable both options and let users pick.
Support Across Self-Hosted Services
The good news: WebAuthn support has landed or is landing fast.
Authentik (the auth platform) has WebAuthn built in. It’s in the passwordless flow section. You can make it mandatory or optional. We’ll get to setup in a second.
Authelia added WebAuthn a couple releases ago. Similar approach: optional or required.
Vaultwarden (the Bitwarden server) supports WebAuthn. Open-source and self-hostable.
Nextcloud has WebAuthn support built in as of 2FA methods. Users can add it alongside TOTP.
BookStack has WebAuthn in the auth module.
Gitea added it. Self-hosted Git servers can now use passkeys.
Wiki.js supports it.
The pattern is consistent: add a passkey as a 2FA method, or (if the platform supports it) make passkeys a passwordless login option. Most of these landed in 2024 and early 2025, so if you’re running something old, an update might be in order.
Setting Passkeys Up in Authentik
Authentik is the easiest place to start because it’s a dedicated auth platform. Here’s the gist:
- Upgrade to a recent version (late 2024 or 2025 recommended)
- Go to Flows → Create Flow (or edit an existing login flow)
- Add a Stage → WebAuthn Authenticator (for registration)
- Add a Stage → WebAuthn Authenticate (for login)
- Bind these stages in the flow in the right order
The YAML-ish structure (Authentik’s UI will handle this):
stages: - name: "webauthn-registration" type: "webauthn" # user_verification: preferred (default), required, or discouraged user_verification: "preferred" resident_key_requirement: "discouraged" # don't require device binding
- name: "webauthn-authenticate" type: "webauthn" user_verification: "preferred"Then in your policy bindings, apply this flow to your login. Done.
Users hit your login page, click “Sign in with passkey,” get asked for their face/fingerprint, and they’re in. No password involved.
For fallback (what if they lose their phone?), keep a password login option enabled alongside the passkey one. Or keep TOTP. Or both. The point is: you have options.
When the User Loses Their Phone
This is the question every self-hoster asks first.
Answer: They’re no more locked out than they would be if they lost their password manager.
Design pattern:
- User registers a passkey on their phone
- They also register a backup passkey from their password manager
- They also set up TOTP as a secondary factor
- (optional) They print backup codes and keep them in their safe
If they lose their phone: they log in with their password manager’s passkey (or TOTP + password, if they have that configured). Problem solved.
If they somehow manage to lose everything: you have an admin recovery flow where they can email you and prove their identity. Most platforms support this.
The key: don’t make passkeys the only factor. Make them the easy one, keep other methods around for when things go sideways.
Attestation Verification (You Can Skip This)
Attestation is the cryptographic proof that a real authenticator made the key. When a user registers, the browser sends back an attestation object that basically says “this key was made by a real YubiKey” or “this was made by an iPhone Secure Enclave.”
The relying party can verify this attestation and decide: “I trust keys from YubiKeys and iPhones, but not from software authenticators.”
In theory, great. In practice: almost nobody does this. Most self-hosters turn attestation verification off. They figure: if someone has access to your device’s private key, the origin binding and challenge signature are already defeating phishing, so the attestation doesn’t add much.
If you’re paranoid (or if your service handles sensitive stuff), you can enable it. Most Authentik/Vaultwarden deployments have it off.
The Passkey Provider Landscape
Here’s who’s in this space:
1Password — cloud sync, fantastic UX, paid but worth it. Passkeys sync across all your devices. Available on iOS, macOS, Windows, Android.
Bitwarden — open-source, self-hostable, free tier has passkeys. Same sync story as 1Password. Privacy-conscious choice.
Apple — iCloud Keychain syncs passkeys across your Apple devices. Free if you’re already in the ecosystem. Lock-in to Apple hardware, but seamless if you’re there.
Google — Google Password Manager now stores passkeys. Syncs across devices. Google ecosystem lock-in.
Microsoft — Windows Hello, Microsoft Authenticator app, cloud sync coming. Windows ecosystem player.
For self-hosted services, most users will probably use whatever their OS gave them (Apple/Google/Microsoft) or their password manager (1Password/Bitwarden). As the service operator, you don’t pick. You just accept any WebAuthn credential.
The Trust Question
Here’s the uncomfortable part: you’re trusting someone.
If you enable passkey sync in your password manager, you’re trusting that vendor. If you use Apple Keychain, you’re trusting Apple. If you use Google Password Manager, you’re trusting Google.
Versus: remembering a password (trusting your memory) or writing it down (trusting a piece of paper).
The honest take: password managers are more trustworthy than both those alternatives. They use real encryption, they’ve been audited, they have security teams. A piece of paper can burn. Your memory leaks when you tell someone “my password is…” in a meeting.
So no, it’s not perfect. But it’s better. And it’s better than SMS, which trusts your phone carrier. And better than passwords, which trust your ability to remember random character strings.
For a self-hosted service: enable passkeys and let users pick their own authenticator. They’ll pick something they trust more than they trust you (and that’s fine).
Rolling This Out to Your Users
If you’re running Authentik or Vaultwarden or any of the platforms listed above, the steps are:
- Update to a recent version (if you’re not on late 2024 or newer)
- Create a passwordless flow with WebAuthn stages
- Bind that flow to your login
- Send an email to your users: “You can now log in with passkeys”
- Keep the password option available for six months
- After six months, consider deprecating passwords, but don’t eliminate them unless you’re sure everyone’s migrated
Most users will ignore it for a month, then get annoyed trying to remember a password, and then switch over. Some won’t. That’s fine — they can keep using passwords until you sunset the feature.
The real win: you get to stop answering “did you forget your password?” support tickets. Users who want passkeys get the frictionless experience. Users who don’t have a fallback that works fine.
The Bottom Line
Passkeys are the first authentication method in about fifteen years that actually solves the problems it claims to solve: phishing resistance, no replay attacks, no passwords to forget or manage.
The cryptography is sound. The user experience is finally good. The platform support is there. If you’re running a self-hosted service and you’re not tired of password support yet, you will be soon.
Set up WebAuthn on Authentik or whatever you’re running. Keep passwords as a fallback. Watch your users switch over in their own time. Stop worrying about password strength rules and compromised credential dumps.
Passwords are dead. This time, it actually stuck.