Private Docs

เข้ารหัสรายฟิลด์ หรือ เข้ารหัสทั้ง payload — เลือกทางไหน

เทียบ 2 ทางเลือกของชั้นเข้ารหัสระหว่างทาง พร้อมผลกระทบรายมิติ ความเห็นของ advisor รายมิติ และเคสที่ต้องคิดเผื่อ (service อื่นมาเรียก · partner ภายนอก · mobile · batch · debug production) พร้อมทางออกของแต่ละเคส — หน้าสำหรับระดมความคิดก่อนตัดสิน

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

หน้านี้ไว้ ระดมความคิดก่อนตัดสิน ไม่ใช่ข้อกำหนด — ข้อสรุปกับคำถามที่ยังเปิดอยู่ท้ายหน้า

1. กำลังเลือกอะไร

ข้อมูลอ่อนไหว (วันเกิด · สัญชาติ · ที่อยู่ · เบอร์โทร) วิ่งจากหน้าเว็บมาหลังบ้าน ระหว่างทางมีคนกลางที่ ถอด TLS ได้ อยู่หลายจุด (APIM · ingress · proxy ขององค์กร) เราจะเข้ารหัสทับอีกชั้นเพื่อไม่ให้จุดพวกนั้นอ่านออก — คำถามคือเข้ารหัสแค่ไหน

ทาง A — เข้ารหัสรายฟิลด์

JSON ยังเป็น JSON ปกติ แค่ บางค่า กลายเป็นก้อนเข้ารหัส

{
  "email": "somchai@example.com",
  "firstNameTH": "สมชาย",
  "lastNameTH": "ใจดี",
  "birthDate":      "eyJhbGciOiJSU0EtT0FFUC0yNTYi...StUvWx",
  "nationality":    "eyJhbGciOiJSU0EtT0FFUC0yNTYi...KpQrSt",
  "mailingAddress": "eyJhbGciOiJSU0EtT0FFUC0yNTYi...MnOpQr"
}

ทาง B — เข้ารหัสทั้งก้อน

body ทั้งอันกลายเป็นข้อความเข้ารหัสเส้นเดียว ไม่เหลือรูป JSON ให้เห็น

eyJhbGciOiJSU0EtT0FFUC0yNTYi....AbCdEf.GhIjKl.MnOpQr.StUvWx

ทั้ง 2 ทาง หลังบ้านถอดให้เรียบร้อยก่อนถึงโค้ด business ⇒ คนเขียน handler ไม่ต้องรู้เรื่อง และค่าที่บันทึกลงฐานข้อมูลเหมือนกันทุกประการ


2. เทียบรายมิติ + ผลกระทบ + ความเห็น advisor

= ทางที่ advisor เลือกในมิตินั้น

