การอนุญาตให้ User ส่งข้อมูลมาแก้ไข หรือสร้างใหม่ ในระบบ RESTful API เป็นจุดที่อันตรายที่สุดหากไม่มีการควบคุมที่ดีครับ เพราะ Hacker อาจส่ง Field ที่เราไม่ได้อนุญาต เข้ามาใน Request Body เพื่อแอบแก้ไขข้อมูลสำคัญได้
นี่คือสิ่งที่ต้องระวังและจัดการเพื่อให้ API ของคุณปลอดภัยครับ
การควบคุมฟิลด์ที่อนุญาต
นี่คือเรื่องที่สำคัญที่สุด หรือที่เรียกว่า “Allowlist” คือเราต้องระบุให้ชัดเจนว่า Column ไหนบ้างที่อนุญาตให้แก้ไขได้
- Allowed Columns : กำหนดใน Model หรือ Controller ว่า field ไหนที่รับค่าได้ เช่น
first_name,last_name,phone - Guarded Columns: ห้ามรับค่าอย่างเด็ดขาด เช่น
id,role,password_hash,created_at
การทำ Data Validation & Sanitization
อย่าเชื่อใจข้อมูลที่มาจาก Client แม้แต่ตัวอักษรเดีย
- Type Checking: ถ้าช่องนี้เป็นตัวเลข ต้องตรวจสอบว่าเป็นตัวเลขจริงไหม
- Range & Length: เบอร์โทรศัพท์ต้องไม่เกิน 10 หลัก, อายุต้องอยู่ระหว่าง 0-120
- Sanitization: ล้างอักขระพิเศษ (เช่น
<script>) เพื่อป้องกัน XSS (Cross-Site Scripting) และตรวจสอบ Single Quote เพื่อป้องกัน SQL Injection
การตรวจสอบสิทธิ์
ไม่ใช่แค่ Login ผ่าน (Authentication) แล้วจะแก้ได้ทุกอย่าง ต้องเช็คด้วยว่า “เขามีสิทธิ์แก้ Record นี้ไหม”
- Ownership Check: User A ต้องไม่สามารถใช้
PATCH /users/1เพื่อไปแก้ข้อมูลของ User B ได้ - Role-Based Access Control : เฉพาะ Admin เท่านั้นที่เปลี่ยนสถานะ
status: "verified"ของบทความได้
ความแตกต่างระหว่าง PUT และ PATCH
การใช้ Method ให้ถูกช่วยลดโอกาสข้อมูลพัง ได้
- PUT: เป็นการ “แทนที่” ทั้งหมด หากส่งมาไม่ครบ Field ที่หายไปอาจกลายเป็น NULL
- PATCH: เป็นการ “แก้ไขบางส่วน” เฉพาะ Field ที่ส่งมา ซึ่งปลอดภัยและนิยมมากกว่าในการทำ Update
การใช้ DTO
ในระบบขนาดใหญ่ เราจะไม่ส่ง Request Object ตรงๆ เข้าไปที่ฐานข้อมูล แต่จะใช้ DTO เป็นตัวคั่น
- รับ Request เข้ามา
- Map เฉพาะข้อมูลที่ต้องการใส่ใน DTO
- ส่ง DTO ไปจัดการต่อใน Service/Database
🛠 ตัวอย่างการจัดการในทางปฏิบัติ
// ตัวอย่างแบบอันตราย (Don't do this) // รับมาทุกอย่างแล้วบันทึกเลย -> Hacker สามารถส่ง { "role": "admin" } มาได้
User.update (req.body) ;
// ตัวอย่างแบบปลอดภัย (Allowed Columns) const allowedFields = ['first_name', 'last_name', 'bio'];
const updateData = {};
allowedFields.forEach (field => {
if (req.body[field] !== undefined) {
updateData[field] = req.body[field];
}
}) ;
// ตรวจสอบสิทธิ์ก่อนบันทึก
if (user.id === req.user.id || req.user.isAdmin) {
User.update (updateData) ;
}
⚠️ ข้อควรระวังเพิ่มเติม
เมื่อคุณจัดการเรื่อง Security ในระดับ Code แล้ว อย่าลืมระดับ Infrastructure ด้วย
- Rate Limiting: ป้องกันการยิงถล่มเพื่อเดาข้อมูล โดยตั้งค่าใน Nginx
- HTTPS Only: ข้อมูลที่ส่งผ่าน Method POST/PATCH ต้องถูกเข้ารหัสเสมอ เพื่อป้องกันการดักจับข้อมูลระหว่างทาง
อ่านเพิ่มเติม