Dots

ブログとか

パスキーのデモサイトを作った

サイトのURLは pk.urtell.com


パスキーとは

パスキーは、パスワードの代わりに生体認証やセキュリティキーを使ってログインする仕組みで、WebAuthn という Web 標準技術を使っている。

作ったもの

WebAuthn を使ったパスキー認証のデモサイトを作った。実際にパスキーでの登録・ログインを体験できる。

ほぼClaudeCodeに書かせた。

途中でいろいろ飽きて、テーマを切り替えられるようにしたり、多言語対応にしたりした。

機能一覧

認証機能

ユーザー管理

その他

使用技術

ClaudeCodeに書かせるので、ライブラリの使い方を自分で調べる必要が無いので、あえてRustやSolidJSを使った。

Rustに関しては、結構ライブラリがあっていい感じではあるが、Goと比べるとどうしてもプロダクションビルドは結構時間がかかると思った。

バックエンド

フロントエンド

インフラ

どうせアクセスは少ないので、サーバーは AWS Lambda を使ってサーバーレスにした。 フロントエンドは CloudFront + S3 で静的ホスティング。 NeonDBは無料で使える。

CI/CD

GHA で CI/CD もやってみたけど、遅いし、個人だと無駄にコストがかかることに気づいてやめた。

最初に書いたローカル用デプロイスクリプトのほうが速いし、コストもかからなくて良い。

GHA使わない代わりに、precommit、prepushのhookでformat、lint、自動テスト実行を実行させるようにした。 AIが勝手にやばいコードをcommit, push するのを防ぐ意味でもローカルチェックは有効だと思う。

実装内容

WebAuthn の流れ

登録時:

  1. サーバーが challenge を生成
  2. ブラウザが認証器(生体認証など)を呼び出し
  3. 認証器が公開鍵ペアを生成
  4. 公開鍵をサーバーに送信して保存

ログイン時:

  1. サーバーが challenge を生成
  2. ブラウザが認証器を呼び出し
  3. 認証器が秘密鍵で署名
  4. サーバーが公開鍵で検証

データベース構造

-- ユーザーテーブル
users (
  id, username, display_name, role, created_at, updated_at
)

-- 認証情報テーブル
credentials (
  id, user_id, credential_id, public_key, counter, 
  transports, backup_eligible, backup_state,
  device_name, created_at, last_used_at
)

-- セッションテーブル(登録フロー用)
sessions (
  session_id, user_id, challenge, state, expires_at
)

セキュリティ対策

UI の実装

フロントエンドは、無駄に SolidJS を使い、コンポーネントベースで実装している。というか、AIが殴り書いたコードをコンポーネントに分割させるという流れで実装することが多かった。Tailwind CSS でスタイリングしたが、途中で飽きてきて、いろんなテーマを切り替えられるようにした。

多言語対応は i18n システムを自前実装。ロケールファイルを動的にインポートしている。

まとめ

WebAuthn を使ったパスキー認証は、実装してみると意外とシンプルだった。ブラウザと認証器がほとんどの処理を担当してくれるため、サーバー側は公開鍵の保存と検証だけである。

パスワード認証に比べて、フィッシング耐性があり、ユーザーも覚える必要がないため、今後普及していくと思われる。