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