BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

今度は「WebOTP」についてFrontend Weekly LT(社内勉強会)でお話しました

はじめましての人ははじめまして、こんにちは!フロントエンドエンジニアのがっちゃん( @gatchan0807 )です。

今回は社内勉強会 Frontend Weekly LT にて、WebOTP / OTPの概要と使い方について発表をしたので、その内容を皆さまにも共有できればと思って記事にしました。 (以前、同じように社内勉強会での発表内容が記事化された 「Frontend Weekly LT(社内勉強会)で「Vite」について LT しました」の記事もぜひどうぞ)

元々、BASEのどこかに使えないかなぁと思って個人的に調べていた内容を社内共有用にまとめたものなので一部 web.dev の記事をなぞっただけの部分もあるのですが、その内容と共にBASEでのOTPの使い方やWebOTPの利用状況についてのシェアしていければと思います。

イントロ

Chrome 93から新機能が追加され、Android + Chromeユーザーであればさらに便利にOTPを利用できるようになりました。 今回の発表ではそれも含めて皆さまにWebOTPの機能について知ってもらい、世のOTPを使った電話番号認証がもっと便利になると嬉しいなと思っています!

Chrome 93でのWebOTPの変更点の詳細は公式で出ている「WebOTP APIを使ったスムーズなPCでの電話番号認証」についてのChrome Developerの紹介記事 をどうぞ。

ざっくりまとめると、 Androidスマホで受け取ったSMSからブラウザ経由でPCでもOTPを自動入力できるようになった! という変更が入りました。

今回はそれらの変更にも触れつつ、OTP / WebOTPの基礎知識から紹介していこうと思います。

目次

  • はじめに…
  • そもそもOTPは何なのか?
  • WebOTP APIって何?
  • どうやって使う?
  • どこでつかえる?

はじめに…

OTP、WebOTPについての一般論や使い方に関しては主に こちらのweb.devの記事 を参考に、一部自分の知見や社内での利用箇所に関しての情報を整理してまとめています。

そもそもOTPとは何なのか?

OTP(one-time password / ワンタイムパスワード)とは、 Norton先生の記事 によると

ワンタイムパスワードを直訳すると「一度きりのパスワード」です。一定時間ごとに発行され、文字通り一度きりしか使えないパスワード、およびそれを採用した認証の仕組みのことです。 各種サービスへのログインだけでなく、口座からの送金など慎重さが求められる操作する際に、第三者によるアクセスを防ぐ手法として主に金融機関などで普及が進んでいます。

となっており、一度だけ利用するパスワードとそれを使った認証の仕組みの総称です。

ひとくちにOTPといってもいくつか種類があり、今回言及するのはOTPの中でも 指定の電話番号にSMSを使ってOTPを送信するSMS OTPについて で、銀行アプリやGoogle Authenticatorなどの専用のアプリがOTPを発行する仕組みなどは対象外です。

このSMS OTPは電話番号と紐付けた形でユーザーの認証や確認のために利用されていて、主だったところで言うと

  • メールアドレスの代わりのユーザー識別子として電話番号を1ユーザー ≒ 1アカウントとして認識する材料として利用する。
  • 通常のパスワードと別にサインイン時に要求し、二段階認証を行うことでセキュリティをより高める方法として利用する。
  • 大きい金額が移動する決済時に、本当に購入してよいか?を確認する方法として利用する。

のようなユースケースがあります。

BASEのなかでは、ショップに紐づく電話番号が「実際に存在」して、「ショップ運営者が管理している」電話番号であるかを検証する方法 としてSMS OTPが使われています。 (それが確認できた場合、ショップの特商法ページに電話番号が認証済みであることを表示し、より安心できるショップであることを表す方法として利用されています)

PC版でのショップ電話番号認証のUI SP版でのショップ電話番号認証のUI
PC版でのショップ電話番号認証のUI
SP版でのショップ電話番号認証のUI

WebOTP APIって何?