#มิติทาง A — รายฟิลด์ทาง B — ทั้งก้อนผลกระทบ — ใครเจ็บ เจ็บตอนไหนadvisor (FABLE) เลือก
1คนกลางเห็นอะไรเห็นโครงทั้งหมด: ชื่อ field, field ไหนมี/ไม่มี, ความยาวของค่าที่เข้ารหัส และ ค่าที่ไม่ได้ mark ทั้งหมด (อีเมล ชื่อ) · ซ่อนเฉพาะค่าที่ mark เห็นแค่ URL กับก้อนทึบA: ถ้าจำแนกข้อมูลถูก = พอ · แต่ถ้ามี field อ่อนไหวที่ลืม mark = รั่วโดยไม่มีใครรู้
B: ปิดหมดโดยไม่ต้องตัดสินใจอะไรเลย
ทาง B
ปิดสนิทโดยไม่ต้องพึ่งว่าจำแนกข้อมูลถูก — A ปลอดภัยเท่าที่ list ถูกเท่านั้น
2field ใหม่ที่เพิ่มทีหลังค่าตั้งต้นคือ ไม่เข้ารหัส — ต้องมีคนไป mark ทุกครั้ง ลืม = plaintext เงียบ ๆ ไม่มี error ครอบให้เองอัตโนมัติ ลืมไม่ได้A: ความปลอดภัยเสื่อมลงตามเวลาถ้าไม่มี process กำกับ · เจ็บตอน 6 เดือนถัดไป ไม่ใช่วันนี้
B: ไม่มีปัญหานี้เลย
ทาง B
โครงสร้างชนะวินัย — ความเสี่ยง “ลืม” ของ A โตขึ้นตามเวลา
3คนเรียกที่ยังไม่พร้อม ส่ง plaintext มาได้ ระบบรับทั้ง 2 แบบ ⇒ ของเดิมไม่พังต้องเข้ารหัสทั้งก้อนหรือไม่ก็ไม่ต้องเลย — ครึ่ง ๆ ไม่ได้A: เปิดใช้ได้โดยไม่ต้องรอใคร
B: ต้องนัดวันเปลี่ยนพร้อมกันทุกคนที่เรียก endpoint นั้น
ทาง A
รับทั้ง 2 แบบรายฟิลด์ คือทางเดียวที่ไม่หักของเดิม
4การทยอยเปลี่ยน ทีละ field ได้ — เปิดวันเกิดก่อน แล้วค่อยตามด้วยที่อยู่ทั้ง endpoint พร้อมกันA: ย้อนกลับง่าย เสี่ยงต่อรอบต่ำ
B: ทุกรอบคือ big bang
ทาง A
เปิดทีละ field ย้อนกลับทีละ field — B คือ big bang ทุกรอบ
5งานฝั่งคนเรียกต้องรู้ว่า field ไหนบ้าง ที่ต้องเข้ารหัส และต้องตามให้ทันเมื่อรายการเปลี่ยน เข้ารหัสทั้งก้อนจบ ไม่ต้องรู้ว่าข้างในมีอะไรA: รายการ field กลายเป็น contract ที่ต้อง sync กัน 2 ฝั่ง — จุดที่หลุดง่ายเวลามีทีมใหม่มาต่อ
B: ฝั่งคนเรียก ง่ายกว่า
ทาง B
ก้อนเดียวจบ ไม่ต้อง sync รายการ field ข้ามทีม
6ขนาดที่บวมขึ้นทุก field ที่เข้ารหัสพกกุญแจของตัวเองมาด้วย ⇒ บวม ~0.5 KB ต่อ field · 5 field ≈ +2.5 KB บวมครั้งเดียว ~0.5 KB ไม่ว่าข้างในจะมีกี่ fieldA: endpoint ที่ mark หลาย field payload โตขึ้นชัด · จะหนักกับมือถือ/เน็ตช้า
B: คงที่
ทาง B
overhead คงที่ — ของ A โตเป็นเส้นตรงตามจำนวน field
7debug ตอนเกิดเหตุจริง ยังเห็น field ที่ไม่อ่อนไหวใน log/trace ⇒ ตามรอยได้ว่า request ไหนของใครมืดสนิท — เห็นแค่ว่ามี request เข้ามาA: ทีม support ยังทำงานได้
B: 🔴 เกิด incident แล้วสืบไม่ได้เลย ต้องวางเครื่องมืออื่นทดแทนก่อนใช้จริง
ทาง A
ความมืดของ B คือค่า ops ที่ต้องจ่ายทุกวัน ไม่ใช่จ่ายครั้งเดียว
8สัญญาของ API (Swagger)
⚠️ Swagger สร้างจากการอ่านโครง DTO ตอนแอปสตาร์ท ไม่ได้วิ่งผ่าน middlewareทั้ง 2 ทางโชว์ schema ได้เหมือนกัน ประเด็นจริงคือ เอกสารโกหกแค่ไหนเทียบกับของที่ส่งจริง
โครงถูกทั้งหมด · โกหกเฉพาะ ชนิดของ field ที่ mark (เช่นโชว์ birthDate เป็นวันที่ แต่ของจริงส่งเป็นข้อความเข้ารหัส) · ปุ่ม Try it out ยังใช้ได้จริงเพราะระบบรับ plaintext ด้วยโชว์โครงข้างในได้ แต่ของที่ส่งจริงเป็นข้อความก้อนเดียว ⇒ ไม่ตรงกับความจริงทั้งอัน · ปุ่ม Try it out ส่ง JSON ธรรมดา ซึ่งจะผ่านหรือไม่ผ่านขึ้นกับว่าเปิดรับ plaintext อยู่ไหมA: คนมาต่อใหม่อ่านเองรู้เรื่องเกือบหมด เหลือ 3 field ที่ต้องกำกับ
B: ต้องเขียนทับคำอธิบาย body ทั้งอัน ไม่งั้นคนอ่านทำตามแล้วยิงไม่ผ่าน
ทั้งคู่: ต้องแต่ง Swagger เพิ่มถึงจะตรงความจริง 100% แต่ของ A แต่งน้อยกว่ามาก
ทาง A
เอกสารเพี้ยนน้อยกว่า (เฉพาะ 3 field ไม่ใช่ทั้ง body) และ Try it out ยังใช้งานได้ — ชนะห่างน้อยกว่าที่เคยเขียนไว้
9gateway ที่ต้องอ่าน body (validate · route · เก็บสถิติ) ทำได้กับ field ที่ไม่ได้เข้ารหัสทำไม่ได้เลยA: ของเดิมที่อ่าน body อยู่ยังรอด
B: ต้องไล่ดูว่ามี policy ไหนพึ่ง body อยู่บ้าง ก่อนเปิด
ทาง A
policy เดิมที่พึ่ง body ยังรอด
10ความเสียหายเมื่อถอดไม่ได้ปฏิเสธทั้ง request (ปลอดภัยไว้ก่อน)เหมือนกันเท่ากันเสมอ
ปลอดภัยไว้ก่อนเท่ากันทั้งคู่
11การสลับค่าข้ามคำขอแต่ละ field เป็นก้อนอิสระ ⇒ คนที่เซ็นคำขอได้ เอาก้อนที่ดักได้จากคำขออื่นมาแปะได้ ทั้งก้อนผูกกันอยู่แล้ว สลับเฉพาะบางค่าไม่ได้A: ต้องพึ่งชั้นลายเซ็นเป็นตัวกัน · ถ้าวันหน้าเอาไปใช้กับ endpoint ที่ไม่มีลายเซ็น ช่องนี้จะเปิด
B: ปิดโดยโครงสร้าง
ทาง B
ปิดโดยโครงสร้าง — A ต้องพึ่งชั้นลายเซ็นซึ่งเป็นเงื่อนไขภายนอก
12ข้อมูลขากลับ (response) ทำได้ตามปกติ เข้ารหัสเฉพาะ field ที่อ่อนไหว🔴 ทำไม่ได้ตรง ๆ — ถ้าเข้ารหัสทั้งก้อน จะทับ error message กับ status ที่ระบบอื่นต้องอ่านด้วยB: ขากลับต้องออกแบบใหม่ทั้งชุด ไม่ใช่แค่กลับด้านทาง A
ทำได้จริงวันนี้ — ขากลับของ B ยังไม่มีทางไปและชนกฎ error body

