it

Passkeys WebAuthn กับ CDN Configuration —

Passkeys WebAuthn กับ CDN Configuration —

Passkeys และ WebAuthn

Passkeys WebAuthn กับ CDN Configuration —

Passkeys เป็นวิธี Login แบบ Passwordless ที่ใช้ Biometrics หรือ Device PIN แทน Password ทำงานบน WebAuthn Standard ใช้ Public Key Cryptography ปลอดภัยจาก Phishing, Credential Stuffing และ Brute Force

เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ windows server 2022 คือ

CDN ช่วยเพิ่ม Performance สำหรับ Authentication Flow Cache Static Assets, Edge Functions สำหรับ Token Validation, WAF ป้องกัน Attacks, Rate Limiting ที่ Edge

เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ seo website คือ — ข้อมูลครบถ้วน 2026

WebAuthn Server Implementation

# webauthn_server.py — WebAuthn Server ด้วย Python
# pip install py_webauthn flask redis

from flask import Flask, request, jsonify, session
from webauthn import (
    generate_registration_options,
    verify_registration_response,
    generate_authentication_options,
    verify_authentication_response,
)
from webauthn.helpers.structs import (
    AuthenticatorSelectionCriteria,
    ResidentKeyRequirement,
    UserVerificationRequirement,
    PublicKeyCredentialDescriptor,
)
from webauthn.helpers.cose import COSEAlgorithmIdentifier
import json
import redis
import os

app = Flask(__name__)
app.secret_key = os.urandom(32)

# Redis สำหรับเก็บ Challenges และ Credentials
r = redis.Redis(host="localhost", port=6379, db=0)

RP_ID = "example.com"
RP_NAME = "Example App"
ORIGIN = "https://example.com"

# === Registration ===

@app.route("/api/register/options", methods=["POST"])
def register_options():
    """สร้าง Registration Options"""
    data = request.json
    user_id = data["user_id"]
    username = data["username"]

    # ดึง Existing Credentials
    existing = get_user_credentials(user_id)
    exclude = [
        PublicKeyCredentialDescriptor(id=cred["id"])
        for cred in existing
    ]

    options = generate_registration_options(
        rp_id=RP_ID,
        rp_name=RP_NAME,
        user_id=user_id.encode(),
        user_name=username,
        user_display_name=username,
        authenticator_selection=AuthenticatorSelectionCriteria(
            resident_key=ResidentKeyRequirement.REQUIRED,
            user_verification=UserVerificationRequirement.REQUIRED,
        ),
        supported_pub_key_algs=[
            COSEAlgorithmIdentifier.ECDSA_SHA_256,
            COSEAlgorithmIdentifier.RSASSA_PKCS1_v1_5_SHA_256,
        ],
        exclude_credentials=exclude,
        timeout=60000,
    )

    # เก็บ Challenge ใน Redis (expire 5 นาที)
    r.setex(f"challenge:{user_id}", 300, options.challenge)

    return jsonify(options)

@app.route("/api/register/verify", methods=["POST"])
def register_verify():
    """Verify Registration Response"""
    data = request.json
    user_id = data["user_id"]

    challenge = r.get(f"challenge:{user_id}")
    if not challenge:
        return jsonify({"error": "Challenge expired"}), 400

    try:
        verification = verify_registration_response(
            credential=data["credential"],
            expected_challenge=challenge,
            expected_rp_id=RP_ID,
            expected_origin=ORIGIN,
        )

        # เก็บ Credential
        save_credential(user_id, {
            "id": verification.credential_id,
            "public_key": verification.credential_public_key,
            "sign_count": verification.sign_count,
        })

        r.delete(f"challenge:{user_id}")
        return jsonify({"success": True})

    except Exception as e:
        return jsonify({"error": str(e)}), 400

# === Authentication ===

@app.route("/api/login/options", methods=["POST"])
def login_options():
    """สร้าง Authentication Options"""
    data = request.json
    user_id = data.get("user_id", "")

    credentials = get_user_credentials(user_id)
    allow = [
        PublicKeyCredentialDescriptor(id=cred["id"])
        for cred in credentials
    ]

    options = generate_authentication_options(
        rp_id=RP_ID,
        allow_credentials=allow if allow else None,
        user_verification=UserVerificationRequirement.REQUIRED,
        timeout=60000,
    )

    r.setex(f"auth_challenge:{user_id}", 300, options.challenge)
    return jsonify(options)