利用ユースケースがいくつかありBASEを含む様々なWebサービスで使われているSMS OTPですが、下記のような課題があるため、それらを改善する方法が以前より求められてきていました。

  1. 利用者からするとアプリやデバイスを横断して入力するのでめんどくさい
  2. フィッシング詐欺対策として効かない場合がある
  3. 解約した電話番号を再配布することもあるため、別のユーザーに対してSMS OTPが届いてしまうことがありうる

1. 利用者からするとアプリやデバイスを横断して入力するのでめんどくさい

利用者側からすると、ブラウザとSMSを受け取るアプリを移動してSMSで届いたパスワードを一時的に記憶した上で手で入力する必要 があり、安全のためとはいえ煩わしい作業となってしまっています。 さらにPCでOTPが必要な作業を行う場合、デバイスまでもを横断して作業を進める必要があるので、非常に煩わしい作業になりがちでした。

2. フィッシング詐欺対策として効かない場合がある

セキュリティ保護のために利用されていることもあるOTPですが、ID/PassだけでなくOTPも同時に盗み取る手法を取られてしまうと セキュリティ強化のためのOTPが意味をなさなくなってしまう ため、問題になることもありました。

参照: IPA オンライン本人認証方式の実態調査報告書

3. 解約した電話番号を再配布することもあるため、別のユーザーに対してSMS OTPが届いてしまうことがありうる

ユーザーが解約した電話番号を一定期間経過後に別のユーザーに対してキャリアから提供されることもあるため、誤って別のユーザーのOTPが届いてしまうリスクをSMS OTPを利用する場合、どうしても孕んでしまっています。

(※これに関しては、SMS OTPを利用している以上取り除くことが難しい問題のため、専用アプリを使ったOTPや別の手法を取ることが推奨されています)


これらの課題の解決方法の一つとして、WebOTP APIが提供されるようになりました。

現時点では、Android Chromeと一部のモバイル用ブラウザでのみ 提供されているWebOTP APIという機能で、ブラウザがSMSアプリとのやり取りを自動的にブリッジし、 任意のプログラムからSMSに届いたOTPを取得してユーザーの手をわずらわせることなく、自動的に入力までを完了する ことができるようにして、より便利なWebアプリケーションのUXを実現できるようにしています。

iOS / Safari でも実装方法は違いますが、同じような自動入力機構が提供されています。

どうやって使う?

対象のブラウザでWebOTP APIを利用する方法としては3つの対応を行うだけで利用でき、比較的シンプルに利用可能なAPIとなっています。

  1. <input> タグに autocomplete 属性を付与する(WebOTP APIの利用に必須ではないが推奨)
  2. JavaScriptで navigator 配下にある credentials オブジェクトからotp オプション付きでデータを取得する
  3. サービスから送信されるSMSに特定のフォーマットで記述したOTPを追記する

1. <input> タグに autocomplete 属性を付与する(WebOTP APIの利用に必須ではないが推奨)

iOS Safari(ver14以上)の場合は <input> タグに対して autocomplete="one-time-code" を指定することで、自動でそのフォームがOTPの受け取り口であることを認識し、SMSで受け取ったOTPを自動ペーストする作業をレコメンドしてくれる機構があるため、まずその対応をしておきましょう。

web.devでも、iOS / Android問わず類似のUXを提供し、互換性のために強く推奨する実装として紹介されています。

<form>
  <input autocomplete="one-time-code" required/>
  <input type="submit">
</form>

2. JavaScriptで navigator 配下にある credentials オブジェクトからotp オプション付きでデータを取得する

コードとしてはシンプルで、① OTPCredential が使えるかの確認と ② navigator.credentials.getotp オプションを指定して取得するだけです。

OTPCredential が使えるかの確認

PWAやProject Fugu系のAPIを使う場合にはおなじみの if ('XXX' in window) でチェックをし、存在しない場合はWebOTP関連のコードを実行しないようにします。

if ('OTPCredential' in window) {
    // 以下にWebOTPを使うコードを書く
}

navigator.credentials.getotp オプションを指定して取得する

WebOTPでは、実験的機能ではあるもののIE以外の多くのブラウザで実装されているCredentialsContainerというAPIが拡張されて機能が実現されており、それを使ってSMSからOTPを取得します。

