パスワードリセット実装の落とし穴は画面側にある——打ち間違いが無言の行き止まりになる
パスワードリセットの実装で見落とされやすいのは、トークンの作り方ではなく入力フォームの側です。安全のために「そのメールアドレスが登録されているかどうか」を答えない設計にすると、利用者が1文字打ち間違えても画面は「送信しました」と表示します。届かないメールを待つだけの行き止まりが生まれ、しかもそれはエラーとしてログに残りません。この記事では、自社アプリ「先勝」でメールアドレスを訊く画面3枚を揃えるまでに2回取り残した実記録をもとに、フォーム側で必要になる設計判断を公開します。

結論:落とし穴は「エラーを出さない設計」の副作用に集中する
パスワードリセット(パスワード再設定)とは、パスワードを知らない状態のまま本人であることを確かめ、新しいパスワードを設定させる仕組みです。通常は、登録済みのメールアドレス宛に期限つきのリンクを送り、そのリンクを踏めた人を本人とみなします。
実装記事の多くは、この「リンク」の作り方を扱います。トークンの生成、有効期限、使い捨てにする方法。どれも必要です。
ただ、実際に運用してみると、利用者が詰まる場所はもっと手前にありました。メールアドレスを打ち込む欄そのものです。
なぜ打ち間違いが「行き止まり」になるのか
ここで効いてくるのが、ユーザー列挙(user enumeration = そのメールアドレスが登録済みかどうかを、外部から見分けられてしまうこと)への対策です。
「このアドレスは登録されていません」と親切に表示すると、攻撃者はアドレスを大量に入れて会員名簿を作れてしまいます。ですから、登録済みでも未登録でも同じ画面を返すのが定石になっています。
Webアプリのセキュリティ指針を公開している非営利団体 OWASP は、この点をこう書いています。「登録済みのアカウントと存在しないアカウントの両方に対して、一貫した同じメッセージを返すべきである」(出典: OWASP Forgot Password Cheat Sheet(新しいタブで開く))。応答時間まで揃えるべきだ、とも書かれています。
私たちが使っている Supabase の認証機能も同じ思想です。resetPasswordForEmail は、そのアドレスが登録済みかどうかにかかわらず同じ結果を返します(公式リファレンス(新しいタブで開く))。
これは実装のバグではありません。安全側に倒した設計が、そのまま利用者の行き止まりになっているという構造です。私たちは自動化の運用でも、異常終了より「正常に見える失敗」の方が長く放置されることを繰り返し経験してきました(AI自動化は「止まる」より「黙って続く」方が怖い)。認証画面でも同じことが起きていた、というだけの話です。
手当ては3つある。私たちが選んだのは「打たせない」だった
副作用を薄める方法は、大きく3つあります。それぞれ効き目と副作用が違うので、表にして比べました。
| 手当て | 何が良くなるか | 副作用・制約 | 私たちの判断 |
|---|---|---|---|
| 送信完了画面に宛先を表示する | 打ち間違いを利用者が自分で気づける | 宛先の渡し方によっては、メールアドレスがURL・閲覧履歴・リファラに残る | 見送り(下で詳述) |
| 入力欄にこの端末が覚えている住所を復元する | そもそも打つ回数が減る=打ち間違いが起きない | 共用端末では前の人の住所が映る恐れがある。オプトアウト(覚えない選択)が必須 | 採用 |
| 完了画面から「別のアドレスで送り直す」導線を出す | 詰まった人が自力で戻れる | 行き止まりを短くするだけで、原因は消えない | 併用(既存の導線で対応) |
真ん中を選んだ理由は単純で、打たなければ打ち間違えないからです。表示を工夫して気づかせるより、入力そのものを減らす方が確実でした。
もう一つ、再設定画面には固有の事情があります。この画面に来る人は、定義上「忘れて来た人」です。直前の画面に自分のメールアドレスが表示されていたとしても、画面が変わった途端に打ち直しを求められていました。いちばん打ち間違えやすい人に、いちばん多く打たせていたことになります。
「メールアドレスを訊く画面」は、何枚ありますか
ここからが、私たちが2回続けて踏んだ穴です。

