Skip to content

ログイン・認証・パスワード設計

項目内容
Issue#2594
親 Issue#2381 有料記事の体制を整える
関連 Issue#2390 / #2936 / #2974
ステータス設計
最終更新2026-07-28(#2974: 登録成功の IP 単位レート制限)

1. 概要

有料記事 Phase 2 に向け、サイト独自デザインの会員登録・ログイン・パスワードリセット画面を提供する。認証の中核(パスワードハッシュ化、セッション Cookie、パスワードリセットトークン)は WordPress core に全面委譲し、自前で暗号化アルゴリズムやセッション管理を実装しない。

2. パスワード保管・検証方針

2.1 WordPress core への委譲(採用)

処理使用する WordPress 関数保管先
パスワードハッシュ化wp_hash_password( $plaintext )wp_users.user_pass
パスワード検証wp_check_password( $plaintext, $hash )
ログインwp_signon( $credentials, $secure )Cookie(core が設定)
ユーザー作成wp_insert_user( $userdata )wp_users + wp_usermeta
パスワードリセット要求retrieve_password( $user_login )メール + user_activation_key
リセットキー検証check_password_reset_key( $key, $login )
パスワード更新reset_password( $user, $new_pass )wp_users.user_pass

2.2 自前ハッシュ実装を行わない理由

  1. セキュリティ: WordPress core は bcrypt(PHP password_hash)等の現行ベストプラクティスを採用し、バージョンアップでアルゴリズム移行(rehash)も core が担当する。
  2. 互換性: 既存 WP 管理画面・REST API・プラグイン(WooCommerce 等)と同一のユーザー基盤を共有できる。
  3. 監査範囲の縮小: 自前実装すると暗号化・タイミング攻撃対策・ソルト管理が自前責任となり、決済連携と合わせてリスクが増大する。
  4. PCI DSS: カード情報は Stripe Checkout 等に委譲するため、自サイトで扱う機密はパスワードのみ。WP core の実績あるハッシュで十分。

2.3 平文パスワードの取り扱い

ルール理由
平文パスワードを DB・ログ・セッションに保存しない漏洩時の被害最小化
リクエスト処理中のみメモリ上で保持wp_hash_password 呼び出し直後に破棄
HTTPS 必須(本番)通信経路での平文漏洩防止

3. カスタムロール・Capability 設計

3.1 ロール定義

ロール用途付与方法
member一般会員(有料コンテンツ購入者)会員登録完了時に wp_insert_userrole で付与
subscriber不採用(member に統一)

member ロールの Capability(実装時に add_role() で登録):

Capability説明
readログイン済み一般権限(WP 標準)
read_paid_article購入済み有料記事の全文閲覧(カスタム)

3.2 閲覧権限の判定フロー

有料記事(paid_article CPT)の閲覧可否は Capability 単体では決まらない。Service 層で以下を合成判定する。

判定優先順位:

  1. current_user_can( 'edit_post', $post_id ) → 編集者は常に全文
  2. PaidArticlePurchaseRepository::exists( $user_id, $post_id ) → 単号購入済み
  3. MemberSubscriptionRepository::has_active_plan( $user_id, $plan_key ) → サブスク加入(将来)
  4. 上記いずれも該当なし → リードのみ(#2384 推奨)

4. 会員登録フロー

サイト独自の登録フォームから WordPress ユーザーを作成する。手動確認手順は 会員登録 シナリオテスト手順 を参照する。

4.0 通しフロー概要(ユーザー視点)

登録 → 確認メール → メール認証 → ログイン → マイページまでの正常系。登録直後は自動ログインしない。メール未認証でもログイン自体は可能だが、有料記事購入はゲートされる(詳細は 有料記事決済 シナリオテスト手順 へ委譲)。

4.0.1 画面遷移図

会員登録まわりの画面遷移は、正常系と主要なガード分岐を 1 枚で追えるように Mermaid の flowchart で管理する。シーケンス図は処理主体の責務確認に使い、この図はユーザーが表示される画面とリダイレクト先の確認に使う。

補足:

  • 登録成功時は /member-login/?registered=1 へ移動し、確認メール案内を表示する。ここではログイン状態にしない。
  • /member-verify-email/?uid=&token= の成功時(未ログイン)は /member-login/?verified=1 へ移動し、確認完了メッセージを表示する。
  • ログイン中に自分自身の uid 付き確認リンクを開いた場合は検証を実行し、成功時は /member-profile/?verified=1 へ移動する(マイページでのメールアドレス変更後の再確認を想定)。
  • メール未認証ユーザーでも /member-login/ からログインできる。ただし有料記事購入開始時は /member-verify-email/?email_not_verified=1 へ誘導する。
  • ログイン後の redirect_to は同一オリジンのみ許可し、未ログインの購入導線から戻る場合は購入対象記事へ戻す。

4.1 登録 POST シーケンス(実装)

ログイン画面側は registered=1 を受け取り、「ご登録ありがとうございます。確認メールを送信しました…」を表示する(MemberLoginController / MemberAuthCopy)。

4.2 入力バリデーション

項目ルール
メール形式チェック、email_exists() で重複拒否
パスワード最小 8 文字(WP デフォルトに合わせる)、確認入力一致
表示名必須、サニタイズ(sanitize_text_field)、重複禁止(登録・プロフィール更新共通。get_users() で候補取得後に display_name を trim 済み入力と厳密一致(===)で判定。大小文字・全半角の追加正規化はしない。DB 一意制約はないため同時更新時はベストエフォート)
利用規約同意チェック必須

4.3 メール確認

項目方針
トークンメール/URL は平文(ランダム 32 バイト hex)。meta member_email_verify_token には hash_hmac( 'sha256', $token, wp_salt( 'auth' ) ) のみ保存する
有効期限24 時間(meta member_email_verify_expires_at
確認後member_email_verified = 1、トークン meta 削除、購入可能状態に遷移
既存平文ハッシュ保存導入前に発行された未使用平文トークンは互換比較せず無効。ユーザーは確認メール再送で新トークンを取得する
画面 URL/member-verify-email/?uid=&token=
再送同画面のフォーム POST。存在有無・確認済みを漏らさない汎用メッセージ

4.3.1 確認リンク(GET)シーケンス

ログイン画面側は verified=1 を受け取り、「メールアドレスの確認が完了しました。ログインしてご利用ください。」を表示する(MemberLoginController / MemberEmailVerificationCopy::VERIFY_SUCCESS)。

登録情報変更ページ側は verified=1 を受け取り、「メールアドレスの確認が完了しました。」を表示する(MemberProfileController / MemberEmailVerificationCopy::VERIFY_SUCCESS_PROFILE)。マイページでメールアドレスを変更した直後はログイン状態のまま確認メールが届くため、この導線を使う。

4.3.2 確認メール再送(POST)シーケンス

再送成功時はトークンが差し替わるため、古いメール内リンクは無効になる。

4.4 規約同意・再同意フロー

利用規約の改定時に、既存会員へ再同意を求める。同意状態は wp_usermeta に保持する。

項目方針
同意バージョンmember_terms_accepted_version(DB 現行 terms.versionLegalDocumentVersionService::get_current_terms_version() と一致で同意済み。フォールバックは MemberTermsConsentConstants::CURRENT_VERSION
同意日時member_terms_accepted_at(UNIX タイムスタンプ文字列)
初回同意会員登録フォームのチェックボックス送信時に記録
再同意 UI対象ページ上の モーダル(同意必須エリアのみマスク)。購入ブロック時の受け皿として /member-terms-reaccept/ も同一 UI
ゲート単位ページ丸ごとリダイレクトしない。同意必須の本文のみ非表示。同意不要な操作(ログアウト・退会など)は通常どおり利用可
ゲート対象マイページ本体・購入済み一覧・登録情報変更フォーム・有料記事購入

ユーザー動線(全体像)

Phase 1: 非会員 → 会員登録(初回規約同意)

Phase 2: 会員の通常利用(規約改訂前)

Phase 3: 規約改訂 → 再同意

画面一覧(ユーザー視点)

画面URL(例)非会員会員(同意済)会員(再同意必要)
サイト一般/閲覧可閲覧可閲覧可
会員登録/member-register/利用可
ログイン/member-login/利用可利用可利用可
利用規約/terms/閲覧可閲覧可閲覧可
マイページ/member-mypage/ログインへ閲覧可本体マスク+モーダル。ログアウトは通常どおり可
購入済み一覧/member-purchased-articles/ログインへ閲覧可一覧マスク+モーダル
登録情報変更/member-profile/ログインへ利用可登録情報フォームマスク+モーダル。退会は通常どおり可
有料記事購入記事ページ → Checkoutログインへ購入可/member-terms-reaccept/ へ誘導(同一モーダル UI)
規約再同意/member-terms-reaccept/購入ゲート等の受け皿(モーダル)

運用: サイト運営 → 法務文書で利用規約の新版を作成し「現行版にする」。固定ページ /terms/ へ自動同期され、未同意会員には再同意が求められる。初期シード用定数は LegalPagesCopy / MemberTermsConsentConstants::CURRENT_VERSION(DB 空時のフォールバック)。詳細は 法務文書版管理.md

5. ログインフロー

wp-login.php は使用せず、サイト内のログインページから認証する。

5.1 ログイン後のリダイレクト

条件遷移先
redirect_to パラメータあり(同一オリジンのみ)指定 URL
購入フロー中購入対象の有料記事ページ
通常マイページ(購入済み一覧)

redirect_to の検証: wp_validate_redirect() または自前で同一ホストのみ許可(オープンリダイレクト防止)。

6. パスワードリセットフロー

WordPress core のパスワードリセット機構を利用し、UI のみ独自実装する。

  • メール本文のリセット URL(wp-login.php / SiteGuard 改名後のログインパス、クエリ順や wp_lang の有無を問わない)は、会員確認画面 URL(/member-password-reset/?key=&login=)に差し替える。
  • wp-login.php または SiteGuard 改名ログインへ action=rp(もしくは resetpass)で keylogin 付きアクセスがあった場合は、ロールを問わず会員確認画面へリダイレクトする(WordPress 標準のパスワード再設定 UI は公開しない)。

7. CSRF 対策

7.1 WordPress Nonce

フォームnonce アクション名検証関数
会員登録member_registerwp_verify_nonce()
ログインmember_loginwp_verify_nonce()
パスワードリセット要求member_password_reset_requestwp_verify_nonce()
パスワード再設定member_password_reset_confirmwp_verify_nonce()

実装パターン: 管理画面の AdminAjaxNonceVerifier と同様に、フロント向け MemberFormNonceVerifier トレイトを Service/Controller 層で共通化する。

8. ブルートフォース対策・レート制限

WordPress core にはログイン試行回数の標準制限がないため、以下を Transient 方式で行う。

8.1 採用方針: WordPress Transient によるレート制限

対象キー例閾値ロックアウト
IP 単位(ログイン)member_login_fail_ip_{md5(ip)}5 回 / 15 分15 分
ユーザー名単位member_login_fail_user_{md5(login)}5 回 / 15 分15 分
IP 単位(登録失敗)member_register_ip_{md5(ip)}3 回 / 1 時間1 時間
IP 単位(登録成功)member_register_success_ip_{md5(ip)}10 回 / 1 時間1 時間
IP 単位(PW リセット)member_reset_ip_{md5(ip)}3 回 / 1 時間1 時間

失敗カウンタの解除: ログイン成功時は該当の失敗 transient を削除する(正規ユーザーのロックアウト解除)。会員登録では登録成功時に失敗キー(member_register_ip_*)を削除し、成功キー(member_register_success_ip_*)へ record_attempt する。

実装参照:

役割クラス / 定数
共通 UtilMemberRateLimiter
ログインMemberLoginController + MemberLoginConstants
登録MemberRegisterController + MemberRegisterConstants
PW リセットMemberPasswordResetController + MemberPasswordResetConstants
超過時メッセージログイン等: Messages::RATE_LIMIT_EXCEEDED / 会員登録: MemberRegisterCopy::RATE_LIMIT_EXCEEDED

監査テーブル方式の比較・再検討メモは ログイン試行監査テーブル-検討.md を参照。

9. セキュリティチェックリスト

項目対応
HTTPS本番必須
nonce 検証全フォーム POST
パスワード平文非保存core 委譲
ログイン失敗メッセージユーザー存在を漏らさない汎用文言
redirect_to 検証同一オリジンのみ
レート制限Transient(§8.1)
セッション Cookiesecure / httponly は WP 設定に従う

10. 画面一覧(参考)

種別概要
公開画面ログイン・登録・リセット画面
ServiceMemberLoginService
ControllerMemberLoginController

11. 関連ドキュメント