APIセキュリティのベストプラクティスをVerifyする
Vonage Verify API は、電話番号の確認および多要素認証(MFA)のための、安全で信頼性の高いサービスです。しかし、他の認証システムと同様、その全体的なセキュリティは、バックエンドアプリケーションが本サービスとどのように連携しているかにも等しく左右されます。本ガイドでは、ユーザーを保護するために、実装において対応すべき最も重要なセキュリティ上の考慮事項について説明します。
注: このガイドに記載されているベストプラクティスは、SMS、Voice API、WhatsApp、サイレント認証、およびその他のサポートされているチャネルを含む、すべてのVerify APIチャネルに適用されます。一部の例ではサイレント認証のフローが参照されていますが、その根底にある原則はどのチャネルにも共通しています。
信頼モデルを理解する
Vonage Verify API は、ある特定の機能、すなわち、指定された request_id そして code このペアは正しい。ただし、そのペアを送信した人物が、当初検証リクエストを発行した人物と同一人物であるかどうかを判断することはできない。
これは極めて重要な違いです。Verify API はネットワークレベルで動作するため、ユーザーのセッション、ログイン状態、アプリケーションのコンテキストについては一切認識していません。 バックエンドは、認証リクエストを特定のユーザーおよびセッションに関連付ける役割を担っています。 バックエンドがこのバインディングを強制しない場合、有効な request_id そして code ペア(別のセッション、以前のリクエスト、あるいはクライアント側の通信を傍受して取得したもの)は、これを利用して、標的とする任意の電話番号として認証を行うことができます。
したがって、セキュリティモデルは、互いに補完し合う2つの層として理解すべきである:
- Vonageの責任: 暗号学的に正しいものを生成する
code, それを安全に配信し、そのrequest_id/codeペア。 - あなたの責任: 確実に
request_idそしてcode検証のために送信されるのは、現在のセッションにおいて、バックエンドが認証済みユーザーに対して開始したリクエストです。
検証は常にサーバー側で実行する
クライアントアプリケーション(モバイルアプリ、ブラウザ、またはフロントエンド)が、Vonage Verify API に対して直接認証リクエストを送信しないようにしてください。以下の API へのすべての呼び出しは、 POST /v2/verify バックエンドサーバーから実行する必要があります。
これにより、以下のことが保証されます:
- APIの認証情報は、クライアントに公開されることはありません。
- 認証に使用される電話番号は、そのユーザーについてシステムに登録されている番号であり、実行時にクライアントから提供された値ではありません。
- 検証のライフサイクルについては、バックエンド側が完全に管理します。
パターンが間違っています (これはやらないでください):
Client → POST /v2/verify (directly to Vonage, with phone number from user input)
正しいパターン:
Client → POST /your-backend/start-verification
Backend → POST /v2/verify (to Vonage, using phone number from your database)
Backend stores request_id in session/database
Backend → returns request_id (or nothing) to client
request_id をサーバー側で保存し、ユーザーのセッションに紐付ける
Vonage Verify API が検証開始リクエストに応答すると、 request_id. この値は、バックエンド(たとえば、サーバーサイドのセッションやデータベースのレコードなど)に安全に保存し、以下に明示的に関連付ける必要があります:
- 検証を開始した特定のユーザーまたはAccount。
- 電話番号の確認中です。
- 現在の認証セッションまたはトランザクション。
その後、クライアントが検証用のコードを送信した際、バックエンドは以下の処理を行う必要があります:
- を取得する
request_id(クライアント側ではなく)自身のセッション/データベースから。 - 電話
POST /v2/verify/{request_id}サーバーに保存されているもののみを使用してrequest_id.
決して受け入れてはならない request_id クライアントからの入力として。 バックエンドが request_id クライアントからのリクエストを受け取り、それをVonageに直接転送する場合、攻撃者は request_id 以前の有効な認証(別の電話番号や別のユーザーに対するものを含む)に基づいていれば、APIは検証成功を返します。
正しい実装例(Node.js/Express):
// Start verification — called from your app backend only
app.post('/start-verification', async (req, res) => {
const user = await getUserFromSession(req.session.userId);
// Phone number comes from YOUR database, not the client request
const { request_id } = await vonage.verify.start({
brand: 'YourApp',
workflow: [{ channel: 'sms', to: user.phoneNumber }]
});
// Store request_id server-side, bound to the user session
req.session.pendingVerification = {
request_id,
phoneNumber: user.phoneNumber,
userId: user.id,
createdAt: Date.now()
};
res.json({ status: 'verification_started' });
});
// Check code — request_id is retrieved from session, never from client
app.post('/check-code', async (req, res) => {
const { code } = req.body;
const pending = req.session.pendingVerification;
if (!pending || !pending.request_id) {
return res.status(400).json({ error: 'No active verification for this session' });
}
const result = await vonage.verify.check(pending.request_id, code);
if (result.status === 'completed') {
// Clear the pending verification after success
delete req.session.pendingVerification;
res.json({ verified: true });
} else {
res.status(401).json({ verified: false });
}
});
セキュリティ上の判断において、クライアントから提供されたパラメータを決して信用してはならない
クライアントアプリケーションは、認証フローの一環として、バックエンドにデータを送信する場合があります(例えば、 request_id (サイレント認証のリダイレクトで使用するためにクライアントに返される)。このような値はすべて、信頼できない入力として扱うこと:
- しないでください クライアントから提供された
request_idVerifyチェックエンドポイントを呼び出すには。 - しないでください クライアントから提供された電話番号を使用して、どのユーザーをVerifyするかを決定します。
- しないでください 検証が成功したかどうかを判断するために、クライアント側の状態に依存する。
クライアントの役割は、バックエンドでのアクションのトリガー(自社サーバーへの認証済みAPI呼び出しを通じて)および、サイレント認証の場合には、 check_url モバイル通信事業者のネットワーク経由でリダイレクトします。バックエンド側で、その結果を独自に追跡・Verifyする必要があります。
セッションごとの検証ライフサイクル管理の実装
各検証処理は、1つのセッションおよび1人のユーザーに限定する必要があります。バックエンドにおいて、以下のライフサイクル制御を実装してください:
- ユーザー1人につき1つのアクティブなリクエスト: 新しい検証を開始する前に、保留中の
request_idそのユーザーにはすでに存在しています。新しいものを開始する前に、それをキャンセルするか、有効期限を切らせてください。 - 保留中のリクエストの有効期限が短い: ユーザーが妥当な時間枠(例:5分)内に認証を完了しなかった場合、セッションに保存されている
request_idバックエンド上で、開始するには新たな認証が必要となります。 - 使い捨て消費: 検証が正常に完了したら、直ちに
request_idセッション/データベースから。完了したrequest_idApplications内では、決して再利用すべきではありません。 - 試行に失敗した場合は無効にする: 設定された回数分のコード送信に失敗した場合は、検証リクエストをキャンセルし、ユーザーに最初からやり直すよう促す。
アプリケーションレベルでレート制限を適用する
Vonage Verify API には不正防止機能が組み込まれていますが、列挙攻撃、フラッディング攻撃、またはブルートフォース攻撃のリスクを軽減するため、ご自身のアプリケーションでもレート制限を実装する必要があります:
- 電話番号ごとの検証リクエスト数に制限を設ける: 特定の電話番号に対して、一定期間内に送信できる認証リクエストの数を制限する(例:1時間あたり3件まで)。
- ユーザーアカウントまたはIPアドレスごとのリクエスト数を制限する: 1つのアカウントまたはオリジンから、過度な数の認証リクエストが送信されるのを防ぐ。
これらの制御機能は、Vonageのプラットフォームレベルのレート制限および不正防止システムとは別のものであり、それらを補完するものです。Vonageに組み込まれた不正防止機能の詳細については、 不正防止システムのガイド.
登録と本人確認の分離
一般的なアプリケーションにおいて、電話番号による本人確認が行われるのは、主に以下の2つの場面です:
- 参加登録 (2FA用の電話番号の登録):ユーザーが自分のアカウントに新しい電話番号を追加しています。これは通常、1回のみ行われ、認証済みユーザーのAccountレコードに関連付けられる必要があります。
- 検証 (ログイン時の2段階認証):ユーザーは、登録時に登録した電話番号を現在も所有していることを証明しています。
これら2つのフローには、それぞれ異なるセキュリティ対策が必要です:
その間 登録、登録しようとしている電話番号がすでに別のアカウントに関連付けられていないことを確認し、その番号を追加する前にユーザーが認証されていることを確認してください。
その間 ログイン時の認証、Verify APIのリクエストで使用する電話番号が、データベースにそのユーザーとして登録されている番号と一致していることを確認してください。ログイン時にクライアントから提供された番号を使用してはなりません。ユーザーの電話番号を知っている攻撃者が、そのユーザーに代わって認証リクエストを発行できないようにする必要があります。
サイレント認証に関する追加ガイダンス
「サイレント認証」では、特定の多段階リダイレクトフローが導入されており、その流れでは check_url ユーザーのモバイル端末は、通信事業者のネットワークを介してこれに従わなければなりません。さらに、以下の点に注意が必要です:
- を保存する
request_id直ちに 呼び出した後/v2/verifyそして、クライアントに応答を返す前に、[ ] に記載されているように、それをユーザーセッションに関連付けます。 を保存するrequest_idサーバーサイドで処理し、ユーザーセッションに紐付ける. - ~を通り過ぎないでください
request_idクライアントへ 「サイレント認証のリダイレクトフロー」に厳密に必要な場合を除き。どうしてもこれを渡す必要がある場合は(クライアントがcheck_url)、これを短命な単回使用トークンとして扱い、コードチェックの段階でセッションと照合してVerifyしてください。 - リダイレクト時にモバイルデータ通信を強制する:について
check_urlWi-Fiではなく、モバイル通信事業者のネットワーク経由で実行する必要があります。Wi-Fi経由でリクエストが行われた場合、エラーが発生し、通信事業者レベルでのSIM所有証明が失われます。ネイティブモバイルアプリを構築する際は、VonageのAndroidまたはiOS SDKを使用してこれを強制してください。 また、これらのSDKには、組み込みの接続チェック機能、リダイレクト間のタイムアウト管理(最大10回のリダイレクト、各5秒のタイムアウト)、および障害に的確に対応できる型付き例外が用意されています。詳細は サイレント認証ベストプラクティスガイド 実装の詳細については。 - サイレント認証を開始する前に、モバイルデータの接続状態を確認してください: デバイスでモバイルデータ通信が有効になっていない場合は、「サイレント認証」の手順を開始しないでください。その代わりに、ワークフローの次のチャネル(SMS、音声など)に直接進んでください。モバイルデータ通信がない状態でサイレント認証リクエストを開始すると、リクエストは失敗し、フォールバックチャネルに到達するまでに不必要な遅延が生じます。Vonage SDK は
sdk_no_data_connectivity実行中にこの状態が検出された場合の例外 — アプリでは、デフォルトの60秒のタイムアウトを待つのではなく、これをキャッチして直ちに対応する必要があります。 - SDKの例外を処理し、バックエンドのフェイルオーバーを速やかに実行する: サイレント認証フローの途中でSDKが例外をスローした場合(例えば、
sdk_no_data_connectivity,sdk_connection_errorあるいはsdk_redirect_error)、モバイルアプリは直ちにバックエンドに通知する必要があります。その後、バックエンドはPOST /v2/verify/{request_id}/next_workflowエンドポイントを使用して、待機することなく検証を次のチャネルに進めることができます。何もしない場合、プラットフォームは60秒後に自動的にタイムアウトし、次のワークフローに移行します。しかし、バックエンド側からこれを即座にトリガーすることで、ユーザーの待ち時間を最小限に抑え、検証のライフサイクルを管理下に置くことができます。これは、 セッションごとの検証ライフサイクル管理の実装. クライアントによる自己解決に依存してはならない。例外処理は、バックエンド主導の状態遷移をもたらすものでなければならない。 - 結果は常にサーバー側で確認してください: 「サイレント認証」のリダイレクトによるクライアント側からの成功報告を鵜呑みにしないでください。バックエンドは最終的な検証ステータスを受け取り、独自に認証の可否を判断する必要があります。
要約チェックリスト
Verify APIの統合を本番環境に展開する前に、以下の点を確認してください:
- へのすべての呼び出し
POST /v2/verifyはサーバー側でのみ行われます。 - 認証リクエストに記載されている電話番号は、クライアントからの入力ではなく、貴社のデータベースから取得されたものです。
- について
request_idはサーバー側(セッションまたはデータベース)に保存され、クライアントからは一切受け付けられません。 - について
request_id検証を開始した特定のユーザーおよびセッションに紐付けられています。 - 確認待ちの項目は、期限が過ぎると無効となります。
- 検証が完了したか、失敗した場合は、直ちにクリーンアップされます。
- アプリケーションレベルでのレート制限は、電話番号ごと、ユーザーごと、およびIPアドレスごとに設定されています。
- 登録とログインの認証フローは、それぞれ個別のセキュリティチェックを経て、別々に処理されます。
- サイレント認証のリダイレクトフローはモバイルデータ通信を介して実行され、結果はサーバー側で確認されます。