Private Docs

API Restriction · 01 — role ที่ผูกกับ app ที่เรียกจริง

FE ส่ง AppId มาใน header, backend เอาไปหาใน blob ว่า user มี role อะไรใน app นั้น แล้วใส่แค่นั้น หาไม่เจอคือ 401

อัปเดต: 2026-09-08

⬅️ 00 — ที่มาและสภาพปัจจุบัน · ทำต่อที่ 02 — spec

01. role ที่ผูกกับ app ที่เรียกจริง

แผนที่จะทำ ยังไม่มีโค้ด · ตรวจกับ Backend_Package@10.30.0 แล้ว

กติกา — มีข้อเดียว

sequenceDiagram
    autonumber
    participant FE as หน้าจอ
    participant MW as middleware กลาง
    participant R as Redis
    participant EP as endpoint

    FE->>MW: request + header AppId
    MW->>R: ดึง user info
    R-->>MW: รายการ app ที่ user มี พร้อม role ของแต่ละ app
    MW->>MW: หา AppId ที่ส่งมา ในรายการนั้น
    alt เจอ และมี role
        MW->>EP: ใส่ role ของ app นั้น ทุกตัว
        EP-->>FE: ทำงานตามปกติ
    else ไม่เจอ หรือไม่มี role หรือไม่ได้ส่ง header
        MW-->>FE: 401
    end

ทำไม 401 ไม่ใช่ปล่อยผ่านแบบไม่มีสิทธิ์ — เป็นไปไม่ได้ที่ user จะเรียกในนาม app ที่ตัวเองไม่มี role อยู่เลย สถานะนี้ไม่ใช่ “ไม่มีสิทธิ์ทำ operation นี้” แต่คือ “คำขอนี้ไม่สมเหตุสมผลตั้งแต่ต้น” · ไม่ส่ง header มาเลยก็เข้ากติกาเดียวกัน ไม่รู้ว่า app ไหน ก็ไม่มี role ให้ใส่

ทำไมปลอดภัย ทั้งที่ header เชื่อไม่ได้

APIM ไม่ลบ header ของ client เลยสักตัว — sentinel-facade-inbound-v6.xml มี exists-action="delete" จุดเดียวคือ Authorization ที่ :274 ⇒ ใครจะยัด GUID อะไรมาก็ได้ รวมถึงกรณีโดน XSS

ตัวที่ตัดสินคือข้อมูลสิทธิ์ฝั่ง server ไม่ใช่ header แต่ข้อความว่า "GUID ปลอมแย่ที่สุดคือ 401" ไม่ถูกต้อง: user ที่เป็น Platform admin และ FX user สามารถส่ง AppId ของ Platform แล้วผ่าน endpoint ด้วย Platform grants ได้ แม้ request มาจาก FX UI จึงต้องเรียกสิ่งนี้ว่า app context selector ไม่ใช่ app attestation; การไม่เพิ่ม grant ใหม่ให้ user ไม่เท่ากับการแยก trust ระหว่าง app

🔴 นี่ไม่ใช่ app attestation — อย่าอ่านเป็นการพิสูจน์ที่มาของ request header แค่ narrows สิทธิ์ที่ user มีอยู่แล้วลงเหลือของ app เดียว ไม่ได้ตรวจว่า client ที่ยิง request จริง ๆ เป็น Platform UI หรือ FX UI ⇒ user ที่ถือ role ถูกต้องในหลาย app พร้อมกัน (เช่น เป็น admin ทั้ง Platform และ FX) เลือกได้ว่าจะเรียกในนามไหนต่อ request และเซิร์ฟเวอร์ยอมให้ทั้งคู่ — ถ้าธุรกิจต้องการห้าม session เดียวสลับ context ข้าม app ต้องผูก AppId กับ client/session ที่ auth ไว้ตั้งแต่ login ไม่ใช่ header ต่อ request ซึ่งเอกสารชุดนี้ไม่ได้แก้ปัญหานั้น จงใจ

ใส่ role ทุกตัวของ app นั้น ไม่ใช่ตัวแรก

UserAppInfo.Roles (UserInfoForRedis.cs:183-184) เป็น list และ schema ไม่ได้บังคับให้มี role เดียว (UserRoleConfiguration.cs:51-53) · เขียน foreach ไม่แพงกว่า Roles[0] และเลี่ยงบั๊กแบบที่ GetUserShellPermissionsHandler.cs:30-31 มีอยู่ตอนนี้ คือตัวที่สองหายเงียบ

ขนาดงาน

