綴Photo の勉強ノートに戻る
コードレビュー学習2026.05.11

ログイン方法を1つ消すのに、6レイヤー触ることになった — email/password を閉じて OAuth only にした作業ログ

セキュリティ認証DjangoOAuthallauth

公開ローンチ前マスト最後の1ピースとして、email/password の signup / login 動線を閉鎖した話です。閉じる「判断」は一瞬だったのに、実際に消すと設定からテストまで6レイヤーに散っていて、そっちが本題になりました。

この記事でわかること

  • 1つの認証方式が settings / URL / view / serializer / tests / frontend の6レイヤーに散っていること
  • URL だけ消すと dead code・赤いテスト・設定乖離が残る ── 全レイヤー揃えて消す筋
  • (動機)なぜ「OAuth only」にする判断をしたのか=攻撃面の縮小
  • allauth の ACCOUNT_LOGIN_METHODS = set() で起きる W001 警告と silence の理由付け

なぜ閉じると決めたか(動機: 攻撃面の縮小)

認証方式を絞るのは、純粋にメリットが大きい:

残す理由 閉じる理由
パスワード好きなユーザ向け パスワードリスト攻撃の入口
ID/メアド忘れた時の救済 brute force の対象
OAuth provider 障害時の代替 パスワード漏洩リスク

「親しい人と限定共有」 用途で、ユーザは Google / Apple アカウントを既に持っている前提なら:

  • email/password を残すコスト: brute force / credential stuffing / パスワード漏洩の対応
  • 失うもの: ほぼ無し(OAuth で代替可能)

→ 閉鎖が筋。「公開前必須セキュリティ」として優先度が高い。

ここまでが「閉じると決めた」理由で、判断自体は一瞬だった。問題はこの先 ── いざ消そうとすると、email/password 認証が想像以上に多くのレイヤーに根を張っていた。ここからが本題。

何を消すか

Django + allauth + dj-rest-auth + 自前 view が混在しているプロジェクトでは、消す対象が複数のレイヤーに散らばります。

email/password の足あとは Git のソースファイル。DB には無い。settingsconfig/settings/base.pyLOGIN_METHODS を空に起動時にメモリへ読むURLsaccounts/urls.pysignup/ login/ を削除app 側の口も同じファイルviewsaccounts/views.pySignupView などを削除LoginView も同じファイルserializersaccounts/serializers.pySignupSerializer を削除ファイルは空で残すtestsaccounts/tests.pysignup_url を捨てて直す残すテストは口を通さないfrontendfrontend/srclive に UI 残骸なし_archive にだけ残る

「URL だけ消す」と、隣のファイルが食い違います。

1ファイルだけ直すと隣が食い違う。全部 Git のソース。DB には無い。urls.py だけ消す口だけ塞いだ状態views.py が残るtests.py は 404 で赤別 URL から view を再 expose できるviews.py だけ消す中身だけ無くした状態urls.py が残る口の配線だけ残るimport エラーで落ちるbase.py だけ変えるLOGIN_METHODS を空にurls.py が残るsignup/ の口は残る設定と動作が乖離する

→ 全レイヤーまとめて整理するほうが、後で見たときに「これ何で残ってる?」がありません。以下、ファイルごとに何を消したか。

レイヤー1: settings

allauth の標準的な signup / login を無効化する:

python
# base.py
# 2026-05-11: OAuth only 化(Google / Apple)に伴い、email/password signup・login を全面閉鎖。
#   ACCOUNT_LOGIN_METHODS を空 set にして allauth 標準のユーザー名/メールログインを無効化。
#   ACCOUNT_SIGNUP_FIELDS も空にして signup フォーム自体の組み立てを止める。
#   OAuth 経由のユーザー作成は SOCIALACCOUNT_* + accounts.adapters.CustomSocialAccountAdapter で完結する。
ACCOUNT_LOGIN_METHODS = set()   # ← ログイン方法を空っぽに=「メール/パスワードでは入れない」状態にする行(=出入口を1つ減らす)
ACCOUNT_SIGNUP_FIELDS = []
ACCOUNT_EMAIL_VERIFICATION = 'none'
SOCIALACCOUNT_ADAPTER = 'accounts.adapters.CustomSocialAccountAdapter'
# allauth W001 を silence: LOGIN_METHODS / SIGNUP_FIELDS が両方空という構成は
# allauth の前提から外れているが、OAuth only 化の意図的な状態。
# allauth の signup / login view は URL include しておらず、副作用なし。
SILENCED_SYSTEM_CHECKS = ['account.W001']   # ← 「わざとこうしてるよ」とツールに伝えて警告を黙らせる行

ポイント:

  • ACCOUNT_LOGIN_METHODS = set() は allauth の前提から外れてるので account.W001 警告が出ます
  • 警告を黙らせる時は SILENCED_SYSTEM_CHECKS + 理由コメント で「意図的だよ」を明示
  • allauth の URL を include していなければ、警告は実害ゼロ

レイヤー2: URLs

email/password 系の URL を削除:

