SiamCafe.net Blog
Cybersecurity

Passkeys WebAuthn SaaS Architecture

passkeys webauthn saas architecture
Passkeys WebAuthn SaaS Architecture | SiamCafe Blog
2026-05-09· อ. บอม — SiamCafe.net· 9,248 คำ

Passkeys WebAuthn

Passkeys WebAuthn FIDO2 Passwordless Authentication Biometric Fingerprint Face ID Public Key Cryptography SaaS Phishing-resistant Cross-device iCloud Google Password Manager

Auth MethodPhishing-safeUXSetupเหมาะกับ
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}")

เคล็ดลับ

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

📖 บทความที่เกี่ยวข้อง

Passkeys WebAuthn Open Source Contributionอ่านบทความ → Passkeys WebAuthn Platform Engineeringอ่านบทความ → Passkeys WebAuthn CDN Configurationอ่านบทความ → Passkeys WebAuthn Cost Optimization ลดค่าใช้จ่ายอ่านบทความ → Passkeys WebAuthn CI CD Automation Pipelineอ่านบทความ →

📚 ดูบทความทั้งหมด →