DEV Community

Cover image for How Can Passkeys Let You Log In Without Ever Sending Your Password?
Tanu Priya
Tanu Priya

Posted on

How Can Passkeys Let You Log In Without Ever Sending Your Password?

Imagine a company discovers that attackers have stolen its user database. If passwords were stored correctly, the attackers would still need to crack the password hashes before using them. But if users reused those passwords on other websites, a single breach could put several accounts at risk.

Now imagine an authentication system in which the website never receives a password in the first place. That's the idea behind passkeys. You select your account, verify yourself with your fingerprint, face, or device PIN, and you're signed in. Behind that simple interaction, your device uses public-key cryptography to prove that you possess the right credential.

The interesting part isn't that you can log in without typing. It's that the website can verify your authentication without receiving your private key or asking you to disclose a password. Let's look at how passkeys work, why they make phishing much harder, and what developers should understand before implementing them in a real application.


Why Passwords Remain a Security Problem

Passwords aren't inherently insecure. HTTPS protects them while they're being transmitted, and properly implemented authentication systems store salted password hashes rather than plaintext passwords. The problem is what happens around them.

People reuse passwords, phishing websites imitate legitimate login pages, and attackers steal password databases to attempt offline cracking. Even a strong password can be compromised if someone enters it on a convincing fake website. Multi-factor authentication helps, but some methods, including SMS codes and one-time passwords entered into phishing pages, can still be intercepted or relayed.

Consider a simple example: you use the same password for a shopping website and another service. The shopping website suffers a breach, and an attacker recovers your password. They can now try those credentials against your other accounts. A password is a reusable secret, which means obtaining it can give an attacker more than one opportunity to gain access.

What if logging in didn't require you to hand over a reusable secret at all? That's where public-key cryptography comes in.


How Passkeys Work: Two Keys, Different Responsibilities

Passkeys use public-key cryptography. Instead of asking the website to verify a secret you provide, they allow it to verify cryptographic proof generated using a private key. During registration, your authenticator or passkey provider creates a public-private key pair associated with your account.

The public key is sent to the website and stored on its server. The private key remains protected by your authenticator or passkey provider and is used to produce authentication signatures. Although the keys are mathematically related, knowing the public key does not make it practically feasible to derive the private key using modern cryptographic techniques.

This changes what the server needs to protect. An attacker who obtains the public key cannot automatically use it to authenticate as you. The private key is the important part, and the website doesn't need to receive it.

There's another detail worth understanding: your fingerprint isn't the passkey itself. It is one way to authorize your device's authenticator to use the credential. Depending on the device, you might use face recognition, a PIN, or another supported verification method. The local verification step authorizes access to the credential, while cryptography provides the website with evidence that the credential was used.


Passkey Registration: Establishing Trust

Before a website can authenticate you with a passkey, it needs a public key associated with your account. Suppose you're building a web application and want to add a Create a passkey button to its account settings.

When the user requests registration, the backend generates a fresh, unpredictable challenge and prepares the WebAuthn registration options. The browser then invokes the Web Authentication API, which interacts with the user's authenticator or passkey provider. The authenticator creates a credential and returns a registration response.

The backend verifies the response, including the challenge and expected origin, and stores the public key, credential ID, and other required credential information. The private key is not uploaded to the backend.

The website now has the information it needs to verify future authentication attempts. Unlike password authentication, the server can retain a public key that is safe to disclose rather than a password hash that attackers might try to crack. This doesn't make the entire application automatically secure, though. Account records, sessions, recovery mechanisms, and the rest of the infrastructure still need proper protection.


What Happens When You Log In?

Registration establishes the relationship between your account and the public key. Authentication is where that relationship gets used.

Imagine you've registered a passkey for bank.example. A few days later, you visit the website and select Sign in with a passkey. The backend generates a fresh challenge and sends authentication options to the browser. The browser invokes WebAuthn authentication, typically through navigator.credentials.get(), and your authenticator locates the appropriate credential. Depending on the configuration, you may be asked to verify yourself using your fingerprint, face, or device PIN.

Once any required user verification succeeds, the authenticator uses the private key to sign the relevant authentication data. This is not simply a signature over an arbitrary string. WebAuthn uses structured data that binds the response to the challenge and relying party, along with other security-relevant information.

The browser returns the authentication response to the backend. The response contains the information needed to verify the assertion, but it does not contain the private key. The backend checks the signature against the stored public key and validates the challenge, origin, relying-party ID, and other applicable requirements. If the checks succeed, the application can establish an authenticated session.

Notice what never happened: you didn't type a password into the website, and your private key wasn't transmitted to the server. The server received cryptographic evidence that the registered credential was used, then verified that evidence against the expected authentication context.


How Can the Server Verify Something It Never Received?

This comes down to digital signatures. A digital signature allows someone to demonstrate control of a private key without revealing it. Anyone with the corresponding public key can verify the signature, but that verification does not reveal the private key.

Here's a simplified illustration of the idea:

// Conceptual pseudocode, not a WebAuthn implementation

const signature = sign(privateKey, authenticationData);

const valid = verify(
  publicKey,
  authenticationData,
  signature
);
Enter fullscreen mode Exit fullscreen mode

The server doesn't need access to privateKey. It needs the public key, the expected authentication context, and the signature to perform verification.

Actual WebAuthn authentication is more involved than this example. The signature covers authenticator data combined with a hash of the client data JSON, which includes information such as the challenge and origin. The server must validate the contents of that data as well as the signature.

This is important because an attacker might record a valid authentication response and attempt to submit it again later. A properly implemented WebAuthn flow verifies that the challenge matches the current login attempt and has not already been accepted. It also checks the origin and relying-party details instead of treating every valid-looking signature as sufficient.

