Back to blog

Passkeys and WebAuthn: The Future of Authentication Has Arrived

Hello HaWkers, you've probably already seen the "Sign in with Passkey" option appearing on more and more websites. This week, Telegram announced passkeys support, following a trend that is transforming how we authenticate on the web.

Do you still depend on traditional passwords? Let's understand why passkeys are considered the future of authentication and how to implement them in your applications.

What Are Passkeys and WebAuthn

Passkeys and WebAuthn are not competing technologies - they are parts of a system that works together to enable secure passwordless login.

WebAuthn (Web Authentication)

WebAuthn is a standard API developed by the FIDO Alliance and W3C that enables logins without traditional passwords.

Main characteristics:

  • Based on public key cryptography
  • W3C standard since 2019
  • Supported by all modern browsers
  • Core component of the FIDO2 project

Passkeys

Passkeys are FIDO authentication credentials that allow login using the same device unlock process.

How they work:

  • Use biometrics, PIN or device pattern
  • Synced between devices via cloud
  • Don't require memorizing passwords
  • Resistant to phishing by design

Why Passkeys Are More Secure

Traditional passwords have fundamental security problems that passkeys solve by design.

Password Problems

Common vulnerabilities:

Problem Passwords Passkeys
Reuse Common Impossible
Phishing Vulnerable Immune
Database leak Data exposed Only public key
Brute force Possible Impossible
Keyloggers Vulnerable Immune

How Passkeys Protect

1. Domain-bound:
A passkey created for yourbank.com cannot be used on yourbank.fake.com, even if the user is tricked.

2. No shared secret:
The server stores only the public key, which is useless without the private key that never leaves the device.

3. Unique per service:
Each passkey is a unique key pair, so a leak from one service doesn't affect others.

How It Works Technically

Let's understand the registration and authentication flow with WebAuthn.

Registration Flow

// 1. Server generates challenge and options
const publicKeyCredentialCreationOptions = {
  challenge: new Uint8Array(32), // Random challenge from server
  rp: {
    name: "My Application",
    id: "mysite.com"
  },
  user: {
    id: new Uint8Array(16), // User's unique ID
    name: "user@email.com",
    displayName: "User Name"
  },
  pubKeyCredParams: [
    { alg: -7, type: "public-key" },   // ES256
    { alg: -257, type: "public-key" }  // RS256
  ],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    residentKey: "required",
    userVerification: "required"
  },
  timeout: 60000
};

// 2. Browser requests credential creation
const credential = await navigator.credentials.create({
  publicKey: publicKeyCredentialCreationOptions
});

// 3. Send public key to server to store
await fetch('/api/auth/register', {
  method: 'POST',
  body: JSON.stringify({
    id: credential.id,
    rawId: btoa(String.fromCharCode(...new Uint8Array(credential.rawId))),
    response: {
      clientDataJSON: btoa(String.fromCharCode(
        ...new Uint8Array(credential.response.clientDataJSON)
      )),
      attestationObject: btoa(String.fromCharCode(
        ...new Uint8Array(credential.response.attestationObject)
      ))
    }
  })
});

Authentication Flow

// 1. Server sends challenge
const publicKeyCredentialRequestOptions = {
  challenge: new Uint8Array(32), // New challenge from server
  rpId: "mysite.com",
  allowCredentials: [], // Empty for discoverable passkeys
  userVerification: "required",
  timeout: 60000
};

// 2. Browser requests authentication
const assertion = await navigator.credentials.get({
  publicKey: publicKeyCredentialRequestOptions
});

// 3. Send signature for server verification
const response = await fetch('/api/auth/login', {
  method: 'POST',
  body: JSON.stringify({
    id: assertion.id,
    rawId: btoa(String.fromCharCode(...new Uint8Array(assertion.rawId))),
    response: {
      clientDataJSON: btoa(String.fromCharCode(
        ...new Uint8Array(assertion.response.clientDataJSON)
      )),
      authenticatorData: btoa(String.fromCharCode(
        ...new Uint8Array(assertion.response.authenticatorData)
      )),
      signature: btoa(String.fromCharCode(
        ...new Uint8Array(assertion.response.signature)
      ))
    }
  })
});

Practical Implementation

To facilitate implementation, libraries exist that abstract the complexity.

SimpleWebAuthn (Recommended)

// Frontend - @simplewebauthn/browser
import {
  startRegistration,
  startAuthentication
} from '@simplewebauthn/browser';

// Registration
const registrationOptions = await fetch('/api/webauthn/register-options')
  .then(res => res.json());

const registration = await startRegistration(registrationOptions);

await fetch('/api/webauthn/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(registration)
});

// Login
const authOptions = await fetch('/api/webauthn/auth-options')
  .then(res => res.json());

const authentication = await startAuthentication(authOptions);

await fetch('/api/webauthn/authenticate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(authentication)
});

Backend with Node.js

// Backend - @simplewebauthn/server
import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} from '@simplewebauthn/server';

// Generate registration options
app.get('/api/webauthn/register-options', async (req, res) => {
  const options = await generateRegistrationOptions({
    rpName: 'My Application',
    rpID: 'mysite.com',
    userID: user.id,
    userName: user.email,
    attestationType: 'none',
    authenticatorSelection: {
      residentKey: 'required',
      userVerification: 'required'
    }
  });

  // Save challenge in session
  req.session.challenge = options.challenge;

  res.json(options);
});

// Verify registration
app.post('/api/webauthn/register', async (req, res) => {
  const verification = await verifyRegistrationResponse({
    response: req.body,
    expectedChallenge: req.session.challenge,
    expectedOrigin: 'https://mysite.com',
    expectedRPID: 'mysite.com'
  });

  if (verification.verified) {
    // Save credential in database
    await saveCredential(user.id, verification.registrationInfo);
    res.json({ success: true });
  }
});

2025 Updates

The passkeys ecosystem continues to evolve with significant improvements.

WebAuthn Level 3

The WebAuthn Level 3 specification was published as a Working Draft in January 2025, bringing:

  • Enhanced APIs for creating and using passkeys
  • New security features
  • Better cross-device support

NIST Guidelines

NIST will finalize the Digital Identity Guidelines (SP 800-63-4) in July 2025:

  • Passkeys recognized as "syncable authenticators"
  • Can achieve Authenticator Assurance Level 2 (AAL2)
  • Phishing-resistant MFA required for US federal agencies

Passkey Portability

The FIDO Alliance is developing new protocols:

  • CXP (Credential Exchange Protocol) - Secure transfer between providers
  • CXF (Credential Exchange Format) - Standard exchange format

Platforms with Support

Passkeys are already supported by major platforms.

Sync by Provider

Provider Sync Platforms
Apple iCloud Keychain iOS, macOS
Google Google Password Manager Android, Chrome
Microsoft Windows Hello Windows 10/11
1Password App All
Dashlane App All

Service Adoption

Popular services with passkey support:

  • Google
  • Microsoft
  • Apple
  • Amazon
  • GitHub
  • PayPal
  • eBay
  • Nintendo
  • Telegram (November 2025)

Implementation Considerations

When adding passkeys to your application, consider these points.

Fallback Required

Not all devices support passkeys. Keep alternative options:

// Check browser support
const supportsWebAuthn = () => {
  return window.PublicKeyCredential !== undefined &&
    typeof window.PublicKeyCredential === 'function';
};

// Check conditional passkey support
const supportsConditionalUI = async () => {
  if (!supportsWebAuthn()) return false;

  return await PublicKeyCredential
    .isConditionalMediationAvailable?.() ?? false;
};

UX Considerations

  • Offer passkeys as an option, not mandatory
  • Explain benefits to users
  • Allow registration of multiple passkeys
  • Keep recovery method

Conclusion

Passkeys represent the biggest evolution in web authentication since password popularization. With broad browser and operating system support, intrinsic phishing resistance, and superior user experience, it's the right time to start implementing.

For developers, libraries like SimpleWebAuthn make implementation accessible. For users, the experience is simpler and more secure than traditional passwords.

If you want to deepen your knowledge in web security, I recommend checking out another article: GitHub Actions Pricing Changes 2026 where you'll discover how to protect your CI/CD pipelines.

Let's go! 🦅

💻 Master JavaScript for Real

The knowledge you gained in this article is just the beginning. There are techniques, patterns, and practices that transform beginner developers into sought-after professionals.

Invest in Your Future

I've prepared complete material for you to master JavaScript:

Payment options:

  • 1x of $4.90 no interest
  • or $4.90 at sight

📖 View Complete Content

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments