サイトのURLは pk.urtell.com


パスキーとは

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

作ったもの

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

ほぼClaudeCodeに書かせた。

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

機能一覧

認証機能

  • パスキーでのユーザー登録
  • パスキーでのログイン
  • 複数のパスキー登録(スマホ、PC、セキュリティキーなど)
  • パスキーの削除

ユーザー管理

  • ユーザー情報の表示
  • パスキー一覧の表示(デバイス名、作成日、最終使用日)
  • 管理者による他ユーザーの管理

その他

  • 6言語対応(日本語、英語、中国語、韓国語、フランス語、スペイン語)
  • 10種類のテーマ
  • レスポンシブ対応

使用技術

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

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

バックエンド

  • Rust + Axum
  • PostgreSQL
  • webauthn-rs ライブラリ

フロントエンド

  • SolidJS
  • TypeScript
  • Tailwind CSS

インフラ

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

  • terraform
  • Docker
  • CloudFront + APIGW + AWS Lambda
  • CloudFront + S3
  • NeonDB(PostgreSQL)

CI/CD

  • ローカルからのデプロイスクリプト
  • GitHub Actions(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
)

セキュリティ対策

  • Rate limiting(1分間に10回まで)
  • 入力値のバリデーション
  • HTMLサニタイゼーション
  • CORS 設定
  • JWT トークンによるセッション管理

UI の実装

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

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

まとめ

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

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