CredentialsContainer自体は認証に関連するメソッド群をPromiseベースのAPIとして提供するインターフェイスで、 Credential Management APIの一部です。 このAPIは基本的にブラウザのパスワード管理システムと対話するために利用しますが、今回のテーマからは外れるので割愛します。

実装自体は下記のように navigator.credentials.getotp: { transport: ['sms'] } オプションを指定して取得し、Promiseが解決した時に受け取るオブジェクト内から code を取得して利用する形です。

より詳しいOTP取得までのプロセスとPromiseで受け取れるオブジェクトの中身などはこちらの解説をご確認ください。

navigator.credentials.get({
  otp: { transport:['sms'] },
  signal: ac.signal
}).then(otp => {
  input.value = otp.code;
  if (form) form.submit();
}).catch(err => {
  console.log(err);
});

web.devより引用したコードの全体

if ('OTPCredential' in window) {
  window.addEventListener('DOMContentLoaded', e => {
    const input = document.querySelector('input[autocomplete="one-time-code"]');
    if (!input) return;
    const ac = new AbortController();
    const form = input.closest('form');
    if (form) {
      form.addEventListener('submit', e => {
        ac.abort();
      });
    }
    navigator.credentials.get({
      otp: { transport:['sms'] },
      signal: ac.signal
    }).then(otp => {
      input.value = otp.code;
      if (form) form.submit();
    }).catch(err => {
      console.log(err);
    });
  });
}

3. サービスから送信されるSMSに特定のフォーマットで記述したOTPを追記する

HTML / JavaScript側の修正は以上で、最後にサービス側から送信しているSMSに手を加えることでWebOTPを使ったSMS OTPの自動入力を実現できます。

この対応をしていないSMSだと、SMSで送信されたテキストの中のどこにOTPのコードが記載されているのかが把握できず、また、どのサービス用のOTPなのかも判定ができないため、 SMS OTPの最終行に特定のフォーマットで紐付けるドメインとOTPを記入して送信する ことでプログラムからOTPの内容と対象サービスが判定できるようにフォーマットを定義しています。

そのフォーマットというのは以下のような形で、

あなたの認証コードは 123456 です。
こちらの認証コードをご入力ください。

@www.example.com #123456

SMSの最終行に @ で始まる形でドメインを指定し、半角スペースで区切った後に # から始まるOTPを記載する というものです。(フォーマットの細かいNG事項はこちらの一覧をご確認ください)

このフォーマットはW3Cで定義されており、origin-bound-one-time-code-message (直訳すると、オリジン(ドメイン)に紐付けられたワンタイムコード付きメッセージ)という名前でiOS / Android共にこのフォーマットを前提にSMS OTPの実装をしています。

どこで使える?

WebOTP APIに対応しているブラウザは前述の通り、Android Chromeと一部のモバイルブラウザのみではあるのですが、iOS Safariでも別の実装方法input タグの autocomplete属性として one-time-code を指定する方法)を取ることが可能です。

そして前述の通り、すでにWebOTPに対応しているサービスであれば、 ユーザー側の環境がPC側はChrome、SMSを受け取るスマホ側はAndroid Chromeで双方に同じアカウントでログインしている場合に受け取ったOTPをデバイスを越えて自動入力が行えるようになったアップデートが入り、これまで以上にSMS OTP入力の手間が減るようになっています。

まとめ

これらのWebOTP / autocomplete="one-time-code" を使ったオーナーさんの手間を減らす細かい改善対応をこれから行っていければ良いなぁ〜と、調べながらに思いました。

また、今回Chrome 93で追加されたWebOTPのアップデートによってデバイスをも越えた連携ができるようになりましたし、(iOSが強い日本国内だと大手を振って使っていきにくい & 特定ベンダーに依存したコードを増やし過ぎてしまうのも問題の火種になりかねないという課題はあるものの、)Googleの世界で揃えたときのWebアプリケーションでできることが増えるのは見てて楽しいものがあるなぁ〜という感想も持ちました。

これからも、ちょくちょくブラウザ関連でWebでできることが広がるアップデートがあればシェアしていければなと思います! もし、「この部分間違ってるよ」や「この方が伝わりやすいよ」等あればTwitter等にご連絡いただけますと幸いです!

参照