hogashi.*

日記から何から

ワンタイムパスワード(OTP)のベストプラクティスじゃない入力フォームに出会う

 こんにちは、 id:hogashi です。 masawada Advent Calendar 2022 - Adventar の 2日目です。

OTP 入力フォーム

 なぜか id:masawada さんとたまにワンタイムパスワード (OTP) の話をする印象があります。偶然生成された「ホホンドホド」という文字列*1が TOTP で出そうな見た目じゃん、とか。
 最近もまた微妙に使いづらい入力フォームに出会いました。そこで、世に存在するベストプラクティスとそれに沿わないフォームを見て、ベストたる所以をなんとなく感じてみる回をお送りします。結果的に GitHub がなんかむずい感じになっているという記事になりましたが、もちろん各サービスそれぞれ良いと思ってやっているはずなのであくまで個人の感想です。

まずベストプラクティスを見る

web.dev

 web.dev では、 input タグを:

  • type="text"
  • inputmode="numeric"
  • autocomplete="one-time-code"

にして使うのがよいと書かれています (フォーム以外に、 SMS の内容の書き方や WebOTP API についても書かれているので、一度見てみてください)。
 あと前提として、値を 1つの input だけに入力する想定です。今回の記事ではどちらかというとそっちが重要。

それでは本題です


<input autofocus="" type="text" name="user-code-0" id="user-code-0" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 0" data-next="user-code-1">
<input type="text" name="user-code-1" id="user-code-1" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 1" data-next="user-code-2" data-previous="user-code-0">
<input type="text" name="user-code-2" id="user-code-2" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 2" data-next="user-code-3" data-previous="user-code-1">
<input type="text" name="user-code-3" id="user-code-3" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 3" data-next="user-code-5" data-previous="user-code-2">

<span class="h1">-</span>
<input type="text" name="user-code-4" id="user-code-4" class="d-none" aria-label="User code 4" value="-" readonly="">

<input type="text" name="user-code-5" id="user-code-5" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 5" data-next="user-code-6" data-previous="user-code-3">
<input type="text" name="user-code-6" id="user-code-6" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 6" data-next="user-code-7" data-previous="user-code-5">
<input type="text" name="user-code-7" id="user-code-7" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 7" data-next="user-code-8" data-previous="user-code-6">
<input type="text" name="user-code-8" id="user-code-8" class="form-control js-user-code-field h1" maxlength="1" style="height: 2em; max-width: 1.5em; text-transform: uppercase" aria-label="User code 8" data-previous="user-code-7">

 最近遭遇したのがこれ。 GitHub CLI でログイン (gh auth login) するときに、 Login with a web browser を選ぶと、ブラウザが開いてこの画面になります。 1文字打つと JavaScript でカーソルが右の input に進むタイプなのですが、入力中に input が空のままカーソルが右の input に進んでしまう、ということが起きました。厳しい。

 よく観察すると、 keydown で文字を入力し、 keyup で次の input に進む、という挙動になっています。例えば "AB" と入力するとき、 A キーを押したまま B キーを押す (そしてどっちも離す) と:

# イベント input とカーソル
1 最初 | _ _ _ - _ _ _ _
2 A (keydown) A _ _ _ - _ _ _ _
3 B (keydown) A _ _ _ - _ _ _ _
4 A (keyup) A | _ _ - _ _ _ _
5 B (keyup) A _ | _ - _ _ _ _

……という感じになって、 2つ目の input に B が打てないままカーソルが 3つ目に行ってしまうのでした。ちなみに手順 3 でも A しか入っていないのは maxlength="1" だからですね (1つ目の input に 2文字入ろうとして無視される)。
 あとせっかくコンソールにテキストでワンタイムパスワードが出るのに、コピペで入力できないのもつらい。いつもの input と同じ入力体験になってくれるか、あるいは素朴に全体で 1つの input を使ってくれると嬉しいな〜と思っています。

ちなみに


<input type="text" maxlength="1" autocomplete="none" value="">
<input type="text" maxlength="1" autocomplete="none" value="">
<input type="text" maxlength="1" autocomplete="none" value="">
<input type="text" maxlength="1" autocomplete="none" value="">
<input type="text" maxlength="1" autocomplete="none" value="">

 Steam も↑の GitHub CLI の OTP の入力フォームとかなり近い形 (1桁ごとに input がある) になっているんですが、うまく作ってあるのか、↑のような困ったことは起きず、しかもメールに書かれたワンタイムパスワードをコピペで入力することもできる(!)ので、特に不便を感じたことがないです。とはいえアクセシビリティとかはどうなのか気になる。

ちなみに2


<input
  type="text"
  name="sudo_otp"
  id="totp"
  value=""
  autocomplete="off"
  autofocus="autofocus"
  class="form-control input-block js-verification-code-input-auto-submit mb-2"
  inputmode="numeric"
  pattern="([0-9]{6})|([0-9a-fA-F]{5}-?[0-9a-fA-F]{5})"
  placeholder="XXXXXX">

 GitHub (Web ブラウザで使ってるとき) のフォームは、 HTML 的には大体ベストプラクティスに沿っているのですが、 6桁入力すると (Enter を押す前に) 即座に submit されるようになっています。 Enter の手間はないものの、最後の桁をミスったときは 6桁の入力が最初からになるのがちょっとつらいポイントですね。 submit はユーザのタイミングでやらせてほしい……。

むすび

 僕が個人的に素朴なものを好きなのでバイアスがかかっているかもしれませんが、やはりユーザの体験は損なわないようにしたい、そしてベストプラクティスとされているものは説得力がありそう、という感じでした。あとここまで書いてなんですが、今後は WebAuthn や Passkeys などの OTP の入力が不要な認証方法に移り変わっていくものと思うので、そもそも入力の体験に気を払う必要はなくなっていくかもしれない…… (楽になるのはもちろん歓迎)。それでは良いお年を🎍

*1:string_random[ヘッドホン]{5,6}から "ヘッドンホホ" を出そうとして遊んでた