本物のエントロピーでコードを生成
暗号学的に安全な乱数生成器を使い、予測可能なシードやタイムスタンプ由来の値は決して使わないでください。6桁がセキュリティと使いやすさの一般的なバランスで、より短いコードは安全のためにより厳しいレート制限が必要です。
同じコードを二度送らず、ユーザーIDや連番からコードを推測できるようにしないでください。SMS OTPのセキュリティ全体は、短い寿命の中でコードが予測不能であることにかかっています。
- 汎用PRNGではなくCSPRNG(例:secrets/cryptoライブラリ)を使いましょう。
- 6桁が標準;4桁を使うならレート制限と有効期限を強く締めましょう。
- リクエストごとに1つのコード;新しいコードを発行するとき以前のコードを無効化しましょう。
コードを安全に保存、平文は厳禁
OTPを寿命の短いパスワードのように扱いましょう。生の値ではなく(識別子と有効期限を含む)コードのハッシュを保存し、データベース漏洩が有効なコードを露出させないようにします。タイミング攻撃を避けるため定数時間関数で比較し、使用または期限切れになった瞬間にレコードを削除しましょう。
- 保存前にコードをハッシュ化し、定数時間で比較しましょう。
- 各コードを単一のユーザー/セッションと目的に結び付けましょう。
- 成功、期限切れ、または試行上限到達時に削除しましょう。
短い有効期限を設定
コードは時間単位ではなく分単位で生きるべきです。5〜10分の有効期限が一般的な範囲です:遅れたSMSがまだ届くほど長く、漏れたコードがたいてい既に無効なほど短い。遅いメッセージが壊れたフローに見えないよう、残りの有効時間をユーザーに表示しましょう。
短い有効期限を再送クールダウンとセットにしましょう。さもないとメッセージが遅れたときユーザーが再送を連打し、SMSコストが倍増します。
あらゆる次元でレート制限
レート制限は、コードの総当たり推測と送信エンドポイントの悪用(コストが発生し送信者がフラグされることがある)の両方を止める仕組みです。送信と確認の両方で、電話番号ごと、IPごと、アカウントごとに制限しましょう。
- 確認試行:コードあたりの試行回数(例:5回)を制限し、その後無効化して再送を強制しましょう。
- 送信リクエスト:送信間のクールダウンと番号あたりの1日上限を設けましょう。
- グローバルガード:自動化された悪用を鈍らせるIPごと・アカウントごとの上限。
- 上限が繰り返し当たったら指数バックオフやCAPTCHAを追加しましょう。
リトライとフォールバックチャネルの処理
SMSはベストエフォートなので、良いフローは一部のメッセージが届かないと想定します。クールダウン後の再送を提供し、1〜2回のSMS失敗後はフォールバックチャネル — コードを読み上げる音声通話、WhatsApp、メール — へエスカレーションしましょう。これこそマネージドVerify APIが自動化するものです。
失敗状態を人間的にしましょう:明確な「届きませんでしたか?」の導線、番号を変える方法、繰り返しの失敗後のサポート連絡先。
- 1
最初の試行:SMS
コードを送り、再送クールダウン付きの見えるカウントダウンを開始しましょう。
- 2
要求時に再送
クールダウン後の再送を許可し、新しいコードを発行するとき以前のコードを無効化しましょう。
- 3
チャネルのエスカレーション
繰り返しのSMS失敗後、同じ認証を届ける音声や代替チャネルを提供しましょう。
- 4
出口を提供
行き止まりではなく、ユーザーが番号を直したりサポートに届けるようにしましょう。
セキュリティを損なわずにUXを改善
小さな工夫が完了率を高めます:モバイルブラウザがコードを読めるようWebOTP APIとSMS自動入力の形式を使い、メッセージの文言を一貫させ(一部のプラットフォームは信頼できる送信者をキャッシュ)、メッセージにコードとアプリ名以外は何も入れないこと。OTPメッセージにリンクは避けましょう — ユーザーをクリックに慣らし、キャリアのスパムフィルターに引っかかります。
- モバイルでWebOTP/ワンタイムコード自動入力をサポートしましょう。
- メッセージは最小限に:アプリ名 + コード、マーケティングなし、リンクなし。
- メッセージ文はローカライズしても、コードの形式は安定させましょう。