1回目(2026-08-22):新規登録の画面に「メールアドレスはこの端末が記憶します」と書いてあるのに、その欄は共通部品を通っておらず、一度も記憶していませんでした。本番で対照実験をして同定しています。新規登録の画面で入力しても端末側の保存領域は変化せず、同じ手順をログイン画面でやると値が入りました。仕組みは生きていて、登録画面だけ配線されていなかったのです。ここで「ログインと新規登録の2枚が揃った」と見なして閉じました。
2回目(2026-09-08):別件で本番のHTMLを数え直していて、再設定画面の入力欄だけが素のままだと分かりました。本番HTMLで実測した差は次のとおりです。
- ログイン画面の入力欄 …
name="email" value=""(共通部品=覚える・復元する) - 再設定画面の入力欄 …
name="email"(value 属性なし=素の入力欄)
それ以外の属性(type・required・autocomplete・ラベルの結び付け)は一字一句同一でした。違いは「この端末が覚えているかどうか」だけです。2枚そろった日から17日間、3枚目は誰の視界にも入っていませんでした。
修正そのものは3ファイル・21行の追加と10行の削除で、新しい部品も新しい文言も作っていません。既に本番で動いている自社部品を1つ差し替えただけです。難しかったのは直し方ではなく、取り残しに気づくことでした。
却下した案:送信完了画面に宛先を出す
表で「見送り」とした案の理由を書いておきます。良さそうに見えて、条件次第で逆効果になるからです。
私たちの実装では、送信処理のあと ?sent=1 のような形で完了画面へリダイレクトしています。ここに宛先を渡そうとすると、メールアドレスがURLに乗ります。URLに乗ったものは、ブラウザの履歴に残り、そのページから外部リンクを踏めばリファラ(どのページから来たかの情報)として外へ出る可能性もあります。
共用端末を想定すると、これは打ち間違い対策として得たものより失うものが大きい判断でした。
代案は残してあります。サーバーからURLで渡すのではなく、その端末が覚えている住所とブラウザ側で突き合わせて表示する形です。これなら宛先はその端末から出ません。ただし、覚えていない端末では依然として打ち間違えられるので、現時点で行き止まりは半分しか塞げていません。この点は未完了として記録しています。
自分の再設定フローを点検する4手順
同じ穴を確かめるだけなら、10分ほどで終わります。ブラウザとターミナルがあれば足ります。
- 兄弟を数える — メールアドレスを訊く画面をすべて挙げる(ログイン/新規登録/再設定/招待/メール変更など)。多くの場合、思っていたより1枚多い
- 本番のHTMLで突き合わせる — 各画面の入力欄の属性(value・autocomplete・name・required)を並べて比べる。ローカルではなく本番の出力で見る
- 画面が書いている「約束」を実測する — 「記憶します」「自動で入力されます」と断言している文があれば、実際にその通りになるか対照実験する。効いている画面を対照群にすると、推測でなく同定できる
- 打ち間違いを1回わざと起こす — 存在しないアドレスで再設定を要求し、利用者から見て何が起きるかを自分の目で確認する
再発防止として私たちが取ったのは、検査を1本増やすことではなく、分岐そのものを無くす方向でした。type="email" の入力欄がコードベース内で1箇所にしか存在しない状態にしたので、「4枚目を足したときに配線を忘れる」が起こりにくくなります。注意書きより機械で担保できる形の方が長持ちする、というのは記事118本を走査して数字でも確かめたことです(AIエージェントが指示を守らない理由)。
改修後は、「覚えない」を選んだ端末で復元しない・新たに覚えない・外した瞬間に既存の記憶ごと消えることを機械で検査し、7項目すべて緑になっています。
この記事で扱わないこと(バックエンド側の定石)
意図的に書かなかった範囲を明示しておきます。どれも重要ですが、フォーム側をどう直すかという判断を前に進めないため、出典に譲ります。
- トークンの生成方法・有効期限・使い捨て化 … OWASP Forgot Password Cheat Sheet(新しいタブで開く) と認証全般の指針(新しいタブで開く)が詳しい
- パスワードの保存方式・強度ルール … NIST SP 800-63B(新しいタブで開く) が現在の基準
- メールが迷惑メール判定される配信側の話 … 本記事の「届かない」は打ち間違いが原因のケースを指しています
なお、Webアプリの脆弱性全般の入門としては、IPA「安全なウェブサイトの作り方」(新しいタブで開く)が日本語で読める一次資料です。
入力欄の autocomplete 属性の書き方はMDNのリファレンス(新しいタブで開く)にまとまっています。ブラウザ側の自動入力も「打たせない」手当ての一部なので、autocomplete="username" などを正しく付けておくと効きます。
私たちが認証画面をこの粒度で見直せているのは、1つのプロダクトを6日で本番まで出したあと、毎日1件ずつ改善を積む運用に切り替えたからです(Claude Codeと多エージェントで6日間でプロダクトを作った話)。
よくある質問
Q. 「このメールアドレスは登録されていません」と表示してはいけないのですか?
セキュリティ上は表示しないのが定石です。表示すると、アドレスを総当たりで入れることで会員かどうかを判別され、名簿として使われる恐れがあります。ただし、これは絶対の規則ではなく設計判断です。社内システムのように利用者が限定されている場合、利便性を優先して明示する選択もあり得ます。判断の根拠を残しておくことが大切です。
Q. 打ち間違いに気づかせる方法は、本当にないのでしょうか?
「登録の有無を漏らさずに、打ち間違いだけ教える」は原理的に困難です。両者を区別して表示した時点で、登録の有無が漏れるからです。現実的な打ち手は、この記事で書いたように入力回数そのものを減らすことと、完了画面から送り直せる導線を用意することの組み合わせになります。
Q. メールアドレスを端末に覚えさせるのは、プライバシー上問題ありませんか?
共用端末を考えると、無条件に覚えるのは避けるべきです。私たちは「この端末に保持する」というチェックボックスを置き、外している端末では復元も保存もせず、外した瞬間に既存の記憶も消えるようにしています。加えて、新規登録の画面だけは「覚えるが復元はしない」に分けています。前の人の住所が新規登録の欄に映るのを避けるためです。
Q. 再設定を要求されたら、その時点で古いパスワードを無効にすべきですか?
一般には、リンクを踏んで新しいパスワードが設定されるまで、古いパスワードは有効なままにします。要求を受けただけで無効化すると、第三者が他人のアドレスで要求を投げるだけでログインを妨害できてしまうためです。詳しい要件は前掲の OWASP の資料を確認してください。
Q. 認証画面が何枚あるか、機械で数えられますか?
数えられます。私たちは type="email" の入力欄をコードベース全体から検索し、共通部品の1箇所だけに集約されている状態を確認しました。検査を増やすより、そもそも分岐が生まれない構造にする方が腐りにくいというのが今回の結論です。
コメント
#個人開発#認証#UX設計#先勝