公開ローンチ前マスト最後の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 が混在しているプロジェクトでは、消す対象が複数のレイヤーに散らばります。
「URL だけ消す」と、隣のファイルが食い違います。
→ 全レイヤーまとめて整理するほうが、後で見たときに「これ何で残ってる?」がありません。以下、ファイルごとに何を消したか。
レイヤー1: settings
allauth の標準的な signup / login を無効化する:
# 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 を削除:
# 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):
# 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 が消えたので全部赤くなります:
# 旧
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 自体を検証するテストは削除。
# 新
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 用:
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 コードから探す:
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 で確認:
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月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。