@app.route("/api/login/verify", methods=["POST"])
def login_verify():
    """Verify Authentication Response"""
    data = request.json
    user_id = data["user_id"]

    challenge = r.get(f"auth_challenge:{user_id}")
    credential = get_credential_by_id(user_id, data["credential"]["id"])

    try:
        verification = verify_authentication_response(
            credential=data["credential"],
            expected_challenge=challenge,
            expected_rp_id=RP_ID,
            expected_origin=ORIGIN,
            credential_public_key=credential["public_key"],
            credential_current_sign_count=credential["sign_count"],
        )

        # อัพเดท Sign Count
        update_sign_count(user_id, data["credential"]["id"],
                         verification.new_sign_count)

        session["user_id"] = user_id
        r.delete(f"auth_challenge:{user_id}")
        return jsonify({"success": True, "user_id": user_id})

    except Exception as e:
        return jsonify({"error": str(e)}), 401

def get_user_credentials(user_id):
    data = r.get(f"creds:{user_id}")
    return json.loads(data) if data else []

def save_credential(user_id, cred):
    creds = get_user_credentials(user_id)
    creds.append(cred)
    r.set(f"creds:{user_id}", json.dumps(creds))

def get_credential_by_id(user_id, cred_id):
    creds = get_user_credentials(user_id)
    return next((c for c in creds if c["id"] == cred_id), None)

def update_sign_count(user_id, cred_id, new_count):
    creds = get_user_credentials(user_id)
    for c in creds:
        if c["id"] == cred_id:
            c["sign_count"] = new_count
    r.set(f"creds:{user_id}", json.dumps(creds))

# if __name__ == "__main__":
#     app.run(host="0.0.0.0", port=5000, ssl_context="adhoc")

Frontend WebAuthn Client

Passkeys WebAuthn กับ CDN Configuration —
// webauthn_client.js — WebAuthn Frontend Client

class PasskeyAuth {
  constructor(apiBase = '/api') {
    this.apiBase = apiBase;
  }

  // === Registration ===
  async register(userId, username) {
    // 1. ขอ Options จาก Server
    const optionsRes = await fetch(`/register/options`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ user_id: userId, username }),
    });
    const options = await optionsRes.json();

    // 2. แปลง Base64 เป็น ArrayBuffer
    options.challenge = this.base64ToBuffer(options.challenge);
    options.user.id = this.base64ToBuffer(options.user.id);

    // 3. เรียก Browser WebAuthn API
    const credential = await navigator.credentials.create({
      publicKey: options,
    });

    // 4. ส่งผลลัพธ์กลับ Server
    const verifyRes = await fetch(`/register/verify`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        user_id: userId,
        credential: {
          id: credential.id,
          rawId: this.bufferToBase64(credential.rawId),
          response: {
            attestationObject: this.bufferToBase64(
              credential.response.attestationObject
            ),
            clientDataJSON: this.bufferToBase64(
              credential.response.clientDataJSON
            ),
          },
          type: credential.type,
        },
      }),
    });

    return verifyRes.json();
  }

  // === Authentication ===
  async login(userId = '') {
    const optionsRes = await fetch(`/login/options`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ user_id: userId }),
    });
    const options = await optionsRes.json();

    options.challenge = this.base64ToBuffer(options.challenge);
    if (options.allowCredentials) {
      options.allowCredentials = options.allowCredentials.map(c => ({
        ...c, id: this.base64ToBuffer(c.id),
      }));
    }

    const assertion = await navigator.credentials.get({
      publicKey: options,
    });

    const verifyRes = await fetch(`/login/verify`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        user_id: userId,
        credential: {
          id: assertion.id,
          rawId: this.bufferToBase64(assertion.rawId),
          response: {
            authenticatorData: this.bufferToBase64(
              assertion.response.authenticatorData
            ),
            clientDataJSON: this.bufferToBase64(
              assertion.response.clientDataJSON
            ),
            signature: this.bufferToBase64(assertion.response.signature),
          },
          type: assertion.type,
        },
      }),
    });

    return verifyRes.json();
  }

  // === Helpers ===
  base64ToBuffer(base64) {
    const binary = atob(base64.replace(/-/g, '+').replace(/_/g, '/'));
    return Uint8Array.from(binary, c => c.charCodeAt(0)).buffer;
  }

  bufferToBase64(buffer) {
    return btoa(String.fromCharCode(...new Uint8Array(buffer)))
      .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
  }

  // === Check Support ===
  static isSupported() {
    return window.PublicKeyCredential !== undefined;
  }

  static async isPlatformSupported() {
    if (!this.isSupported()) return false;
    return PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
  }
}

