Presigned URL cho client PUT thẳng lên S3, server không phải làm proxy dữ liệu — nhưng đổi lại server mất quyền kiểm tra nội dung tại chỗ. Bù lại bằng hai việc: siết ngay trong chữ ký, và xác nhận sau khi upload.
Siết ngay khi ký: URL ký kèm điều kiện, không ký "trống".
- Đặt sẵn key theo quy tắc của server (uploads/{userId}/{uuid}.jpg), không cho client tự chọn đường dẫn — nếu không, user ghi đè file của người khác.
- Ràng buộc Content-Type và giới hạn kích thước (với POST policy dùng content-length-range), TTL ngắn (vài phút).
- Kiểm tra hạn mức của user trước khi cấp URL. Cấp chữ ký = cấp quyền ghi, nên chính bước cấp mới là chốt phân quyền.
Biết upload xong — không tin client:
- Cách chắc chắn là S3 Event Notification (s3:ObjectCreated:*) bắn vào SQS/Lambda/webhook. Đây là nguồn sự thật vì nó phát ra từ chính S3.
- Hoặc client gọi POST /uploads/:id/complete, nhưng server phải HeadObject để xác nhận file có thật, kích thước và content-type đúng, rồi mới chuyển bản ghi sang ready.
Xác thực nội dung sau đó: đọc magic bytes thay vì tin phần mở rộng, quét virus nếu là file người dùng chia sẻ cho nhau, và ảnh thì re-encode qua thư viện xử lý ảnh để loại payload nhúng.
Mô hình dữ liệu: tạo bản ghi files trạng thái pending trước khi ký; job dọn các bản ghi pending quá hạn và object mồ côi trên bucket.