SMS OTP フォームのベスト プラクティス

SMS OTP フォームを最適化してユーザー エクスペリエンスを改善する方法について学びます。

ユーザーの電話番号を確認する一般的な方法として、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 メッセージが送信元宛てのメッセージ形式に準拠していることを確認することで、さらに多くのことができます。

SMS テキストの書式を設定する

SMS 経由で送信される送信元限定のワンタイム コードの仕様に準拠することで、OTP の入力時のユーザー エクスペリエンスを向上させます。

形式のルールは単純です。SMS メッセージの最後に、受信者のドメインに @ を、OTP に # を追加します。

例:

Your OTP is 123456

@web-otp.glitch.me #123456

OTP メッセージに標準形式を使用すると、コードの抽出が容易になり、信頼性が向上します。OTP コードをウェブサイトに関連付けることで、ユーザーが悪意のあるサイトにコードを提供してしまうことを防ぐことができます。

この形式を使用すると、次のようなメリットがあります。

  • OTP はドメインにバインドされます。ユーザーが SMS メッセージで指定されたドメイン以外にいる場合、OTP の候補は表示されません。また、フィッシング攻撃やアカウント乗っ取りのリスクも軽減されます。
  • ブラウザは、謎めいたヒューリスティックや不安定なヒューリスティックに頼ることなく、OTP を確実に抽出できるようになります。

ウェブサイトが autocomplete="one-time-code" を使用する場合、iOS 14 以降を搭載した Safari では、上記のルールに従って OTP が提案されます。

この SMS メッセージ形式は、Safari 以外のブラウザにもメリットがあります。Android 版 Chrome、Opera、Vivaldi も、autocomplete="one-time-code" を介さず、WebOTP API でオリジンにバインドされた 1 回限りのコードルールをサポートしています。

WebOTP API を使用する

WebOTP API は、SMS メッセージで受信した OTP にアクセスできるようにします。otp タイプ(OTPCredential)で navigator.credentials.get()transportsms を含む)を呼び出すと、ウェブサイトは、オリジンにバインドされたワンタイム コードに準拠する SMS が配信され、ユーザーからアクセス権が付与されるまで待機します。OTP が JavaScript に渡されると、ウェブサイトはそれをフォームで使用したり、サーバーに直接 POST したりできます。

navigator.credentials.get({
  otp: {transport:['sms']}
})
.then(otp => input.value = otp.code);
WebOTP API の動作。

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);
    });
  });
}

写真提供: Jason LeungUnsplash)。