คำตัดสินรวมของ advisor

เลือกทาง A — แต่ ไม่ใช่เพราะนับแต้ม (A ชนะ 6 · B ชนะ 5 · เสมอ 1 ซึ่งแทบเท่ากัน)

เหตุผลคือ น้ำหนักของมิติที่แพ้ไม่เท่ากัน

  • มิติที่ B แพ้ เป็น dealbreaker เชิงปฏิบัติ — ข้อ 3 (หักทุกคนที่เรียกอยู่วันนี้) กับข้อ 12 (ขากลับยังไม่มีทางไป) สองข้อนี้ไม่มี mitigation ราคาถูก
  • มิติที่ A แพ้ มี ทางปิดที่ถูกกว่ามาก — ข้อ 2 (ลืม mark) ปิดด้วยด่านตรวจอัตโนมัติตาม §4 · ข้อ 1 กับ 11 พึ่งการจำแนกข้อมูลกับชั้นลายเซ็นที่มีอยู่แล้ว

สรุปผลกระทบเป็นภาพเดียว

  • ทาง A เจ็บช้าแต่เจ็บเรื่อย ๆ — วันแรกทุกอย่างราบรื่น แต่ความปลอดภัยขึ้นกับวินัยของคนที่มา mark field ในอีกหลายปีข้างหน้า
  • ทาง B เจ็บหนักวันแรกแต่จบ — ต้องนัดทุกคนเปลี่ยนพร้อมกัน ต้องยอมมืดตอน debug ต้องออกแบบขากลับใหม่ แต่พอผ่านแล้วไม่มีช่องให้ลืม

แล้วข้างนอกเขาทำกันแบบไหน

