0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WebAuthn / Passkey を使ったログイン実装メモ(Firebase + Cloud Functions)

0
Posted at

目的

パスワードを使わずにログインできる仕組みを、WebAuthn (Passkey) + Firebase + Cloud Functions で実装しました。実装時に詰まったポイントを中心に、全体像をメモとして残します。

参考ドキュメント

WebAuthn/Passkeyを導入するメリット

WebAuthnは、従来のID/パスワードではなく公開鍵暗号方式を用いる認証規格です。

  • フィッシング耐性: ドメイン(RP ID)がブラウザレベルで検証されるため、偽サイトに認証情報を送ることが構造上不可能です
  • パスワードレス: ユーザーは生体認証(Touch ID/Face ID)やデバイスのロック解除だけでログインできます
  • 秘密情報の非保持: サーバー側には「公開鍵」のみを保存し、ログインに必要な「秘密鍵」はユーザー端末の外に出ないため、サーバー流出時のリスクが極めて低いです

登録(Registration)の流れ

Passkeyの登録は大きく分けて以下の4ステップです

1. registerRequest(サーバー側)

役割: 認証のchallenge を発行し、セッションに一時保存します

  • Endpoint: POST /passkey/registerRequest
  • 重要ポイント:
  • challenge: ランダムなバイト列。リプレイ攻撃防止のため、必ずサーバーで検証用に一時保存します
  • rp.id: 実行ドメインと一致させる必要があります(localhostなら localhost
  • residentKey: "required": これを指定することで、ユーザー名の入力なしでログインできる「パスキー」として保存されます

2. ブラウザ側で鍵ペア生成

ブラウザ標準の navigator.credentials.create() を呼び出します

// サーバーから取得したJSON(Base64URL)をUint8Arrayに変換して渡す
const credential = await navigator.credentials.create({
  publicKey: {
    ...options,
    challenge: base64urlToUint8Array(options.challenge),
    user: {
      ...options.user,
      id: base64urlToUint8Array(options.user.id)
    }
  }
});

注意: WebAuthn APIは ArrayBuffer を期待するため、JSONでやり取りする際は Base64URL ↔ Uint8Array の相互変換が必須です

3. registerResponse(サーバー側)

役割: ブラウザから届いた署名(Attestation)を検証し、公開鍵を保存します

  • 検証項目:
  1. 保存していた challenge と一致するか
  2. origin が期待するもの(自社サイト)か
  3. rpId が正しいか
  • 完了処理: 検証成功後、Firestoreのユーザードキュメントに Credential IDPublic Key を紐付けて保存します

ログイン(Authentication)の流れ

登録時と似ていますが、検証対象が異なります

  1. loginRequest: サーバーから challenge を受け取る
  2. ブラウザ: navigator.credentials.get() で署名を生成
  3. loginResponse: サーバー側で、保存済みの「公開鍵」を使って届いた署名を検証する

Cloud Functions (Firebase) でハマったところ

1. ステートレスな環境でのChallenge管理

Cloud Functionsはリクエストごとにセッション維持されないため、メモリ上で challenge を保持できません

解決策: Firestoreに一時セッションを作成する

// passkeySessions/{sessionId} に保存
{
  challenge: "...",
  uid: "...",
  expiresAt: Timestamp // 2〜5分程度の短寿命に設定
}

registerResponse 時には、クライアントから送られた sessionId を元にFirestoreから challenge を引き当てて検証します。

2. ライブラリ選定

WebAuthnのバイナリ検証を自前で行うのは非常に困難です。Node.js環境であれば、以下のライブラリがデファクトスタンダードです

これらを使うと、面倒なバイナリ/Base64変換や署名検証ロジックを大幅に簡略化できます

まとめ

  • WebAuthnの実装は「バイナリデータの扱い」と「サーバー側でのステート管理」が肝
  • Firebase Cloud Functionsを使う場合は、Firestoreをうまく活用してステートレスな制限をカバーする必要がある
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?