Private Docs

IMPL PLAN 03 — Client → API Edge (APIM Hardening)

เปิด control ที่ APIM ให้ครบ 4 ตัว: subscription key, rate limit, JWT pre-validation, payload size

อัปเดต: 2026-07-07

IMPL PLAN 03 — Client → API Edge (APIM Hardening)

วันที่: 07/07/2026 · สถานะ: ⏳ รอ verify · Priority: 🟠 Phase 2 อ้างอิง: DISCUSSION หมวด 3 · SG-20 จาก COMPLIANCE

สรุป

เปิด control ที่ APIM ให้ครบ 4 ตัว: subscription key enforcement · rate limit (ตอนนี้ comment out อยู่ — SG-20) · JWT pre-validation · payload size — ทั้งหมดเป็น policy XML ฝั่ง APIM ไม่แตะโค้ด backend


Sequence Diagram — Request ผ่านขอบที่ harden แล้ว

sequenceDiagram
    autonumber
    participant SPA as Client (Angular/Mobile)
    participant CF as Cloudflare WAF
    participant GW as App Gateway (WAF)
    participant APIM as APIM
    participant SG as Sentinel
    participant BE as Backend

    SPA->>CF: HTTPS request + Cookie + Ocp-Apim-Subscription-Key
    CF->>GW: ผ่าน WAF rules (OWASP CRS)
    GW->>APIM: forward

    rect rgb(230, 240, 255)
    Note over APIM: ลำดับ policy ใหม่ (inbound)
    APIM->>APIM: ① validate subscription key → ไม่มี/ผิด = 401 ทันที
    APIM->>APIM: ② check request size ≤ limit → เกิน = 413
    APIM->>APIM: ③ rate-limit-by-key (per subscription/session) → เกิน = 429
    APIM->>SG: ④ resolve-session (cookie → JWT) — flow เดิม
    SG-->>APIM: JWT
    APIM->>APIM: ⑤ validate-jwt (issuer/audience/expiry/signature ผ่าน OpenID config)<br/>→ malformed/expired = 401 ที่ขอบ ไม่ทะลุถึง backend
    end

    APIM->>BE: forward + JWT (strip Authorization ขาออกตามเดิม)
    BE->>BE: validate JWT ซ้ำ (defense-in-depth — ไม่ยกเลิก)
    BE-->>SPA: response

ขั้นตอน Implement

APIM policy อยู่ 2 ที่: git-tracked XML ใน Backend_Iac (ถ้ามี) + ตัว resource บน Azure Portal — ทุกการแก้ต้อง commit XML เข้า git ด้วย ไม่แก้ portal อย่างเดียว

3.1 Subscription Key Enforcement

#จุดAction
1APIM Productsสร้าง product ต่อ consumer group: superapp-web, superapp-mobile, partner-{name} — แต่ละตัว require subscription
2APIM APIsตั้ง subscriptionRequired: true ทุก API (ตอนนี้บาง API เปิด open)
3Frontendgateway-credentials.interceptor.ts (มีอยู่แล้วใน apps/shell/src/app/interceptors/) — เพิ่มแนบ Ocp-Apim-Subscription-Key จาก environment config
4Key storage ฝั่ง FEsubscription key เป็น app identity ไม่ใช่ user secret (rate-limit/analytics per app) — ฝังใน env config ได้ แต่ต้องเข้าใจว่าใครเปิด devtools ก็เห็น → ห้ามใช้แทน authentication

คำตอบเรื่อง key จากไหน (FE): subscription key ≠ ความลับระดับ credential — มันคือตัวระบุ app สำหรับ rate limit/quota/analytics · การป้องกันจริงยังคงเป็น JWT (user) + WAF · ถ้าต้องการผูกแน่นกว่านั้นสำหรับ partner ใช้ client certificate (mTLS ที่ APIM รองรับ)

3.2 เปิด Rate Limit กลับ (SG-20)

<!-- inbound policy — ตัวเลขเป็น starting point ปรับตาม load test -->
<choose>
    <when condition="@(context.Request.Headers.ContainsKey("Authorization"))">
        <rate-limit-by-key calls="200" renewal-period="60"
            counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization","anon").GetHashCode().ToString())" />
    </when>
    <otherwise>
        <rate-limit-by-key calls="30" renewal-period="60"
            counter-key="@(context.Request.IpAddress)" />
    </otherwise>
</choose>
  • uncomment/เพิ่ม policy ที่ global scope + ตรวจว่าครอบทุก route (per-API policy อาจ override — audit ทีละ API)
  • ตัวเลข 200/30 ต่อ 60s = design intent เดิม — ยืนยันกับ load จริงก่อน production

3.3 JWT Pre-validation ที่ขอบ

<validate-jwt header-name="Authorization" failed-validation-httpcode="401"
              failed-validation-error-message="Unauthorized">
    <openid-config url="https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" />
    <audiences><audience>{api-audience}</audience></audiences>
    <issuers><issuer>{issuer-ciam}</issuer><issuer>{issuer-entra}</issuer></issuers>
