진짜 엔트로피로 코드 생성
암호학적으로 안전한 난수 생성기를 사용하고, 예측 가능한 시드나 타임스탬프에서 파생한 값은 절대 쓰지 마세요. 여섯 자리가 보안과 사용성의 일반적 균형점이며, 더 짧은 코드는 안전을 위해 더 엄격한 속도 제한이 필요합니다.
같은 코드를 두 번 보내지 말고, 사용자 ID나 순번에서 코드를 유추할 수 있게 만들지 마세요. SMS OTP의 보안 전체가 짧은 수명 안에서 코드가 예측 불가능하다는 데 달려 있습니다.
- 범용 PRNG가 아니라 CSPRNG(예: secrets/crypto 라이브러리)를 사용하세요.
- 6자리가 표준이며, 4자리를 쓴다면 속도 제한과 만료를 강하게 조이세요.
- 요청당 하나의 코드; 새 코드를 발급할 때 이전 코드를 무효화하세요.
코드를 안전하게 저장, 절대 평문 금지
OTP를 수명이 짧은 비밀번호처럼 다루세요. 원시 값이 아니라 (식별자와 만료를 포함한) 코드의 해시를 저장해 데이터베이스 유출이 유효한 코드를 노출하지 않게 하세요. 타이밍 공격을 피하기 위해 상수 시간 함수로 비교하고, 사용되거나 만료되는 즉시 레코드를 삭제하세요.
- 저장 전 코드를 해시하고, 상수 시간으로 비교하세요.
- 각 코드를 단일 사용자/세션과 목적에 결속하세요.
- 성공, 만료, 또는 시도 한도 도달 시 삭제하세요.
짧은 만료 시간 설정
코드는 시간이 아니라 분 단위로 살아 있어야 합니다. 5~10분 만료가 일반적 범위입니다: 지연된 SMS가 아직 도착할 만큼 길고, 유출된 코드가 대개 이미 죽었을 만큼 짧습니다. 느린 메시지가 고장 난 플로우처럼 보이지 않도록 남은 유효 시간을 사용자에게 표시하세요.
짧은 만료를 재전송 쿨다운과 함께 두세요. 그렇지 않으면 메시지가 지연될 때 사용자가 재전송을 연타해 SMS 비용을 배로 늘립니다.
모든 차원에서 속도 제한
속도 제한은 코드의 무차별 대입 추측과 발송 엔드포인트 남용(비용을 발생시키고 발신자가 플래그될 수 있음)을 모두 막는 장치입니다. 발송과 확인 모두에서 전화번호별, IP별, 계정별로 제한하세요.
- 확인 시도: 코드당 시도 횟수(예: 5회)를 제한한 뒤 무효화하고 재전송을 강제하세요.
- 발송 요청: 발송 간 쿨다운과 번호당 일일 한도를 두세요.
- 전역 가드: 자동화 남용을 무디게 하는 IP별·계정별 한도.
- 한도가 반복적으로 걸리면 지수 백오프나 CAPTCHA를 추가하세요.
재시도와 폴백 채널 처리
SMS는 최선 노력 방식이므로 좋은 플로우는 일부 메시지가 도착하지 않으리라 전제합니다. 쿨다운 후 재전송을 제공하고, 한두 번의 SMS 실패 후에는 폴백 채널 — 코드를 읽어 주는 음성 통화, WhatsApp, 이메일 — 로 에스컬레이션하세요. 이것이 바로 관리형 Verify API가 자동화하는 것입니다.
실패 상태를 인간적으로 만드세요: 명확한 "못 받으셨나요?" 경로, 번호를 바꿀 방법, 반복 실패 후 지원 연락 수단.
- 1
첫 시도: SMS
코드를 보내고 재전송 쿨다운과 함께 보이는 카운트다운을 시작하세요.
- 2
요청 시 재전송
쿨다운 후 재전송을 허용하고, 새 코드를 발급할 때 이전 코드를 무효화하세요.
- 3
채널 에스컬레이션
반복된 SMS 실패 후 같은 인증을 전달할 음성이나 대체 채널을 제공하세요.
- 4
출구 제공
막다른 길 대신 사용자가 번호를 고치거나 지원에 닿게 하세요.
보안을 해치지 않으면서 UX 개선
작은 손질이 완료율을 높입니다: 모바일 브라우저가 코드를 읽도록 WebOTP API와 SMS 자동완성 형식을 사용하고, 메시지 문구를 일관되게 유지하며(일부 플랫폼은 신뢰 발신자를 캐싱), 메시지에 코드와 앱 이름 외에는 아무것도 넣지 마세요. OTP 메시지에 링크를 피하세요 — 사용자가 클릭하도록 길들이고 통신사 스팸 필터에 걸립니다.
- 모바일에서 WebOTP / 일회용 코드 자동완성을 지원하세요.
- 메시지를 최소로: 앱 이름 + 코드, 마케팅 없음, 링크 없음.
- 메시지 텍스트는 현지화하되 코드 형식은 안정적으로 유지하세요.