SMS を送信して本人確認を行うために、ワンタイム パスワード(OTP)の入力をユーザーに求めるのが一般的です。SMS OTP のユースケースには、次のようなものがあります。
- 2 要素認証。ユーザー名とパスワードに加えて、SMS OTP を使用することで、アカウントが SMS OTP を受信したユーザーによって所有されていることを示す強力なシグナルとして使用できます。
- 電話番号の確認。一部のサービスでは、ユーザーのプライマリ識別子として電話番号が使用されます。このようなサービスでは、ユーザーは電話番号と 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.example.com #123456
OTP メッセージの標準形式により、抽出がより簡単かつ確実になります。OTP コードをウェブサイトに関連付けることで、悪意のあるサイトにコードを提供するようユーザーをだますことが難しくなります。
Precise の形式設定ルール
Precise ルールは次のとおりです。
- メッセージの先頭には、4 ~ 10 文字の英数字(少なくとも 1 つの数字を含む)を含む、対人可読テキスト(省略可)を配置し、最後の行に URL と OTP を配置します。
- API を呼び出したウェブサイトの URL のドメイン部分の前に
@を付ける必要があります。 - URL には
#と、その後に OTP を含める必要があります。文字数は 140 文字以下にする必要があります。
この形式を使用すると、次のようなメリットがあります。
- OTP はドメインにバインドされます。ユーザーが SMS メッセージで指定されたドメイン以外のドメインにいる場合、OTP の候補は表示されません。これにより、フィッシング攻撃やアカウントの不正使用のリスクも軽減されます。
- ブラウザは、不可解で不安定なヒューリスティックに依存することなく、OTP を確実に抽出できるようになります。
ウェブサイトで autocomplete="one-time-code" が使用されている場合、iOS 14 以降の Safari は、これらのルールに従って OTP を提案します。
この 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 に渡されると、ウェブサイトはフォームで OTP を使用するか、サーバーに直接 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);
});
});
}