The cryptography provides the proof; the protocol establishes what that proof is allowed to mean. A cryptographic primitive can work exactly as designed while the surrounding application still contains a security vulnerability.


Why Passkeys Make Phishing Much Harder

Imagine an attacker creates a convincing copy of a bank's login page at bank-example.attacker.com. The logo, colors, and login form look identical to the legitimate website. With password-based authentication, a user might enter their credentials without noticing the different domain, allowing the attacker to capture the password and attempt to use it elsewhere.

A passkey changes this interaction because WebAuthn credentials are associated with a relying party, generally identified through the website's domain. The browser and authenticator use that relying-party context during authentication. A passkey registered for bank.example cannot simply be used by the unrelated domain bank-example.attacker.com.

The fraudulent page cannot ordinarily obtain a valid authentication response for the bank just by displaying a fingerprint prompt. The credential is bound to the legitimate relying-party context, and the server checks the expected origin and relying-party information. This is why passkeys are considered strongly resistant to conventional phishing attacks.

However, phishing resistance doesn't mean complete protection against every attack. An attacker might still steal an authenticated session, compromise a device, exploit a vulnerable application, or take advantage of a weak account recovery process. Passkeys remove an important attack path, but the rest of the application still needs sound security practices.


Does the Website Receive Your Fingerprint?

No. The website doesn't need your fingerprint or face data to authenticate you with a passkey. When you use biometric verification, the check happens locally through your device's supported authentication mechanism. If verification succeeds, the authenticator permits the required credential operation.

The website receives the authentication response, not your fingerprint image or biometric template. Depending on the authenticator and configuration, user verification may instead use a device PIN or another supported method.

This separation matters for privacy as well as security. A website can authenticate a user without building a database of biometric information. It is also useful to distinguish user presence, which indicates interaction with the authenticator, from user verification, which indicates that the authenticator verified the user through an appropriate local method. The exact requirements depend on the WebAuthn request and the application's security policy, so developers should configure and verify them deliberately rather than assuming every passkey interaction involved a fingerprint.


What Happens If You Lose Your Device?

Passkeys improve authentication, but developers must also consider how users regain access when a device is lost or replaced. The answer depends on the type of credential and how it is managed.

Some passkeys can be synchronized across devices through a passkey provider. If a user replaces their phone and regains access to that provider, their passkeys may become available on the new device. Other credentials are bound to a particular authenticator. If that device becomes unavailable, the user may need another registered credential or an account recovery process.

For developers, passkey registration should not be treated as a feature that can be forgotten after launch. Users need a way to manage registered credentials, add another passkey, and revoke a lost or compromised credential. They also need a recovery process that doesn't become an easy way for attackers to bypass authentication.

For example, if an application requires a passkey for login but allows anyone who knows an email address to reset an account through a weak recovery flow, that recovery mechanism may become the easiest way into the account. A secure authentication system must consider the entire journey: registration, login, session management, credential revocation, and account recovery.


Implementing Passkeys in a JavaScript Application

If you're building with Next.js, React, or another JavaScript framework, you don't need to implement public-key cryptography yourself. Browsers expose WebAuthn through the Credentials Management API. Two methods are particularly relevant: navigator.credentials.create() creates a credential during registration, while navigator.credentials.get() requests an existing credential during authentication.

Here's what the browser-side API calls look like:

// Registration: options are prepared by your backend
const credential = await navigator.credentials.create({
  publicKey: registrationOptions,
});

// Authentication: options are prepared by your backend
const assertion = await navigator.credentials.get({
  publicKey: authenticationOptions,
});
Enter fullscreen mode Exit fullscreen mode

These snippets demonstrate the browser API calls, not a complete authentication implementation. The registrationOptions and authenticationOptions variables represent the public-key options prepared by your backend for their respective flows.

The browser returns credential objects that must be serialized appropriately before being sent to the backend. Your server must then verify the registration response or authentication assertion. That includes generating and tracking unpredictable challenges, checking the expected origin and relying-party ID, validating signatures and authenticator data, storing credential IDs and public keys, and enforcing the application's user-verification and session policies.

You also need to handle errors, unsupported authenticators, user cancellation, and situations where the requested credential isn't available. For JavaScript and TypeScript projects, SimpleWebAuthn is a useful library to explore. It provides browser-side and server-side tools for WebAuthn, allowing you to build on an established implementation rather than writing the protocol from scratch.

One practical recommendation is worth emphasizing: don't treat a successful browser API call as proof that the user has been authenticated. The server must verify the returned response before granting access. Otherwise, the application risks trusting client-side behavior that an attacker can manipulate. For production systems, use a maintained WebAuthn implementation and follow its current documentation.


What Passkeys Change for Developers

Passkeys aren't just another login option. They change the kind of evidence an application uses to authenticate a user. With passwords, the user demonstrates knowledge of a secret that can be copied, reused, phished, or stolen. With passkeys, the authenticator produces a cryptographic response using a protected private key, and the server verifies that response using the public key registered to the account.

This doesn't eliminate every security problem, but it reduces the need to handle reusable passwords and makes conventional credential-phishing attacks significantly harder. For users, that can mean fewer passwords to remember and a simpler sign-in experience. For developers, it means adopting a different authentication model and thinking carefully about credential registration, verification, recovery, and session security.

The real shift isn't from typing a password to scanning a fingerprint. It's from asking users to disclose a reusable secret to verifying cryptographic proof without receiving that secret. That's the part of passkeys worth understanding, especially if you're building modern web applications.

Top comments (0)