</validate-jwt>
  • ใส่หลัง session façade (ตำแหน่ง ⑤ ใน diagram — validate JWT ที่ façade ออกมา)
  • multi-issuer ต้องครบทั้ง CIAM + Entra ID (ตาม backend ปัจจุบัน)
  • ⚠️ backend ยังต้อง validate เอง — APIM validation คือชั้นกรอง ไม่ใช่ชั้นแทน (Zero Trust B4)

3.4 Payload Size ที่ขอบ

<check-header name="Content-Length" failed-check-httpcode="413" ... />
<!-- หรือใช้ policy expression ตรวจ context.Request.Body ขนาด > 10MB → return-response 413 -->

ตั้งให้สอดคล้อง RequestSizeLimitMiddleware ฝั่ง backend (ขอบ ≥ backend เสมอ เพื่อไม่ให้ backend เจอ request ที่ขอบปล่อยแต่ตัวเองรับไม่ได้)

3.5 Verify WAF จริง (จาก ◇ HLD-asserted → ✅ verified)

  • export Cloudflare WAF rules + App GW WAF policy → เก็บ snapshot ใน Backend_Iac/docs/ เป็นหลักฐานต่อผู้ตรวจ
  • ยืนยัน OWASP CRS version + mode (Prevention ไม่ใช่ Detection)

Verify

  • เรียก API โดยไม่มี subscription key → 401 ที่ APIM (ไม่ถึง backend — ดูจาก App Insights dependency)
  • ยิงเกิน rate → 429 + header Retry-After
  • ส่ง JWT expired → 401 จาก APIM (backend log ไม่มี request เข้า)
  • POST 20MB → 413 ที่ขอบ
  • Regression: flow ปกติ login→ใช้งาน ไม่กระทบ (E2E เขียว)

⚠️ ข้อควรระวัง

  1. เปิด subscription enforcement ก่อนแจก key ให้ทุก client = client เดิมพังหมด — ลำดับ: สร้าง product/key → แจกทุก app (web/mobile/partner) → monitor ว่าทุก traffic มี key → ค่อยบังคับ
  2. rate limit ที่ APIM เป็น per-gateway-instance ใน consumption tier — Premium (ที่ใช้อยู่) แชร์ counter ได้ แต่ตรวจ config counter-key ให้ per-client จริง ไม่ใช่ global (ซ้ำบทเรียน self-DoS ของ Consent)
  3. validate-jwt เพิ่ม latency ~ms + ต้อง reach OpenID config endpoint — ตั้ง cache (default มี) + fail behavior ชัดเจน
  4. อย่าลืม SIT/UAT — policy ต้องเหมือน prod (ต่างแค่ค่า) มิฉะนั้นเทสไม่เจอปัญหาก่อนขึ้นจริง

🎯 ถ้าเป็นผมจะทำอย่างไร (ความเห็น Fable)

  1. ลำดับ: rate limit (3.2) ก่อน subscription key (3.1) — rate limit แค่ uncomment policy ที่มีอยู่ ไม่ต้อง coordinate กับ client ใดๆ · subscription key ต้องสร้าง product/แจก key/monitor ก่อน enforce = งานหลายสัปดาห์
  2. D7 = per frontend app + per partner (superapp-web, superapp-mobile, partner-{name}) — per-tenant ละเอียดเกินไปสำหรับเป้าหมาย (identity ของ tenant อยู่ใน JWT อยู่แล้ว)
  3. enforce แบบ 2 จังหวะ: จังหวะแรก policy แค่ log request ที่ไม่มี key (observe mode) 1-2 สัปดาห์ → ดู traffic จริงว่าครบทุก client → ค่อย 401 — กัน client ที่ลืมนึกไม่ถึง (LIFF, WebView, partner เก่า)
  4. validate-jwt ที่ขอบทำท้ายสุด — มี fail-mode ซับซ้อนสุด (OpenID config unreachable, multi-issuer) และ backend validate อยู่แล้ว — คุณค่า marginal ต่ำกว่า 3 ตัวแรก
  5. 3.5 (verify WAF) ทำคู่ขนานได้เลย — เป็นงาน read-only ไม่เสี่ยง แต่ได้หลักฐานตอบผู้ตรวจทันที — ถ้าให้เลือกงานแรกของหมวดนี้ผมเลือกตัวนี้
  6. ทุก policy XML ต้องเข้า git ก่อนแตะ portal — ถ้าตอนนี้ policy อยู่แค่บน portal ให้ export เข้า Backend_Iac เป็น step 0 — ไม่งั้นการ “uncomment” จะไม่มี history ว่าใครเปิด/ปิดอะไรเมื่อไหร่

📚 อ้างอิงมาตรฐาน (สำหรับใช้ในที่ประชุม):

หลักการแหล่งอ้างอิง
Subscription key = app identity ไม่ใช่ authentication (official position)Microsoft Learn — Subscriptions in Azure API Management
validate-jwt policy referenceMicrosoft Learn — Validate JWT policy
rate-limit-by-key policy referenceMicrosoft Learn — rate-limit-by-key policy
เหตุผลที่ต้องมี rate limit / payload limit ที่ขอบOWASP API4
— Unrestricted Resource Consumption
Defense-in-depth: ขอบ validate แล้ว backend ต้อง validate ซ้ำ (verify explicitly)Microsoft — Zero Trust security model