// const auth = new PasskeyAuth();
// await auth.register('user123', 'john@example.com');
// await auth.login('user123');

CDN Configuration

# === CDN Configuration สำหรับ Authentication ===

# 1. Cloudflare Configuration
# cloudflare-workers/auth-edge.js
#
# export default {
#   async fetch(request, env) {
#     const url = new URL(request.url);
#
#     // Rate Limiting สำหรับ Auth Endpoints
#     if (url.pathname.startsWith('/api/login') ||
#         url.pathname.startsWith('/api/register')) {
#       const ip = request.headers.get('CF-Connecting-IP');
#       const key = `rate::`;
#       const count = await env.RATE_LIMIT.get(key);
#
#       if (count && parseInt(count) > 10) {
#         return new Response('Too Many Requests', { status: 429 });
#       }
#
#       await env.RATE_LIMIT.put(key, (parseInt(count || 0) + 1).toString(),
#         { expirationTtl: 60 });
#     }
#
#     // JWT Validation ที่ Edge
#     if (url.pathname.startsWith('/api/protected')) {
#       const token = request.headers.get('Authorization')?.split(' ')[1];
#       if (!token) {
#         return new Response('Unauthorized', { status: 401 });
#       }
#       // Validate JWT at edge
#     }
#
#     return fetch(request);
#   }
# };

# 2. Nginx CDN Cache Configuration
# /etc/nginx/conf.d/auth-cdn.conf

# # Cache Static Assets
# location ~* \.(js|css|png|jpg|svg|woff2)$ {
#     proxy_cache static_cache;
#     proxy_cache_valid 200 1d;
#     add_header X-Cache $upstream_cache_status;
#     add_header Cache-Control "public, max-age=86400";
# }
#
# # No Cache สำหรับ Auth Endpoints
# location /api/login {
#     proxy_pass http://auth-backend;
#     proxy_no_cache 1;
#     proxy_cache_bypass 1;
#     add_header Cache-Control "no-store";
#
#     # Rate Limiting
#     limit_req zone=auth_limit burst=10 nodelay;
# }
#
# location /api/register {
#     proxy_pass http://auth-backend;
#     proxy_no_cache 1;
#     add_header Cache-Control "no-store";
#     limit_req zone=auth_limit burst=5 nodelay;
# }
#
# # Security Headers
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# add_header X-Content-Type-Options "nosniff" always;
# add_header X-Frame-Options "DENY" always;
# add_header Content-Security-Policy "default-src 'self'" always;

# 3. Rate Limit Zone
# limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/s;

echo "CDN Configuration:"
echo "  Static Assets: Cache 1 day"
echo "  Auth Endpoints: No cache, Rate limited"
echo "  Edge: JWT validation, Rate limiting"
echo "  Security: HSTS, CSP, X-Frame-Options"

Best Practices

  • Resident Keys: ใช้ Resident Keys (Discoverable Credentials) สำหรับ Usernameless Login
  • User Verification: ตั้ง User Verification เป็น Required เสมอ
  • Challenge Expiry: ตั้ง Challenge ให้ Expire ใน 5 นาที เก็บใน Redis
  • Sign Count: ตรวจสอบ Sign Count ป้องกัน Cloned Authenticators
  • CDN No-cache Auth: ไม่ Cache Auth Endpoints ที่ CDN
  • Rate Limiting: ตั้ง Rate Limit สำหรับ Login/Register ที่ CDN Edge

Passkeys คืออะไร

วิธี Login Passwordless ใช้ Biometrics ลายนิ้วมือ Face ID Device PIN แทน Password ทำงานบน WebAuthn ปลอดภัยกว่า Password ไม่ Phishing ไม่ Leak Cross-device iCloud Google Password Manager

แนะนำเพิ่มเติม — อ่านเพิ่มเติมที่ SiamCafeBook

เนื้อหาเกี่ยวข้อง — ดูเพิ่มเติมเรื่อง Zipkin Tracing IoT Gateway

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง