Passkeys WebAuthn
Passkeys WebAuthn FIDO2 Passwordless Authentication Biometric Fingerprint Face ID Public Key Cryptography SaaS Phishing-resistant Cross-device iCloud Google Password Manager
| Auth Method | Phishing-safe | UX | Setup | เหมาะกับ |
|---|---|---|---|---|
| Passkeys | ใช่ 100% | ดีมาก | ปานกลาง | Modern SaaS |
| Password + MFA | บางส่วน | แย่ | ง่าย | Legacy |
| Magic Link | บางส่วน | ดี | ง่าย | Low-friction |
| OAuth (Google) | ใช่ | ดี | ง่าย | Consumer |
| Security Key | ใช่ 100% | ปานกลาง | ยาก | High-security |
WebAuthn Implementation
# === WebAuthn Registration & Authentication ===
# npm install @simplewebauthn/server @simplewebauthn/browser
# Server — Registration (Node.js)
# import {
# generateRegistrationOptions,
# verifyRegistrationResponse,
# } from '@simplewebauthn/server';
#
# const rpName = 'My SaaS App';
# const rpID = 'app.example.com';
# const origin = 'https://app.example.com';
#
# // Step 1: Generate registration options
# app.post('/api/auth/register/options', async (req, res) => {
# const user = await getUser(req.session.userId);
# const options = await generateRegistrationOptions({
# rpName,
# rpID,
# userID: user.id,
# userName: user.email,
# attestationType: 'none',
# authenticatorSelection: {
# residentKey: 'preferred',
# userVerification: 'preferred',
# },
# });
# req.session.challenge = options.challenge;
# res.json(options);
# });
#
# // Step 2: Verify registration
# app.post('/api/auth/register/verify', async (req, res) => {
# const verification = await verifyRegistrationResponse({
# response: req.body,
# expectedChallenge: req.session.challenge,
# expectedOrigin: origin,
# expectedRPID: rpID,
# });
# if (verification.verified) {
# await saveCredential(req.session.userId, {
# credentialID: verification.registrationInfo.credentialID,
# publicKey: verification.registrationInfo.credentialPublicKey,
# counter: verification.registrationInfo.counter,
# });
# res.json({ success: true });
# }
# });
# Browser — Registration
# import { startRegistration } from '@simplewebauthn/browser';
#
# async function registerPasskey() {
# const options = await fetch('/api/auth/register/options',
# { method: 'POST' }).then(r => r.json());
# const result = await startRegistration(options);
# const verification = await fetch('/api/auth/register/verify',
# { method: 'POST', body: JSON.stringify(result) }).then(r => r.json());
# if (verification.success) alert('Passkey created!');
# }
from dataclasses import dataclass
@dataclass
class PasskeyCredential:
user: str
device: str
authenticator: str
created: str
last_used: str
status: str
credentials = [
PasskeyCredential("user@example.com", "iPhone 15", "Face ID", "2025-01-01", "2025-01-20", "Active"),
PasskeyCredential("user@example.com", "MacBook Pro", "Touch ID", "2025-01-02", "2025-01-20", "Active"),
PasskeyCredential("user@example.com", "Windows PC", "Windows Hello", "2025-01-05", "2025-01-18", "Active"),
PasskeyCredential("admin@example.com", "YubiKey 5", "Security Key", "2025-01-01", "2025-01-20", "Active"),
PasskeyCredential("admin@example.com", "Pixel 8", "Fingerprint", "2025-01-03", "2025-01-19", "Active"),
]
print("=== Registered Passkeys ===")
for c in credentials:
print(f" [{c.status}] {c.user}")
print(f" Device: {c.device} | Auth: {c.authenticator}")
print(f" Created: {c.created} | Last used: {c.last_used}")
SaaS Architecture
# === Passkeys SaaS Architecture ===
# Authentication Flow:
# 1. User clicks "Sign in with Passkey"
# 2. Server generates challenge
# 3. Browser calls navigator.credentials.get()
# 4. User verifies with biometric
# 5. Browser sends signed challenge to server
# 6. Server verifies signature with stored public key
# 7. Server issues session token (JWT)
# Database Schema
# CREATE TABLE users (
# id UUID PRIMARY KEY,
# email VARCHAR(255) UNIQUE NOT NULL,
# name VARCHAR(255),
# created_at TIMESTAMP DEFAULT NOW()
# );
#
# CREATE TABLE passkey_credentials (
# id UUID PRIMARY KEY,
# user_id UUID REFERENCES users(id),
# credential_id BYTEA UNIQUE NOT NULL,
# public_key BYTEA NOT NULL,
# counter INTEGER DEFAULT 0,
# device_name VARCHAR(255),
# authenticator_type VARCHAR(50),
# created_at TIMESTAMP DEFAULT NOW(),
# last_used_at TIMESTAMP
# );
#
# CREATE INDEX idx_credential_id ON passkey_credentials(credential_id);
# Server — Authentication (Node.js)
# import {
# generateAuthenticationOptions,
# verifyAuthenticationResponse,
# } from '@simplewebauthn/server';
#
# app.post('/api/auth/login/options', async (req, res) => {
# const options = await generateAuthenticationOptions({
# rpID,
# userVerification: 'preferred',
# });
# req.session.challenge = options.challenge;
# res.json(options);
# });
#
# app.post('/api/auth/login/verify', async (req, res) => {
# const credential = await findCredential(req.body.id);
# const verification = await verifyAuthenticationResponse({
# response: req.body,
# expectedChallenge: req.session.challenge,
# expectedOrigin: origin,
# expectedRPID: rpID,
# authenticator: credential,
# });
# if (verification.verified) {
# await updateCounter(credential.id, verification.authenticationInfo.newCounter);
# const token = generateJWT(credential.userId);
# res.json({ token });
# }
# });
@dataclass
class AuthMetric:
metric: str
passkey: str
password: str
improvement: str
auth_metrics = [
AuthMetric("Login Time", "2.5s", "12s", "4.8x faster"),
AuthMetric("Success Rate", "99.2%", "85%", "+14.2%"),
AuthMetric("Phishing Attacks", "0", "150/month", "100% eliminated"),
AuthMetric("Support Tickets (password)", "0", "200/month", "100% eliminated"),
AuthMetric("Account Takeover", "0", "5/month", "100% eliminated"),
AuthMetric("User Satisfaction", "4.8/5", "3.2/5", "+50%"),
]
print("\n=== Passkeys vs Passwords ===")
for m in auth_metrics:
print(f" [{m.metric}]")
print(f" Passkey: {m.passkey} | Password: {m.password}")
print(f" Improvement: {m.improvement}")
Migration Strategy
# === Password to Passkey Migration ===
# Phase 1: Add Passkey Option (Month 1-2)
# - Add "Create Passkey" in account settings
# - Show passkey prompt after password login
# - Track adoption rate
#
# Phase 2: Encourage Passkeys (Month 3-4)
# - Show passkey prompt on every login
# - Offer incentive (premium feature trial)
# - Email campaign about passkey benefits
#
# Phase 3: Passkey-preferred (Month 5-6)
# - Default to passkey login
# - Password as fallback
# - Nudge remaining users
#
# Phase 4: Password-optional (Month 7+)
# - Allow users to remove password
# - Keep password as recovery option
# - Monitor adoption metrics
migration_metrics = {
"Total Users": "50,000",
"Passkey Enrolled": "32,000 (64%)",
"Passkey-only Users": "18,000 (36%)",
"Password-only Users": "18,000 (36%)",
"Avg Passkeys per User": "2.3",
"Monthly Passkey Logins": "180,000",
"Monthly Password Logins": "45,000",
"Password Reset Requests": "Down 85%",
"Support Tickets": "Down 70%",
}
print("Migration Progress:")
for k, v in migration_metrics.items():
print(f" {k}: {v}")
# Fallback Options
fallbacks = [
"Magic Link: ส่ง Email one-time login link",
"OTP: ส่ง SMS/Email 6-digit code",
"Recovery Code: Pre-generated one-time codes",
"Password: Keep as last resort",
"Admin Override: Manual identity verification",
]
print(f"\n\nFallback Authentication:")
for i, f in enumerate(fallbacks, 1):
print(f" {i}. {f}")
เคล็ดลับ
- Gradual: ค่อยๆ Migrate ไม่บังคับทันที
- Multi-device: ให้ User สร้าง Passkey หลายอุปกรณ์
- Fallback: มี Magic Link หรือ OTP สำรอง
- UX: ทำ UI สร้าง Passkey ง่ายที่สุด 1-2 คลิก
- Monitor: ติดตาม Adoption Rate ทุกสัปดาห์
Passkeys คืออะไร
ยืนยันตัวตนไม่ใช้รหัสผ่าน Public Key Cryptography Biometric ลายนิ้วมือ Face ID PIN ปลอดภัย ไม่ Phishing Cross-device iCloud Google
WebAuthn คืออะไร
W3C Standard API Browser Authenticator Fingerprint Face ID Security Key FIDO2 Chrome Safari Firefox Edge navigator.credentials
Passkeys ปลอดภัยกว่ารหัสผ่านอย่างไร
ไม่มี Password ขโมย Phishing Domain-bound Brute Force Credential Stuffing Private Key อุปกรณ์ Biometric Challenge Replay Attack
Implement Passkeys ใน SaaS อย่างไร
SimpleWebAuthn Node.js py_webauthn Python Registration Authentication Credential Database Multiple Passkeys Fallback Magic Link OTP
สรุป
Passkeys WebAuthn FIDO2 Passwordless Biometric SaaS Architecture Public Key Phishing-resistant SimpleWebAuthn Migration Cross-device Security Production