แบบใครทำหมายเหตุ
ไม่ทำเลย — พึ่ง TLS + การยืนยันตัวตนอย่างเดียวส่วนใหญ่ที่สุด — API สาธารณะระดับโลกแทบทั้งหมด (Stripe · GitHub · ผู้ให้บริการคลาวด์เจ้าใหญ่)ถือว่า TLS พอแล้ว และไม่ยอมแลกความซับซ้อนกับ gateway/observability ที่ต้องเสียไป
รายฟิลด์ (ทาง A)เมื่อ มีข้อบังคับ ให้ปกป้องข้อมูลบางตัวเป็นพิเศษ — ต้นแบบคือ มาตรฐานบัตรเครดิต (PCI) ที่เข้ารหัสเลขบัตรตั้งแต่เครื่องรูดจนถึงผู้ประมวลผล เพื่อไม่ให้ระบบของร้านค้าเห็นเลขบัตรเลยเป็นรูปแบบที่พบบ่อยที่สุดเมื่อมีการทำจริง เพราะ gateway กับเครื่องมือ monitoring ยังทำงานได้
ทั้งก้อน (ทาง B)งาน ธนาคารคุยกับธนาคาร และ payment gateway ฝั่งเอเชียบางเจ้า (อินเดีย/จีน/เอเชียตะวันออกเฉียงใต้) มักบังคับให้เข้ารหัสทั้ง payloadมักเป็นกรณีที่ “API” จริง ๆ แล้วคือท่อส่งข้อความ ไม่ใช่ REST API ที่มีคนอื่นมาใช้ต่อ
ผสม — เซ็นทั้งข้อความ + เข้ารหัสเฉพาะ field สำคัญ🔵 รูปแบบที่นิยมที่สุดเมื่อองค์กรตัดสินใจทำจริงตรงกับที่ระบบนี้มีอยู่แล้ว — เรามีชั้นลายเซ็นครอบทั้ง body อยู่แล้ว บวกเข้ารหัสรายฟิลด์ = แบบผสมพอดี

ข้อมูลที่น่าสนใจที่สุดในตารางนี้: มาตรฐาน FAPI 2.0 ของวงการธนาคารเปิด ตัดการเซ็นและเข้ารหัส request แบบเดิมออกจากตัวมาตรฐานหลัก แล้วใช้วิธีส่งคำขอทางช่องหลังบ้าน (PAR) แทน — แต่ยังเก็บการเซ็นไว้เป็น profile เสริมสำหรับกรณีที่ต้องการปฏิเสธความรับผิดชอบไม่ได้ · และ profile เสริมนั้นระบุชัดว่าไม่ต้องเข้ารหัส · รายละเอียดเต็มอยู่ใน §2.1 ข้างล่าง

สรุป: ถ้าเทียบกับข้างนอก การที่เราทำ ลายเซ็น + เข้ารหัสรายฟิลด์ ถือว่าอยู่ในกลุ่มที่ทำมากกว่าค่าเฉลี่ยอยู่แล้ว · การขยับไป ทั้งก้อน จะทำให้เราอยู่ในกลุ่มที่แคบลงมาก ซึ่งส่วนใหญ่เป็นงานท่อส่งข้อความระหว่างธนาคาร ไม่ใช่ API ที่มีหลายฝ่ายมาต่อ

2.1 TLS กับ mTLS ต่างกันยังไง

TLS (ที่ใช้กันทั่วไป)mTLS (TLS สองทาง)
ใครพิสูจน์ตัวตนฝั่ง server ฝ่ายเดียว — เบราว์เซอร์ตรวจว่าใบรับรองของเว็บถูกต้องไหมทั้งสองฝั่ง — server ตรวจใบรับรองของ client ด้วย
client เป็นใครรู้ตอนไหนรู้ทีหลัง ที่ชั้นแอป (รหัสผ่าน · token · API key)รู้ตั้งแต่ก่อนส่งข้อมูลแม้แต่ไบต์เดียว
ใช้กับอะไรเว็บทั่วไป — เพราะแจกใบรับรองให้ผู้ใช้ทุกคนไม่ไหวระบบคุยกับระบบ — จำนวนคู่สนทนาจำกัด แจกใบรับรองได้
ได้อะไรเพิ่มเข้ารหัสระหว่างทาง + รู้ว่าคุยกับ server ตัวจริงเพิ่มการยืนยันตัวตน client + ใช้ ผูก token เข้ากับใบรับรอง ได้ ⇒ ขโมย token ไปใช้ต่อไม่ได้ถ้าไม่มีกุญแจส่วนตัวคู่กัน

2.2 FAPI 2.0 มีอะไรบ้าง และเราขาดอะไร

FAPI (Financial-grade API) เป็นชุดมาตรฐานของ OpenID Foundation สำหรับ API ที่ต้องการความปลอดภัยระดับการเงิน มี 3 เอกสาร

เอกสารสถานะเนื้อหา
FAPI 2.0 Security ProfileFinal · 22 ก.พ. 2025ตัวหลัก — ข้อบังคับเรื่องการขอและใช้ token
FAPI 2.0 Attacker Modelนิยามว่ากันภัยแบบไหนบ้าง
FAPI 2.0 Message SigningFinal · ก.ย. 2025เสริม ไม่บังคับ — เซ็นข้อความเพื่อให้ปฏิเสธความรับผิดชอบไม่ได้ · ระบุชัดว่าไม่ต้องเข้ารหัส

