SiamCafe.net Blog
Cybersecurity

Passkeys WebAuthn Hexagonal Architecture

passkeys webauthn hexagonal architecture
Passkeys WebAuthn Hexagonal Architecture | SiamCafe Blog
2025-09-27· อ. บอม — SiamCafe.net· 9,900 คำ

Passkeys WebAuthn

Passkeys WebAuthn FIDO2 Passwordless Biometric Public Key Cryptography Hexagonal Architecture Ports Adapters Domain-driven Design Authentication Security

MethodSecurityUXPhishingImplementation
Passkeys/WebAuthnสูงมากดีมากป้องกันปานกลาง
Password + 2FAดีปานกลางเสี่ยงง่าย
Magic Linkดีดีเสี่ยงง่าย
OAuth/SSOดีดีมากเสี่ยงปานกลาง

WebAuthn Implementation

# === WebAuthn Registration & Login ===

# JavaScript — Registration (Client)
# async function register() {
#   const options = await fetch('/api/webauthn/register/options', {
#     method: 'POST',
#     headers: { 'Content-Type': 'application/json' },
#     body: JSON.stringify({ username: 'alice' }),
#   }).then(r => r.json());
#
#   // Browser calls Authenticator (Touch ID, Face ID, etc.)
#   const credential = await navigator.credentials.create({
#     publicKey: {
#       challenge: base64ToBuffer(options.challenge),
#       rp: { name: "My App", id: "example.com" },
#       user: {
#         id: base64ToBuffer(options.userId),
#         name: "alice",
#         displayName: "Alice",
#       },
#       pubKeyCredParams: [
#         { alg: -7, type: "public-key" },   // ES256
#         { alg: -257, type: "public-key" },  // RS256
#       ],
#       authenticatorSelection: {
#         authenticatorAttachment: "platform",
#         residentKey: "required",
#         userVerification: "required",
#       },
#     },
#   });
#
#   // Send credential to server
#   await fetch('/api/webauthn/register/verify', {
#     method: 'POST',
#     body: JSON.stringify(serializeCredential(credential)),
#   });
# }

# JavaScript — Login (Client)
# async function login() {
#   const options = await fetch('/api/webauthn/login/options')
#     .then(r => r.json());
#
#   const assertion = await navigator.credentials.get({
#     publicKey: {
#       challenge: base64ToBuffer(options.challenge),
#       rpId: "example.com",
#       userVerification: "required",
#     },
#   });
#
#   const result = await fetch('/api/webauthn/login/verify', {
#     method: 'POST',
#     body: JSON.stringify(serializeAssertion(assertion)),
#   }).then(r => r.json());
#
#   console.log('Logged in:', result.user);
# }

from dataclasses import dataclass
from typing import List

@dataclass
class PasskeyCredential:
    user: str
    credential_id: str
    device: str
    authenticator: str
    created: str
    last_used: str

credentials = [
    PasskeyCredential("alice", "cred_001", "iPhone 15", "Face ID", "2024-01-15", "2024-02-20"),
    PasskeyCredential("alice", "cred_002", "MacBook Pro", "Touch ID", "2024-01-15", "2024-02-19"),
    PasskeyCredential("bob", "cred_003", "Pixel 8", "Fingerprint", "2024-02-01", "2024-02-20"),
    PasskeyCredential("bob", "cred_004", "Windows PC", "Windows Hello", "2024-02-01", "2024-02-18"),
]

print("=== Registered Passkeys ===")
for c in credentials:
    print(f"  [{c.user}] {c.device} ({c.authenticator})")
    print(f"    ID: {c.credential_id} | Created: {c.created} | Last: {c.last_used}")

Hexagonal Architecture

# === Hexagonal Architecture for Auth ===

# Architecture:
# ┌─────────────────────────────────────┐
# │         Adapters (Inbound)          │
# │  REST API │ GraphQL │ gRPC          │
# ├───────────┴─────────┴───────────────┤
# │           Ports (Inbound)           │
# │  AuthService │ UserService          │
# ├─────────────────────────────────────┤
# │         Domain (Core)               │
# │  User │ Credential │ Challenge      │
# │  AuthPolicy │ WebAuthnVerifier      │
# ├─────────────────────────────────────┤
# │           Ports (Outbound)          │
# │  UserRepo │ CredentialRepo │ Cache  │
# ├─────────────────────────────────────┤
# │         Adapters (Outbound)         │
# │  PostgreSQL │ Redis │ S3            │
# └─────────────────────────────────────┘

# Python — Domain Layer
# @dataclass
# class User:
#     id: str
#     username: str
#     display_name: str
#     credentials: List[WebAuthnCredential]
#
# @dataclass
# class WebAuthnCredential:
#     credential_id: bytes
#     public_key: bytes
#     sign_count: int
#     device_name: str
#
# class AuthService:  # Port (Inbound)
#     def __init__(self, user_repo, credential_repo, challenge_cache):
#         self.user_repo = user_repo          # Port (Outbound)
#         self.credential_repo = credential_repo
#         self.challenge_cache = challenge_cache
#
#     def generate_registration_options(self, username):
#         user = self.user_repo.find_by_username(username)
#         challenge = os.urandom(32)
#         self.challenge_cache.store(user.id, challenge, ttl=300)
#         return RegistrationOptions(challenge, user)
#
#     def verify_registration(self, user_id, credential_data):
#         challenge = self.challenge_cache.get(user_id)
#         credential = WebAuthnVerifier.verify(credential_data, challenge)
#         self.credential_repo.save(user_id, credential)

# Adapters
# class PostgresUserRepo:  # Adapter for UserRepo Port
#     def find_by_username(self, username):
#         row = db.execute("SELECT * FROM users WHERE username = ?", username)
#         return User(**row)
#
# class RedisChallengeCashe:  # Adapter for ChallengeCache Port
#     def store(self, user_id, challenge, ttl):
#         redis.setex(f"challenge:{user_id}", ttl, challenge)

layers = {
    "Domain": ["User", "Credential", "Challenge", "AuthPolicy", "WebAuthnVerifier"],
    "Inbound Ports": ["AuthService", "UserService", "SessionService"],
    "Outbound Ports": ["UserRepository", "CredentialRepository", "ChallengeCache"],
    "Inbound Adapters": ["REST Controller", "GraphQL Resolver", "gRPC Handler"],
    "Outbound Adapters": ["PostgreSQL Repo", "Redis Cache", "S3 Storage"],
}

print("\n=== Hexagonal Layers ===")
for layer, components in layers.items():
    print(f"  [{layer}]: {', '.join(components)}")

Production Setup

# === Production Passkeys ===

# Python Server — py_webauthn library
# pip install py-webauthn
#
# from webauthn import (
#     generate_registration_options,
#     verify_registration_response,
#     generate_authentication_options,
#     verify_authentication_response,
# )
#
# # Registration
# options = generate_registration_options(
#     rp_id="example.com",
#     rp_name="My App",
#     user_id=user.id.encode(),
#     user_name=user.username,
#     user_display_name=user.display_name,
# )
#
# # Verification
# verification = verify_registration_response(
#     credential=credential_data,
#     expected_challenge=stored_challenge,
#     expected_rp_id="example.com",
#     expected_origin="https://example.com",
# )

auth_metrics = {
    "Total Users": "15,000",
    "Passkey Enrolled": "8,500 (56.7%)",
    "Password Only": "6,500 (43.3%)",
    "Login Success Rate (Passkey)": "99.2%",
    "Login Success Rate (Password)": "87.5%",
    "Avg Login Time (Passkey)": "1.2 seconds",
    "Avg Login Time (Password)": "8.5 seconds",
    "Phishing Incidents (Passkey)": "0",
    "Phishing Incidents (Password)": "12/month",
    "Support Tickets (Password Reset)": "Down 75%",
}

print("Auth Dashboard:")
for k, v in auth_metrics.items():
    print(f"  {k}: {v}")

# Migration Strategy
migration = [
    "Phase 1: เพิ่ม Passkey เป็น Optional (Password ยังใช้ได้)",
    "Phase 2: แนะนำ Passkey เมื่อ Login ด้วย Password",
    "Phase 3: Passkey เป็น Default (Password เป็น Fallback)",
    "Phase 4: Password-only accounts ต้องเพิ่ม Passkey",
    "Phase 5: ปิด Password Login สำหรับ Account ที่มี Passkey",
]

print(f"\n\nMigration Strategy:")
for i, step in enumerate(migration, 1):
    print(f"  {step}")

เคล็ดลับ

Passkeys คืออะไร

Passwordless Public Key Biometric Face ID Touch ID FIDO2 WebAuthn ไม่ Phishing ไม่ Leak Apple Google Microsoft Sync

WebAuthn ทำงานอย่างไร

Browser credentials.create() Authenticator Key Pair Private Key อุปกรณ์ Public Key Server Challenge Sign Verify Standard

Hexagonal Architecture คืออะไร

Ports Adapters แยก Business Logic Infrastructure Domain ตรงกลาง Interface Implementation เปลี่ยน DB ไม่กระทบ Mock Test Clean

Passkeys ปลอดภัยกว่า Password อย่างไร

ไม่ Leak ไม่ Phishing Origin Bound Public Key Private Key อุปกรณ์ Biometric Challenge Replay ไม่ Brute Force ไม่จำ

สรุป

Passkeys WebAuthn FIDO2 Passwordless Biometric Hexagonal Architecture Ports Adapters Domain Public Key Cryptography Challenge Verify Registration Login Security

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

Passkeys WebAuthn Open Source Contributionอ่านบทความ → Passkeys WebAuthn Testing Strategy QAอ่านบทความ → Passkeys WebAuthn Post-mortem Analysisอ่านบทความ → Passkeys WebAuthn Stream Processingอ่านบทความ → BGP Routing Advanced Hexagonal Architectureอ่านบทความ →

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