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

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

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

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 にアクセスできるようにします。transportsms が含まれている場合、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 の使用方法について詳しくは、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)。