เทียบข้อบังคับกับของที่เรามี

#FAPI 2.0 Security Profile บังคับเรามีไหมช่องว่าง
1token ต้องผูกกับผู้ถือ (sender-constrained) ด้วย mTLS หรือ DPoP — ห้ามออก bearer token ธรรมดา❌ เป็น bearer token🔴 ช่องว่างใหญ่สุด — แต่ดูหมายเหตุข้างล่าง
2client ยืนยันตัวตนด้วย private_key_jwt หรือ mTLS เท่านั้น❌ ใช้ client secret (ยืนยันแล้วในโค้ด Sentinel — TokenRefreshService กับ GraphTokenProvider ส่ง client_secret)🔴 ต้องเปลี่ยนไปใช้กุญแจแทนรหัสลับ
3PAR — ส่งคำขอ authorize ทางช่องหลังบ้านก่อน และต้องปฏิเสธคำขอที่ไม่ผ่าน PAR❓ ยังไม่ยืนยันขึ้นกับว่า Entra รองรับไหม — เช็คที่ .well-known/openid-configuration ของ tenant ว่ามี pushed_authorization_request_endpoint หรือเปล่า
4PKCE ด้วยวิธี S256มีอยู่แล้ว — Sentinel ใช้ AddOpenIdConnect มาตรฐานและไม่ได้ปิด UsePkce (ค่าตั้งต้นของ framework คือเปิด)ไม่มีช่องว่าง
5iss ในคำตอบ (กันสลับผู้ให้บริการ)❓ ยังไม่ยืนยันเช็คว่า Entra ส่ง iss กลับมาไหม และเราตรวจหรือเปล่า
6TLS 1.2 ขึ้นไป + ชุดรหัสตาม BCP 195❓ น่าจะผ่าน แต่ยังไม่ได้ตรวจ cipher จริงตรวจที่ระดับ gateway/ingress
7ใช้ authorization code flow ห้าม implicit✅ ใช้ code flow อยู่แล้วไม่มีช่องว่าง

หมายเหตุสำคัญของข้อ 1 — อย่าอ่านว่า “เราสอบตกข้อใหญ่สุด”

สถาปัตยกรรมของเราเก็บ token ไว้ฝั่ง server ทั้งหมด (คุกกี้เป็นแค่ตัวชี้ ไม่มี token อยู่ข้างใน) ⇒ ภัยที่ข้อ 1 ตั้งใจจะกัน คือ “token ถูกขโมยจากเบราว์เซอร์แล้วเอาไปใช้ต่อ” ถูกตัดออกไปเยอะแล้วโดยโครงสร้าง เพราะ token ไม่เคยไปถึงเบราว์เซอร์เลย

สิ่งที่ยังเหลือคือ คุกกี้ถูกขโมย ไม่ใช่ token ถูกขโมย ⇒ ช่องว่างจริงเล็กกว่าที่ตารางทำให้ดู · แนวทางที่เราใช้ (เก็บ token ที่ server แล้วให้เบราว์เซอร์ถือแค่ตัวชี้) ตรงกับข้อเสนอแนะของเอกสาร OAuth 2.0 for Browser-Based Applications (ยังเป็นร่าง) อยู่แล้ว

ถ้าจะเดินตาม FAPI จริง เรียงตามความคุ้ม

  1. เปลี่ยน client secret เป็น private_key_jwt (ข้อ 2) — ทำได้ฝั่งเราเป็นหลัก ไม่ต้องรอ Entra รองรับอะไรใหม่ และตัดความเสี่ยงรหัสลับรั่วออกไปเลย
  2. เช็คว่า Entra รองรับ PAR ไหม (ข้อ 3) — ถ้ารองรับ เปิดได้ค่อนข้างตรงไปตรงมา
  3. ผูก token กับผู้ถือด้วย DPoP (ข้อ 1) — งานใหญ่สุด และคุ้มน้อยสุดสำหรับเรา เพราะโครง server-side token ตัดภัยหลักไปแล้ว

แหล่งอ้างอิง

⚠️ สิ่งที่ยังไม่ได้ยืนยัน: สถานะการรองรับ PAR และ DPoP ของ Microsoft Entra — ค้นแล้วยังไม่เจอเอกสารทางการที่ชี้ขาด ต้องตรวจจาก .well-known/openid-configuration ของ tenant เราเอง


3. เคสที่ต้องคิดเผื่อ

ไม่ได้มีแต่เบราว์เซอร์ของเราเองที่เรียก API — เคสพวกนี้ต้องมีคำตอบก่อนตัดสิน

เคสทาง A เจออะไรทาง B เจออะไรทางออกที่เสนอ
service อื่นในบ้านเรียกกันเอง (ไม่มีเบราว์เซอร์ ไม่มี WebCrypto)ส่ง plaintext มาได้เลย ไม่ต้องทำอะไรต้องเขียน crypto ฝั่ง .NET เองทุก service ที่เรียกแยกเป็นเส้นของภายในต่างหาก — ดู §3.1 ข้างล่าง
partner ภายนอกเรียกเข้ามาบอกเขาว่าต้องเข้ารหัส field ไหนบ้าง + ต้องตามเมื่อรายการเปลี่ยนบอกว่า “เข้ารหัสทั้งก้อน” จบ อธิบายง่ายกว่ามากแยกเป็นเส้นของ partner ต่างหาก — แต่ “ไม่เข้ารหัส” มีเงื่อนไข ดู §3.2
QA · Postman · ทดสอบมือทดสอบ field ที่ไม่เข้ารหัสได้ตามปกติต้องมีเครื่องมือเข้ารหัสก่อนถึงจะยิงได้เลยสักครั้งเตรียม collection ที่เข้ารหัสให้อัตโนมัติ · ทาง B ต้องมีตั้งแต่วันแรก ไม่งั้น QA ทำงานไม่ได้
งานเบื้องหลัง · batch · sync จากระบบ HRไม่กระทบ ส่ง plaintext ตามเดิมต้องแก้ทุกตัวที่ยิงเข้า endpoint นั้นหา caller ให้ครบก่อนเปิด — ตัวที่มักลืมคือ cronjob กับ script ของ ops
หน้าเว็บเวอร์ชันเก่าที่ยังค้างในเครื่องคนยังใช้งานได้พังทันทีที่เปิดทาง B ต้องมีช่วงรับทั้ง 2 แบบอยู่ดี ⇒ ข้อดีเรื่อง “ลืมไม่ได้” หายไปในช่วงเปลี่ยนผ่าน
เพิ่ม field อ่อนไหวใหม่ในอนาคตต้องมีคนไป mark · ลืมแล้วไม่มีสัญญาณเตือนครอบเองทาง A ต้องมี ด่านตรวจอัตโนมัติ ว่า field ที่จำแนกว่าอ่อนไหวถูก mark ครบ ไม่ใช่ฝากไว้กับ reviewer
เกิดปัญหาบน production ต้องสืบตามรอยจาก field ที่ไม่อ่อนไหวได้ต้องพึ่ง trace id อย่างเดียวทาง B ต้องลง correlation id ที่ไม่ได้เข้ารหัส ไว้ที่ header ให้ครบก่อน ไม่งั้นสืบไม่ได้เลย

3.1 แยกเส้นสำหรับภายใน — เห็นด้วย แต่ยังไม่ใช่ตอนนี้

ท่าที่เสนอ: แยก route เป็น 2 เส้น (เส้นข้างนอกสำหรับหน้าเว็บ · เส้นข้างในสำหรับ service ด้วยกัน) แล้ววิ่งเข้า business logic ตัวเดียวกัน

ปรับนิดเดียวจากที่คุณเสนอ: ควรเป็น 2 action บาง ๆ แยกกัน แล้วส่งคำสั่งตัวเดียวกันเข้าไป ไม่ใช่ action เดียวแปะ 2 route — เพราะสิ่งที่ทำให้ 2 เส้นต่างกัน (ใครเข้าได้ · ต้องเซ็นไหม · field ไหนเข้ารหัส) เป็นของที่ผูกกับ action ไม่ใช่ผูกกับ route ⇒ action เดียวจะตั้งนโยบายต่างกันไม่ได้เลย · ส่วน business logic ยังใช้ตัวเดียวกัน ไม่มีโค้ดซ้ำ

ได้อะไรต้องระวังอะไร
เส้นข้างนอก บังคับเข้ารหัสได้จริง ไม่ต้องเปิดรับ plaintext ค้างไว้ตลอดกาล🔴 เส้นข้างในต้องออกไปข้างนอกไม่ได้จริง ๆ — ต้องกันด้วยระดับเครือข่าย (ไม่ลงทะเบียนใน gateway เลย) ไม่ใช่กันด้วยความตั้งใจ · ถ้าหลุด = มีประตูหลังที่ข้ามการเข้ารหัสทั้งหมด
ตั้งวิธียืนยันตัวตนคนละแบบได้ (ข้างใน = API key/mTLS · ข้างนอก = token ของผู้ใช้ + ลายเซ็น)มี 2 เส้นให้ดูแล — การตรวจความถูกต้องของข้อมูลต้องไม่หลุดจากกัน
แยก log แยกสถิติได้ว่า traffic มาจากทางไหนเอกสารต้องแยกกลุ่ม ไม่งั้นคนนอกไปเจอเส้นข้างใน

คำตอบสำหรับ “เอาแบบง่ายสุด”: ตอนนี้ยังไม่ต้องแยก — ใช้เส้นเดียวที่รับได้ทั้ง plaintext และแบบเข้ารหัสไปก่อน เพราะยังไม่มี service ไหนในบ้านยิงเส้นนี้จริง อย่าสร้างประตูรอคนที่ยังไม่มา

แยกเมื่อไหร่: ตอนที่จะ บังคับ ให้ทุก request เข้ารหัส เพราะ “บังคับ” กับ “รับ plaintext ด้วยก็ได้” อยู่บนเส้นเดียวกันไม่ได้

3.2 แยกเส้นสำหรับ partner — เห็นด้วยกับโครง แต่ “ไม่เข้ารหัส” ต้องมีเงื่อนไข

เห็นด้วย ว่าควรมีเส้นของ partner แยกต่างหาก — คนละสัญญา คนละรอบเวลาปล่อยของ คนละวิธียืนยันตัวตน

แต่เหตุผล “เป็น service-to-service อยู่แล้ว เลยไม่ต้องเข้ารหัส” ยังไม่พอ — เพราะ partner อยู่ข้างนอก ⇒ traffic ของเขา วิ่งผ่าน gateway ตัวเดียวกับที่เราไม่ไว้ใจจนต้องสร้างชั้นนี้ขึ้นมา ถ้าเขาส่งเลขบัตร/วันเกิดเป็น plaintext ผ่านเส้นนั้น เท่ากับเจาะรูข้าง ๆ ประตูที่เพิ่งล็อกไป

จะให้เส้น partner เป็น plaintext ได้ ต้องจริงข้อใดข้อหนึ่ง:

#เงื่อนไขแปลว่า
เส้นนั้น ไม่ผ่านคนกลางที่เราไม่ไว้ใจต่อกันแบบ private link / mTLS ที่ไม่ถูกถอดกลางทาง — ไม่ใช่วิ่งผ่าน gateway ปกติ · 🔴 “เปิด mTLS ที่ gateway” ไม่นับ ดูเหตุผลใน §2.1
ข้อมูลที่ partner ส่ง ไม่ใช่ชุดอ่อนไหวเดียวกันถ้าเขาส่งแค่รหัสอ้างอิง/สถานะ ไม่มีเลขบัตรหรือวันเกิด ก็ไม่มีอะไรให้ปกป้อง

ถ้าข้อใดข้อหนึ่งจริง = ไม่ต้องเข้ารหัส จบ · ถ้าไม่จริงทั้งคู่ เส้น partner ก็ต้องเข้ารหัสเหมือนกัน (ซึ่งทาง B อธิบายให้ partner ฟังง่ายกว่า — ดูตารางข้างบน)


4. ข้อเสนอ

ทาง A (รายฟิลด์) เหมาะกว่าถ้าโจทย์คือ “เริ่มใช้จริงได้เร็ว โดยไม่หักคนที่เรียกอยู่” — โดยเฉพาะถ้ามี partner ภายนอกหรือ service อื่นที่เราบังคับตารางเวลาเขาไม่ได้ · และเป็นแบบที่ตรงกับแนวปฏิบัติของอุตสาหกรรมมากกว่า (ดู §2)

ทาง B (ทั้งก้อน) เหมาะกว่าถ้าโจทย์คือ “ต้องไม่มีช่องให้ลืม และยอมจ่ายค่าเปลี่ยนผ่าน” — แต่ต้องยอมรับ 3 เรื่องก่อน: มืดตอน debug · ขากลับต้องออกแบบใหม่ · ทุกคนที่เรียกต้องเปลี่ยนพร้อมกัน

ถ้าเลือกทาง A ต้องปิดช่องนี้ให้ได้ ไม่งั้นไม่ควรเลือก: ด่านตรวจอัตโนมัติว่า field อ่อนไหวถูก mark ครบทุกตัว — เพราะจุดอ่อนเดียวของทาง A คือ “ลืม” ซึ่งจะเกิดแน่นอนถ้าฝากไว้กับคน


5. บันทึกการถก — เพิ่มข้างล่างนี้

วันที่ประเด็นข้อสรุป
27/08เข้ารหัสทั้งก้อน หรือ เฉพาะบาง fieldเลือก เฉพาะบาง field (ทาง A)
27/08หลังบ้านถอดตรงไหนถอดให้เสร็จก่อนถึงโค้ด business ไม่ให้คนเขียน handler ต้องรู้เรื่อง
01/09ทบทวนอีกรอบว่าจะกลับไปทาง B ไหมยังไม่ตัดสิน — หน้านี้คือของที่เอาไว้ตัดสิน
01/09ความเห็น advisor รายมิติทาง A (ชนะ 6 · แพ้ 5 · เสมอ 1) แต่ชี้ว่าน้ำหนักสำคัญกว่าจำนวน — ข้อ 3 กับ 12 ของ B ไม่มีทางแก้ราคาถูก
01/09ตัดเคสแอปมือถือออกOwner สั่งตัด — ไม่อยู่ในแผน ⇒ ลบแถวใน §3 และลบคำถามเปิดที่เคยอ้างมือถือเป็นตัวตัดสิน
01/09service ในบ้านเรียกกันเอง ควรแยกเป็น 2 เส้นวิ่งเข้า logic เดียวกันไหมเห็นด้วยกับโครง ปรับเป็น 2 action บาง ๆ → คำสั่งเดียวกัน (นโยบายผูกกับ action ไม่ใช่ route) · แต่ยังไม่ต้องทำตอนนี้ เพราะยังไม่มี caller จริง — แยกตอนจะบังคับเข้ารหัส ดู §3.1
01/09partner ภายนอก แยก API แล้วไม่เข้ารหัสเพราะเป็น s2sโครงเห็นด้วย · เหตุผลยังไม่พอ — partner วิ่งผ่าน gateway ตัวเดียวกับที่เราไม่ไว้ใจ ⇒ ต้องมีเงื่อนไข ก หรือ ข ใน §3.2 รองรับก่อนถึงจะปล่อย plaintext ได้
01/09Owner ขอเพิ่ม TLS vs mTLS + FAPI 2.0 ขาดอะไร + แหล่งอ้างอิงเพิ่มเป็น §2.1 กับ §2.2 · อ่านจาก spec จริง · ข้อค้นพบสำคัญ 2 ข้อ: mTLS ไม่ได้แก้ปัญหาไม่ไว้ใจคนกลาง (ถอดที่ไหนก็เห็นที่นั่น) และ FAPI ไม่ได้คุมการเข้ารหัส payload ธุรกิจเลย ⇒ เดินตาม FAPI ไม่ได้ตอบคำถาม A/B ของหน้านี้
01/09ท้วงแถว 8 — Swagger ไม่ได้วิ่งผ่าน middleware แล้วทำไมทาง B ถึงจะมองไม่เห็นท้วงถูก ตรวจโค้ดจริงแล้ว: Swagger สร้างจากการอ่านโครง DTO ตอนสตาร์ท ไม่เกี่ยวกับ middleware · เหตุผลเดิม (“ทาง B ไม่เหลือโครงให้บอก”) เป็นของยุคที่ยังถอด [FromBody] ออก ซึ่งเลิกใช้ไปแล้ว · แก้เหตุผลแถว 8 ใหม่เป็น “เอกสารโกหกแค่ไหน” · คำตัดสินยังเป็นทาง A แต่ชนะห่างน้อยลง · คะแนนรวมไม่เปลี่ยน

คำถามที่ยังเปิดอยู่

  1. ยอมรับได้ไหมว่าคนกลางเห็นโครง request กับค่าที่ไม่ได้ mark (ข้อ 1)
  2. ความเสี่ยง “ลืม mark field ใหม่” ปิดด้วยด่านตรวจอัตโนมัติพอไหม หรือต้องการการรับประกันเชิงโครงสร้าง
  3. มี partner ภายนอก หรือ service อื่น ที่จะมาเรียก endpoint กลุ่มนี้ในอีก 6-12 เดือนไหม
  4. 🔴 สำหรับเส้น partner — เงื่อนไข ก หรือ ข ใน §3.2 ข้อไหนเป็นจริง ถ้าไม่จริงสักข้อ เส้น partner ก็ต้องเข้ารหัสด้วย จะปล่อย plaintext ไม่ได้