ข้อมูลอยู่ใน blob ครบแล้ว (Apps มี appId + roles · AllRoles เป็น [JsonIgnore] computed ไม่ได้อยู่ใน blob จริง) ⇒ แก้ที่ middleware ล้วน ๆ ไม่ต้องแตะ UsersMapper ไม่ต้องแตะ schema

คง CurrentSchemaVersion = 1 สำหรับ additive fields นี้: เมื่อ schema mismatch โค้ดเดิมที่ RedisUserInfoMiddleware.cs:275-282 หยุด enrichment และไม่ fallback แต่ JWT identity เดิมยังอยู่ จึงไม่ใช่ "ไม่มี identity" และไม่รับประกัน 401 หรือ role denial; schemaVersion เป็นรูปแบบข้อมูล ไม่ใช่ revision ของสิทธิ์สำหรับตรวจ revoke

config ที่ต้องมี

keyใช้ทำอะไร
ชื่อ headerไม่ hardcode ในโค้ด
สวิตช์เปิด/ปิดdefault = ปิด เพื่อให้ service ที่ bump version ตามน้ำไม่เปลี่ยนพฤติกรรม

ทั้งสอง key ต้องเติมใน Backend_Iac ครบทุก env ที่ service นั้น deploy — แก้ใน service repo อย่างเดียวไม่มีผลบน cluster

ที่งานนี้ยังไม่ตอบ

endpoint ยังไม่รู้ว่า app ไหนเรียก — role claim หลังใส่แล้วเป็น string เปล่า ๆ ไม่ได้พก AppId ไปด้วย ⇒ การแยกด้วย AppId จริง ๆ เกิดที่ 02 ซึ่งเทียบ เจ้าของสิทธิ์ กับ service เจ้าของ endpoint

company scope หายตั้งแต่ต้นทางUsersMapper.cs:242-257 ทำ GroupBy(AppId) โดยไม่เก็บ UserRole.CompanyId ทั้งที่ unique index มี ⇒ พอพูดเรื่อง “เลือก app” จะโดนถามต่อว่า “แล้ว company ล่ะ” ซึ่งเอกสารชุดนี้ยังไม่ตอบ — กลายเป็น rollout blocker จริงสำหรับ endpoint ที่ scope เป็นบริษัท ดู 03 ข้อ 5

พฤติกรรมปัจจุบัน: InvokeAsync เรียก _next(context) เสมอที่ RedisUserInfoMiddleware.cs:84-91 และเก็บ identity เดิมไว้ที่ :353 ดังนั้นแม้ role-based endpoint ก็ห้ามสรุปว่า deny เมื่อ enrichment ล้มเหลว เพราะ identity อื่นอาจมี role claim ที่ผ่าน policy ได้อยู่แล้ว

ข้อกำหนดเมื่อเปิด app-scoped enforcement: successful app resolution และข้อมูลสิทธิ์ที่ผ่าน freshness check ต้องเป็น hard prerequisite ก่อนเข้า endpoint ทั้ง LogOnly และ Enforce; ไม่มี OID/user info, schema ไม่รองรับ, JSON เสีย, decrypt ล้มเหลว หรือ Redis และ HTTP fallback ใช้ไม่ได้ ต้องไม่เรียก endpoint ต่อและห้ามใช้ JWT roles/AllRoles แทน app context ที่ตรวจไม่สำเร็จ Middleware ต้องตรวจ header หนึ่งค่าเท่านั้น, parse GUID, ตรวจ membership/role ที่ยังใช้ได้ แล้วเก็บ context ที่เลือกไว้ใน HttpContext

ต้องกำหนด endpoint coverage ก่อนเปิดสวิตช์: login/bootstrap, health, anonymous และ machine-to-machine calls ไม่ใช้ข้อสมมติ browser app context โดยอัตโนมัติ ข้อยกเว้นต้องประกาศจาก server-side endpoint metadata พร้อม policy ของตนเอง ห้ามให้ caller ส่ง header เพื่อข้ามการตรวจหรือยกเว้นทั้ง controller ที่มี protected actions ปนอยู่

HTTP contract ที่ต้องยืนยันกับ FE: 401 ใช้เมื่อ authentication ไม่ผ่าน; header ขาด/ซ้ำ/ไม่ใช่ GUID ใช้ 400; authenticated caller ไม่มี app membership/permission ใช้ 403; dependency ล้มเหลวจนตรวจ authorization ไม่ได้ใช้ 503 โดยไม่เข้า endpoint ข้อเสนอนี้แก้กติกา "401 ทุกกรณี" ใน diagram เดิม ต้องมี error code คงที่และทดสอบว่า FE ไม่ refresh token วนบน 400/403/503 ก่อนเปลี่ยน contract

ลำดับ deploy และเรื่องที่ต้องเคาะ → 03