python
# accounts/urls.py
urlpatterns = [
    # ── 残すもの: OAuth 後の認証ユーティリティ ──
    path("csrf/", views.CsrfView.as_view(), name="csrf"),
    path("logout/", views.LogoutView.as_view(), name="logout"),
    path("me/", views.MeView.as_view(), name="me"),
    path("token/refresh/", views.TokenRefreshCookieView.as_view(), name="token_refresh"),
    path("app/token/refresh/", views.AppTokenRefreshView.as_view(), name="app_token_refresh"),
    path("app/logout/", views.AppLogoutView.as_view(), name="app_logout"),
 
    # ── OAuth ──
    path("google/login/", views.GoogleLoginView.as_view(), name="google_login"),
    # ...
    
    # ── 削除: signup/ / login/ / app/signup/ / app/login/ ──
]

CSRF / logout / me / token refresh は OAuth 後のセッション維持に必要なので残す。

レイヤー3: Views & Serializers

URL から呼ばれなくなった view class、それを支えていた serializer も削除:

  • SignupView / LoginView / AppSignupView / AppLoginView を削除
  • SignupSerializer / LoginSerializer を削除

serializers.py は空ファイル+コメントで残す(将来の OAuth 系 serializer 用の placeholder):

python
# accounts/serializers.py
# 2026-05-11: OAuth only 化(Google / Apple)に伴い、SignupSerializer / LoginSerializer は削除。
# OAuth 経由のユーザー作成は SOCIALACCOUNT_ADAPTER(accounts.adapters.CustomSocialAccountAdapter)で完結する。
# このファイルは accounts アプリの serializers モジュールとして残置(将来 OAuth 系の専用 serializer を追加する余地)。

レイヤー4: Tests

これが地味に手間。

signup_url / login_url を setup に使っていたテストは、URL が消えたので全部赤くなります:

python
# 旧
signup_response = client.post(self.signup_url, payload, ...)  # → 404 になる

直す方針は 2 つ:

  • A: signup / login を経由していたテストを 削除
  • B: setup を User.objects.create_user() + RefreshToken.for_user() 直接生成に書き換え

「OAuth 後にトークンが発行された後のフロー」を検証するテスト(me / refresh / logout 等)は B でリライト。signup / login 自体を検証するテストは削除。

ログイン口は閉じた。User は DB、JWT はメモリ、Cookie はテストクライアントのメモリ。PostgreSQLaccounts_usercreate_user で直接作る閉じても残る発行メモリJWT 文字列for_user の戻り値保存しないセットテストクライアントcookies のメモリaccess_token保存しない本番の set_token_cookies はレスポンスの httpOnly Cookie に書く。テストはクライアントメモリへ直接入れる。名前だけ本番と同じ。
python
# 新
def setUp(self):
    # OAuth 経由でユーザーが作成された想定(adapter で create される)
    self.user = User.objects.create_user(username="alice", password="pass12345")
 
def test_me_with_bearer_token(self):
    access = str(RefreshToken.for_user(self.user).access_token)
    self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {access}")
    response = self.client.get(self.me_url)
    self.assertEqual(response.status_code, status.HTTP_200_OK)

Cookie 用:

python
def _set_jwt_cookies(client, user, refresh):
    access = str(refresh.access_token)
    client.cookies["access_token"] = access
    client.cookies["refresh_token"] = str(refresh)

これでテスト 8/8 pass。

レイヤー5: Frontend 残骸チェック

signup / login の文字列を frontend live コードから探す:

bash
grep -rn -E "signup|/login" frontend/src/ | grep -v _archive | grep -v node_modules

このプロジェクトでは _archive/ 配下のみ、live tree には UI 残骸なし。

ただし proxy.ts のコメント (// /auth/login に誘導する...) は残っていた。コメントは「動かしてない」ので影響なし。

検証

最後に backend テスト + Django check で確認:

bash
docker compose exec backend python manage.py test accounts
# → Ran 8 tests in 0.794s OK
 
docker compose exec backend python manage.py check
# → System check identified no issues (1 silenced).

「1 silenced」が 意図的に black-out した W001 の分。

「全レイヤー揃える」ことの意味

この種のセキュリティクリーンアップで、1レイヤーだけ直すのはリスクがあります。先の食い違い3段がそれで、全レイヤーを同時に揃えることでコードベースの整合性が取れます。

学びポイント

  • テストの書き換えは「signup を経由するか / create_user + for_user で直接トークン作るか」で方針を分けると速いです
  • Frontend live tree に signup UI 残骸がないかは grep で機械的に確認できます
  • 警告を silence する時は SILENCED_SYSTEM_CHECKS + 理由コメントで意図を残します
  • (動機側の学び)認証方式を絞る = 攻撃面の縮小。残す方式に明確な理由がないなら絞る価値はあります

おわりに

動機だった「攻撃面の縮小」も公開ローンチ前に一巡やる価値はあるが、やってみると判断より消し切るほうが手間だった。


この記事は2026年5月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。