SMS を送信してユーザーにワンタイム パスワード(OTP)の入力を求め、本人確認を行うことは一般的です。SMS OTP のユースケースには、次のようなものがあります。
- 2 要素認証。ユーザー名とパスワードに加えて、SMS OTP は、SMS OTP を受信したユーザーがアカウントの所有者であることを示す強力なシグナルとして使用できます。
- 電話番号の確認。一部のサービスでは、電話番号がユーザーのプライマリ ID として使用されます。このようなサービスでは、ユーザーは電話番号と SMS で受信した OTP を入力して本人確認を行うことができます。PIN と組み合わせて 2 要素認証を構成することもあります。
- アカウント復元。ユーザーがアカウントにアクセスできなくなった場合は、アカウントを復元する方法が必要です。登録済みのメールアドレスにメールを送信したり、電話番号に SMS OTP を送信したりする方法は、一般的なアカウント復元方法です。
- お支払いの確認: 決済システムでは、セキュリティ上の理由から、一部の銀行やクレジット カード発行会社が支払人に追加の認証を要求します。通常、この目的には SMS OTP が使用されます。
これらのユースケースで SMS OTP フォームを作成するためのベスト プラクティスについては、以下をご覧ください。
チェックリスト
SMS OTP で最適なユーザー エクスペリエンスを提供するには、次の手順を行います。
<input>
要素を次のように使用します。type="text"
inputmode="numeric"
autocomplete="one-time-code"
- OTP SMS メッセージの最終行として
@BOUND_DOMAIN #OTP_CODE
を使用します。 - WebOTP API を使用します。
<input>
要素を使用する
<input>
要素を含むフォームを使用することは、すべてのブラウザで機能するため、最も重要なベスト プラクティスです。この投稿の他の提案が一部のブラウザで機能しない場合でも、ユーザーは OTP を手動で入力して送信できます。
<form action="/verify-otp" method="POST">
<input type="text"
inputmode="numeric"
autocomplete="one-time-code"
pattern="\d{6}"
required>
</form>
入力フィールドでブラウザの機能を最大限に活用するためのアイデアをいくつかご紹介します。
type="text"
通常、OTP は 5 桁または 6 桁の数字であるため、入力フィールドに type="number"
を使用すると、モバイル キーボードが数字のみに切り替わるため、直感的に操作できるかもしれません。ブラウザは入力フィールドが複数の数値のシーケンスではなく、カウント可能な数値であることを想定しているため、予期しない動作が発生する可能性があります。そのため、この方法はおすすめしません。type="number"
を使用すると、入力フィールドの横に上下のボタンが表示されます。これらのボタンを押すと、数値が増減し、先頭のゼロが削除されることがあります。
代わりに type="text"
を使用してください。これでモバイル キーボードが数字のみになるわけではありませんが、inputmode="numeric"
を使用する次のヒントでその処理を行うため、問題ありません。
inputmode="numeric"
inputmode="numeric"
を使用して、モバイル キーボードを数字のみに変更します。
一部のウェブサイトでは、フォーカス時にモバイル キーボードを数字のみ(*
と #
を含む)に切り替えるため、OTP 入力フィールドに type="tel"
を使用しています。このハックは、inputmode="numeric"
が広くサポートされていなかった過去に使用されていました。Firefox が inputmode="numeric"
のサポートを開始したため、意味的に正しくない type="tel"
ハックを使用する必要はありません。
autocomplete="one-time-code"
autocomplete
属性を使用すると、デベロッパーはブラウザが予測入力のサポートを提供するために必要な権限を指定し、フィールドで想定される情報の種類をブラウザに通知できます。
autocomplete="one-time-code"
では、フォームが開いている間にユーザーが SMS メッセージを受信すると、オペレーティング システムが SMS 内の OTP をヒューリスティックに解析し、キーボードがユーザーに入力する OTP を提案します。iOS、iPadOS、macOS の Safari 12 以降でのみ動作しますが、これらのプラットフォームでの SMS OTP のエクスペリエンスを向上させるためのより良い方法であるため、使用することを強くおすすめします。
autocomplete="one-time-code"
が動作中。
autocomplete="one-time-code"
はユーザー エクスペリエンスを向上させますが、SMS メッセージが送信元にバインドされたメッセージ形式に準拠していることを確認することで、さらに改善できます。
オプションの属性
オプションの属性は次のとおりです。
pattern
は、入力された OTP が一致する必要がある形式を指定します。正規表現を使用して一致するパターンを指定します。たとえば、\d{6}
は OTP を 6 桁の文字列に制限します。pattern
の詳細については、JavaScript を使用してより複雑なリアルタイム検証を行うをご覧ください。required
は、ユーザーがフィールドに入力する必要があることを示します。
詳しくは、ログイン フォームのベスト プラクティスをご覧ください。
SMS テキストの書式を設定する
SMS で配信されるオリジン バウンドのワンタイム コードの仕様に沿って、OTP の入力のユーザー エクスペリエンスを向上させます。
基本的な形式ルールは次のとおりです。SMS メッセージの末尾に、受信者のドメインの前に @
を、OTP の前に #
を付けてください。
次に例を示します。
Your OTP is 123456
@web-otp.glitch.me #123456
OTP メッセージの標準形式により、抽出が容易になり、信頼性が向上します。OTP コードをウェブサイトに関連付けることで、悪意のあるサイトにコードを提供してしまう可能性を減らすことができます。
正確な書式設定ルール
Precise ルールは次のとおりです。
- メッセージは、4 ~ 10 文字の英数字文字列(少なくとも 1 つの数字を含む)を含む人間が読めるテキスト(省略可)で始まり、最後の行は URL と OTP に残されます。
- API を呼び出したウェブサイトの URL のドメイン部分の前に
@
を付ける必要があります。 - URL には
#
とその後に続く OTP が含まれている必要があります。文字数は 140 文字以下にする必要があります。
この形式を使用すると、次のようなメリットがあります。
- OTP はドメインにバインドされます。ユーザーが SMS メッセージで指定されたドメイン以外のドメインにいる場合、OTP の候補は表示されません。また、フィッシング攻撃やアカウントの不正使用のリスクも軽減されます。
- ブラウザは、不可解で不安定なヒューリスティックに依存することなく、OTP を確実に抽出できるようになります。
ウェブサイトで autocomplete="one-time-code"
が使用されている場合、iOS 14 以降の Safari は次のルールに従ってワンタイム パスワードを提案します。
この SMS メッセージ形式は、Safari 以外のブラウザにもメリットがあります。Android 版 Chrome、Opera、Vivaldi も、autocomplete="one-time-code"
経由ではありませんが、WebOTP API でオリジン バウンドのワンタイム コード ルールをサポートしています。
WebOTP API を使用する
WebOTP API は、SMS メッセージで受信した OTP へのアクセスを提供します。transport
に sms
が含まれる otp
タイプ(OTPCredential
)で navigator.credentials.get()
を呼び出すと、ウェブサイトは、オリジン バウンドのワンタイム コードに準拠する SMS が配信され、ユーザーによってアクセスが許可されるのを待ちます。OTP が JavaScript に渡されると、ウェブサイトはフォームで使用するか、サーバーに直接 POST できます。
navigator.credentials.get({
otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);
WebOTP API の使用方法について詳しくは、WebOTP API を使用してウェブで電話番号を確認するをご覧ください。または、次のスニペットをコピーして貼り付けます。<form>
で action
と method
の属性を必ず設定してください。
// Feature detection
if ('OTPCredential' in window) {
window.addEventListener('DOMContentLoaded', e => {
const input = document.querySelector('input[autocomplete="one-time-code"]');
if (!input) return;
// Cancel the WebOTP API if the form is submitted manually.
const ac = new AbortController();
const form = input.closest('form');
if (form) {
form.addEventListener('submit', e => {
// Cancel the WebOTP API.
ac.abort();
});
}
// Invoke the WebOTP API
navigator.credentials.get({
otp: { transport:['sms'] },
signal: ac.signal
}).then(otp => {
input.value = otp.code;
// Automatically submit the form when an OTP is obtained.
if (form) form.submit();
}).catch(err => {
console.log(err);
});
});
}