Sinh mã với độ ngẫu nhiên thực sự
Dùng một bộ sinh số ngẫu nhiên an toàn về mật mã, đừng bao giờ dùng hạt giống dễ đoán hay giá trị suy ra từ dấu thời gian. Sáu chữ số là mức cân bằng thường thấy giữa bảo mật và tiện dụng; mã ngắn hơn cần giới hạn tần suất chặt hơn để an toàn.
Đừng bao giờ gửi cùng một mã hai lần, và đừng bao giờ để mã có thể đoán được từ id người dùng hay một chuỗi. Toàn bộ bảo mật của OTP qua SMS dựa trên việc mã không thể đoán trước trong vòng đời ngắn ngủi của nó.
- Dùng CSPRNG (ví dụ thư viện secrets/crypto), không dùng PRNG đa dụng.
- 6 chữ số là chuẩn; nếu dùng 4, hãy siết chặt giới hạn tần suất và thời hạn.
- Một mã cho mỗi yêu cầu; vô hiệu hóa các mã trước khi phát mã mới.
Lưu mã an toàn, đừng bao giờ để dạng văn bản thô
Hãy đối xử với một OTP như một mật khẩu sống ngắn. Lưu một băm của mã (kèm định danh và thời hạn) thay vì giá trị thô, để một vụ rò rỉ cơ sở dữ liệu không phơi bày các mã còn hiệu lực. So sánh bằng một hàm thời gian hằng số để tránh tấn công theo thời gian, và xóa bản ghi ngay khi mã được dùng hoặc hết hạn.
- Băm mã trước khi lưu; so sánh theo thời gian hằng số.
- Buộc mỗi mã vào một người dùng/phiên và một mục đích duy nhất.
- Xóa khi thành công, hết hạn, hoặc khi chạm trần số lần thử.
Đặt một cửa sổ thời hạn ngắn
Mã nên sống vài phút, không phải vài giờ. Thời hạn 5–10 phút là khoảng thường gặp: đủ dài để một SMS trễ vẫn kịp đến, đủ ngắn để một mã bị rò rỉ thường đã chết. Hiển thị thời hạn còn lại cho người dùng để một tin nhắn chậm không bị hiểu là một luồng hỏng.
Ghép thời hạn ngắn với một khoảng chờ gửi lại, nếu không người dùng sẽ bấm gửi lại liên tục khi tin nhắn trễ và nhân đôi chi phí SMS của bạn.
Giới hạn tần suất trên mọi chiều
Giới hạn tần suất là thứ chặn cả việc dò mã bằng vét cạn lẫn việc lạm dụng điểm cuối gửi của bạn (điều tốn tiền và có thể khiến người gửi của bạn bị gắn cờ). Giới hạn theo số điện thoại, theo IP và theo tài khoản, ở cả khâu gửi lẫn khâu kiểm tra.
- Lần thử kiểm tra: giới hạn số lần trên mỗi mã (ví dụ 5) rồi vô hiệu hóa và buộc gửi lại.
- Yêu cầu gửi: khoảng chờ giữa các lần gửi và một trần mỗi ngày trên mỗi số.
- Rào chắn toàn cục: giới hạn theo IP và theo tài khoản để làm cùn sự lạm dụng tự động.
- Thêm lùi lũy thừa hoặc CAPTCHA khi các giới hạn bị chạm liên tục.
Xử lý thử lại và kênh dự phòng
SMS chỉ ở mức nỗ lực tốt nhất, nên một luồng tốt giả định rằng một số tin nhắn sẽ không đến. Cho gửi lại sau một khoảng chờ, và sau một hai lần SMS thất bại, hãy nâng cấp lên một kênh dự phòng — một cuộc gọi thoại đọc mã, WhatsApp, hoặc email — đó chính là điều các Verify API được quản lý tự động hóa.
Làm cho các trạng thái thất bại có tính người: một lối "chưa nhận được?" rõ ràng, một cách đổi số, và liên hệ hỗ trợ sau khi thất bại lặp lại.
- 1
Lần đầu: SMS
Gửi mã và bắt đầu một đồng hồ đếm ngược hiển thị kèm khoảng chờ gửi lại.
- 2
Gửi lại theo yêu cầu
Cho phép gửi lại sau khoảng chờ; vô hiệu hóa mã trước khi bạn phát mã mới.
- 3
Nâng cấp kênh
Sau khi SMS thất bại lặp lại, cung cấp thoại hoặc một kênh thay thế để giao cùng một lần xác minh.
- 4
Cung cấp lối thoát
Cho người dùng sửa số của họ hoặc liên hệ hỗ trợ thay vì đi vào ngõ cụt.
Cải thiện trải nghiệm mà không làm yếu bảo mật
Vài chi tiết nhỏ nâng tỷ lệ hoàn tất: dùng WebOTP API và các định dạng tự điền SMS để trình duyệt di động có thể đọc mã, giữ câu chữ tin nhắn nhất quán (một số nền tảng lưu bộ nhớ đệm người gửi tin cậy), và đừng bao giờ đưa gì khác ngoài mã và tên ứng dụng vào tin nhắn. Tránh đường liên kết trong tin OTP — chúng tập cho người dùng bấm vào, và làm vướng bộ lọc thư rác của nhà mạng.
- Hỗ trợ WebOTP / tự điền mã một lần trên di động.
- Giữ tin nhắn tối giản: tên ứng dụng + mã, không tiếp thị, không liên kết.
- Bản địa hóa văn bản tin nhắn nhưng giữ định dạng mã ổn định.