The Passkey Promise vs. The Passkey Reality
Passkeys arrived with a compelling security pitch: no passwords to steal, no phishing pages that work, no credential databases worth breaching. The FIDO Alliance and major platform vendors — Apple, Google, Microsoft — backed this narrative with real cryptographic substance. Passkeys bind authentication to a specific origin, meaning a fake login page simply cannot harvest a valid credential. That part is true, and it matters.
The problem is what the marketing leaves out.
The phishing-resistance guarantee is conditional. It holds only when the legitimate relying party’s web application is clean — no injected scripts, no compromised third-party dependencies, no cross-site scripting vulnerabilities anywhere in the registration or authentication flow. The moment malicious JavaScript executes on the page, the cryptographic guarantees collapse at the application layer before they ever get the chance to help.
A single XSS vulnerability lets an attacker call the WebAuthn registration API directly from inside the victim’s browser session. The attacker registers their own authenticator against the victim’s account. The server records a successful, legitimate-looking passkey registration. The victim sees nothing unusual. The attacker now holds a persistent authentication backdoor — one that survives password resets, looks identical to a real passkey in any audit log, and bypasses the very control the organization deployed to prevent exactly this outcome.
This is not a theoretical edge case. It is the direct consequence of deploying a strong authentication mechanism on top of a web application layer that most security teams have not hardened to the standard passkeys implicitly require.
Mainstream adoption guides from platform vendors, identity providers, and developer documentation sites consistently skip this dependency. They describe WebAuthn flows, explain resident keys, walk through attestation formats — and say almost nothing about the client-side security posture that must exist underneath all of it. Enterprises reading those guides finish implementation believing they have closed the phishing risk. They have — while opening a different one.
The gap between the marketing narrative and the actual threat model is exactly where sophisticated attackers focus. They do not fight strong cryptography head-on. They route around it, through a vulnerable content security policy, an unvalidated third-party script, or a single reflected XSS parameter that nobody flagged as critical because “we use passkeys now.”
How XSS Turns a Passkey Into a Backdoor
Cross-Site Scripting gives an attacker full JavaScript execution inside a victim’s browser session on the target origin. That single capability is enough to completely compromise a passkey deployment, and almost no implementation guide addresses it.
Here is the precise attack sequence. The attacker finds an XSS vulnerability on the relying party’s domain — a stored payload in a profile field, a reflected parameter in a search result, anything that runs their script in the authenticated user’s session. That script silently calls navigator.credentials.create(), the WebAuthn registration API, using an authenticator the attacker controls. The browser executes the call under the victim’s session credentials. The server receives a well-formed, cryptographically valid registration response and records a new passkey against the victim’s account. No dialog appears. No confirmation prompt fires. The user sees nothing and notices nothing.
The consequences are severe. The attacker now holds a persistent authentication backdoor tied to a legitimate account. They can return at any time, authenticate as the victim using their own device, and the login succeeds — because the passkey is real. Every layer of the passkey security promise holds except the one that was silently undermined during registration.
The server-side audit log records a successful passkey registration. Security tooling sees no anomaly. The event looks identical to a legitimate user adding a second device. Incident response teams reviewing logs after a breach find a clean audit trail pointing to authorized activity, which dramatically complicates forensic investigation and delays containment.
This attack works precisely because the WebAuthn specification, by default, allows attestation: "none" — meaning the server accepts any authenticator without verifying its origin, manufacturer, or trust chain. The server cannot distinguish an attacker-controlled software authenticator from a legitimate hardware security key. The registration succeeds, and the backdoor is established in a single page load.
The technical irony is sharp. Passkeys were built to defeat phishing by binding credentials to the correct origin. XSS operates on the correct origin. The phishing defense is irrelevant when the attacker is already inside the trusted context.
The ‘Attestation None’ Problem: The Missing Context Most Coverage Ignores
Most passkey deployment guides walk developers through generating a challenge, calling navigator.credentials.create(), and storing the resulting public key. What they skip — consistently — is a single parameter that determines whether any of that security is verifiable: attestation.
The WebAuthn specification defines an attestation mechanism precisely for this scenario. When a credential is registered, the authenticator can cryptographically sign a statement about its own identity — its make, model, firmware version, and whether it meets specific security certifications. The relying party server can check that signature against manufacturer certificates to confirm it’s dealing with a real, trusted hardware device.
The default in virtually every tutorial, SDK quickstart, and reference implementation is attestation: "none". With that setting, the server accepts whatever credential arrives and asks zero questions about where it came from. A YubiKey hardware token and a software authenticator running inside attacker-controlled malware are, from the server’s perspective, identical. Both produce a public key. Both pass registration. Both will authenticate successfully on the next login attempt.
This is the exact condition that makes a silent XSS-driven passkey registration catastrophic rather than merely serious. An attacker who injects JavaScript into a logged-in session can call navigator.credentials.create() with their own authenticator, submit the resulting credential to the server’s registration endpoint, and walk away with a persistent authentication backdoor. The server logs a successful passkey registration. No alert fires. No anomaly surfaces. The user’s account now has a second passkey that belongs entirely to the attacker.
Attestation enforcement would break that attack. If the server demanded a verified attestation statement and validated it against the FIDO Metadata Service — a free, continuously updated registry of authenticator certifications maintained by the FIDO Alliance — a software credential generated by malware would fail that check immediately. The registration would be rejected before the backdoor was ever written to the database.
The decision to ship attestation: "none" is a deliberate trade-off: broader device compatibility, simpler server-side logic, no dependency on certificate chains. Those are legitimate engineering reasons. But they carry a cost that most adoption guides never price in — and that cost is the complete inability to detect credential injection when XSS strikes.
The Blast Radius: Why This Is Worse Than Ordinary XSS
Traditional XSS attacks carry a built-in expiration date. Steal a session token and your access dies when the session ends, the user logs out, or the server rotates credentials. Security teams have built entire incident response playbooks around this assumption: detect the breach, invalidate sessions, force a password reset, and the attacker is locked out. That playbook fails completely against a passkey registered through XSS.
A passkey is not a stolen credential — it is a newly minted, fully legitimate one. The private key lives on the attacker’s device. The public key sits in the victim’s account, trusted by the server, registered under normal conditions from the server’s perspective. Nothing in that chain expires. The attacker can return a week later, a month later, or a year later and authenticate successfully. Session invalidation does nothing. A password reset does nothing. Forcing MFA re-enrollment does nothing, because the attacker’s passkey is an MFA-grade credential.
This breaks the standard severity model for XSS. Organizations routinely classify XSS findings on web properties as serious-but-manageable vulnerabilities — patched, logged, closed. An XSS finding on a site that supports passkey registration is a different category of event entirely. The vulnerability is not just a web application flaw anymore; it is a breach of identity infrastructure. The attacker’s credential persists in the account store, indistinguishable from a credential the legitimate user enrolled themselves.
That reclassification carries immediate practical consequences. Compliance frameworks including SOC 2, ISO 27001, and regulations like GDPR and HIPAA treat identity compromise differently than a web vulnerability. Breach notification obligations, mandatory incident timelines, and audit requirements all shift when the incident involves persistent unauthorized access to user accounts rather than a transient session hijack. Security teams that close an XSS ticket without auditing registered passkeys for every affected account are leaving an active backdoor in production while believing they have remediated the incident.
The blast radius is not theoretical. Any organization that deployed passkeys to reduce phishing risk but left XSS vulnerabilities unresolved has swapped one threat model for a worse one — a backdoor with no natural expiration, no password to reset, and no session to kill.
What Developers and Security Teams Must Do Differently
Knowing the threat exists is not enough. Developers and security teams need to make four concrete changes, starting now.
Enforce attestation verification at registration time. When a new passkey is registered, your server must validate that the credential actually came from a legitimate authenticator — a hardware key, a trusted platform module, a recognized device. The WebAuthn specification allows servers to request attestation, but most deployments skip it and accept attestation: "none" by default. That default is a mistake. Requiring and verifying authenticator attestation means an attacker who injects a rogue credential through XSS cannot complete a silent registration — the server rejects a credential it cannot vouch for.
Reclassify XSS findings to critical when passkeys are in scope. The standard severity calculus no longer holds. An XSS vulnerability in an application that supports passkey registration is not merely a high-severity finding — it is a direct path to persistent account takeover with no user interaction after the initial injection. Your vulnerability management policy must reflect this. If your scoring framework is CVSS, apply the scope-changed and high-impact modifiers accordingly. Brief your security operations team so triage reflects the updated risk.
Add out-of-band notification for every new credential registration. Send the account holder an email or SMS the moment a new passkey is added to their account. This creates a detection layer that operates entirely outside the browser, meaning an attacker who successfully injects a credential through XSS immediately triggers a visible alert. Users who receive an unexpected notification can revoke the credential and report the incident before the backdoor is exploited. This is one of the cheapest controls available and one of the most effective.
Deploy a strict Content Security Policy. A well-configured CSP that blocks inline scripts and restricts third-party sources significantly reduces the surface area available for XSS exploitation. CSP is not a substitute for eliminating XSS — it is a containment layer that buys time and reduces blast radius. Eliminating injection vulnerabilities through input validation, output encoding, and rigorous code review remains the root fix. CSP is the safety net, not the solution.
These four measures together — attestation enforcement, severity reclassification, out-of-band alerts, and layered CSP — address the gap that passkey adoption guides consistently ignore.
The Bigger Lesson: New Auth Tech Doesn’t Retire Old Vulnerabilities
The passkey marketing narrative has a consistent shape: passkeys defeat phishing, passkeys eliminate credential stuffing, passkeys replace the weak password. All of that is true. What the narrative omits is equally true — passkeys do nothing to stop an attacker who already has JavaScript execution on the relying party’s origin.
This asymmetry matters because it creates a false sense of completion. Security teams deploy passkeys, check the authentication hardening box, and move on. The underlying web application, with its third-party scripts, its DOM injection points, its unaudited npm dependencies, receives no additional scrutiny. That is exactly backward. A successful passkey registration is irreversible in any practical sense — there is no password reset flow that invalidates it, and most implementations provide no real-time alert when a new authenticator is added. An attacker-registered passkey can sit in an account for weeks before anyone notices, if anyone notices at all.
The stakes are higher than they were with passwords precisely because the credential is harder to revoke and the attack leaves a cleaner audit trail. When a password gets stolen and used, anomalous login patterns — new IP, new device, odd hours — often trigger detection. When an attacker registers a passkey via XSS and authenticates with their own hardware, the authentication event looks legitimate by every technical measure the system records. Identity compromise, regulatory exposure, and audit-trail ambiguity follow from a vulnerability class that predates passkeys by two decades.
Threat models for passkey rollouts must treat XSS as a direct attack vector against the authentication system itself, not as a separate application-layer concern. That means Content Security Policy headers configured to block inline scripts and restrict third-party sources, regular audits of every script running on registration and authentication pages, and server-side registration logic that enforces attestation and flags any new credential registered within an unusual time window. It also means educating every stakeholder involved in the rollout that stronger authentication raises the cost of the failure modes it does not address. Deploying a phishing-resistant credential on a page that an attacker can script is not a security upgrade — it is a security trade.