漏れても使えないものだけを持つ、
ログイン機能の見本。
passkey-auth-starter は、パスワード認証を正しく実装し、パスキーを最初から選べるログイン機能の参照実装です。受託開発の土台としてそのまま使えること、そしてプログラムを書き始めたばかりの人が読んで「ログインの裏で何が起きているか」を理解できることを目指して、コードと解説を公開しています。
TypeScript ・ Node.js 22 ・ Fastify ・ SimpleWebAuthn ・ Argon2id ・ SQLite
何ができるか
新しくサービスを作るときに、ログインまわりで最初から入れておきたいものをひとそろい実装しています。
パスワードを、元に戻せない形で持つ
メールアドレスとパスワードで登録・ログインできます。パスワードは Argon2id(総当たりに時間とメモリがかかるハッシュ)で保存し、15 文字以上を求め、大文字・記号などの文字種は強制しません。漏えい済みのパスワードは登録時に拒否します。
パスキーを、最初から選べる
パスキーで登録・ログインでき、ログイン画面のメール欄の自動入力から 1 タップで入れます。サーバーが持つのは公開鍵だけなので、DB が漏れてもそこからはログインできません。アカウント設定でパスキーの追加・名前変更・削除ができます。
ログイン状態を、盗まれにくく覚える
サーバー側セッションと、HttpOnly・SameSite=Lax・本番では Secure を付けた Cookie で状態を覚えます。DB にはセッション ID そのものを保存しません。ログアウトと、すべての端末からのログアウトに対応しています。
パスワードを忘れたときの回復
メールで届く 15 分・1 回限りのリンクでパスワードを再設定します。メールはアカウントを取り戻す「回復」にだけ使い、ログインの手段には使いません。再設定のトークンもハッシュにして保存します。
総当たり・なりすまし要求への備え
IP ごとのレート制限と、連続して失敗したアカウントの一時ロック、Origin ヘッダの検証による CSRF 対策、監査ログを入れています。ログにはパスワードやトークンの生の値を書きません。
パスキーまで自動テストで確かめる
単体・結合テストに加えて、ブラウザを動かす E2E テストで、WebAuthn の仮想認証器を使ったパスキーの登録とログインを自動で試します。GitHub Actions が変更のたびに同じ検査を実行します。
なぜ作ったか
2026 年、KDDI が ISP 事業者向けに提供していたメールシステムへの不正アクセスで、7,616,173 件のパスワードが漏えいしました。個人情報保護委員会は、そのパスワードがシステムに平文のまま保存されていたと公表しています。平文(何も加工していない、そのまま読める形)で持っていれば、盗まれた瞬間にそのままログインに使われます。
出典: KDDI「ISP事業者向けメールシステムへの不正アクセスに関するご報告」(2026年7月6日、7月21日更新) / 個人情報保護委員会 報道発表資料(2026年8月19日)
正しいやり方は、すでに文書になっています。米国国立標準技術研究所(NIST)のデジタル認証の指針 SP 800-63B-4 は、パスワードをソルト付きのハッシュで保存することを求め、パスワードやワンタイムパスワードを「フィッシング耐性なし」と分類しています。足りないのは知識ではなく、そのとおりに作られた、読める見本だと考えました。
「漏れても使えないものだけを持つ」
passkey-auth-starter は、この一文を設計原則にしています。DB にはパスワード・セッション ID・再設定トークンの生の値を保存しません。パスキーなら、サーバーが持つのは公開鍵だけです。どれだけ気をつけても DB は盗まれる可能性がある、という前提で、盗まれた中身をそのまま使ってもログインできないように作っています。
私たちは受託開発の会社です。ログイン機能は、案件のたびに作り直すと最も間違えやすい部分のひとつなので、土台を公開し、発注する側も中身を確かめられるようにしました。委託先や外部サービスを経由した漏えいについては、コラム「自社は無傷でも、顧客情報は漏れる」で整理しています。
5 分で動かす
動くデモの常時公開はしていません。サーバーの費用と、攻撃を受ける面を増やさないためです。代わりに、手元のパソコンの中だけで動かせます。
必要なものは Node.js 22.12 以降と Git です。
git clone https://github.com/h-kawanishi/passkey-auth-starter.git cd passkey-auth-starter npm install cp .env.example .env npm run dev
ターミナルのログ(JSON 形式)に "msg":"Server listening at http://127.0.0.1:3000" と出たら、ブラウザで http://localhost:3000 を開きます(設定の ORIGIN が http://localhost:3000 なので、127.0.0.1 で開くとパスワードの登録やログインも含めて送信がすべて拒否されます。パスキーもドメイン名が必要です)。
- 「新規登録」から、メールアドレスと 15 文字以上のパスワードで登録するか、「パスキーで登録」を押します。
- アカウント設定画面で、パスキーの追加や名前変更、パスワードの変更を試せます。
- ログアウトして、ログイン画面のメール欄をクリックすると、自動入力の候補にパスキーが出ます。
- 「パスワードを忘れた場合」から再設定を依頼すると、開発中はメールを送る代わりに、ターミナルにリンクが表示されます。
本番で使うときの設定(ORIGIN、RP_ID、SESSION_SECRET、メール送信の接続)は、解説の 10 章にまとめています。
解説の目次
仕組みを「なぜそうするのか」から説明した、初心者向けの解説です。考え方の章(01〜05)はコードを読まなくても読めます。発注する側の方は、そこまで読めば十分です。
- 00読む前にこの解説の読み方と約束
- 01ログインとは何か「本人であることを確かめる」をどう機械にやらせるか
- 02パスワードの正しい持ち方平文・暗号化・ハッシュの違い、ソルト、Argon2id
- 03なぜ15文字で記号は不要なのかNIST SP 800-63B-4 の考え方と漏えい一覧の照合
- 04セッションとCookieログイン状態の覚え方、HttpOnly と SameSite
- 05パスキーの仕組み鍵の組、公開鍵だけを持つサーバー、偽サイトで効かない理由
- 06コードを読む: 登録新規登録のルートを上から順に読む
- 07コードを読む: ログインパスワードとパスキーの 2 経路を読む
- 08パスワード忘れと回復使い切りトークン、メールが認証ではなく回復である理由
- 09攻撃と防御攻撃ごとに、どの仕組みのどの行が防いでいるか
- 10自分のサービスに組み込む変えるもの、変えてはいけないもの
※ 原本は GitHub の docs/guide/ にあります。このページの解説は、原本から自動で生成しています。
よくあるご質問
- 自社のサービスに、そのまま使ってよいですか?
- MIT ライセンスなので、商用を含めて自由に使い、改変できます。ただし、本番で使う前に解説の 10 章「自分のサービスに組み込む」にある、変えてよいもの・変えてはいけないものを確認してください。メール送信の接続は、お使いの送信事業者に合わせて実装が必要です。
- SMS や電話番号での認証は入っていますか?
- 入れていません。NIST SP 800-63B-4 が使い方に制限を付けている手段なので、採用していません。メールもログインの手段には使わず、パスワードを忘れたときの回復にだけ使います。
- パスワードに記号や大文字を必須にしないのはなぜですか?
- NIST SP 800-63B-4 が文字種の強制を禁じているためです。強制すると「Password1!」のような予測しやすい形が増えます。代わりに長さ(15 文字以上)と、漏えい済みパスワードとの照合で守ります。理由は解説の 03 章で説明しています。
- これを使えば安全になりますか?
- 攻撃を成功しにくくする仕組みを入れていますが、どんな仕組みにも限界があります。防ぎきれないリスクは、リポジトリの docs/threat-model.md と解説の 09 章に書いています。導入や既存システムの見直しのご相談は、お問い合わせからどうぞ。
ログイン機能の見直しを、
一緒に進めます。
いま動いているシステムのパスワードの持ち方の確認や、パスキーへの対応、新しいサービスへの組み込みのご相談を受けています。