綴Photo の勉強ノートに戻る
開発記録2026.06.18

ソーシャルログインは入口が増えるだけ — 出口は変えなかった話

認証Django綴Photo

綴Photo に Google / Apple のログインを足したときの、設計判断のメモです。勉強のため。

もともとメール + パスワードのログインを作っていたが、2026-05-11 に OAuth のみに切り替えてメール認証は削除した。その切り替えと合わせて「Google でログイン」「Apple でログイン」を足したときの話で、一番効いた理解は、増えるのは入口だけで、出口はいじらないだった。

入口が増えるだけ、出口は変えない

ログインを「本人確認(入口)」と「トークンを発行してブラウザに持たせる(出口)」に分けて考えます。

入口のトークンはリクエストのメモリ。ユーザー行は DB。JWT は Cookie。dj-rest-auth の Cookie は使わない。入口・GoogleOAuth の access_tokenリクエストのメモリ保存しない入口・AppleOAuth の id_tokenリクエストのメモリ保存しないallauthPostgreSQLaccounts_userユーザー行閉じても残るSimpleJWT出口・CookieJWT の access_tokenJWT の refresh_tokenhttpOnly。ここ1つdj-rest-auth の CookieJWT_AUTH_COOKIE 無しライブラリ側の出口使わないJWT は SimpleJWT がメモリで作り、set_token_cookies が Cookie へ載せる。交差であり置き場所ではない。出口は今までと完全に同じでよかった。

ここで一つ決めた。ソーシャルログイン用のライブラリ(dj-rest-auth)にも Cookie をセットする機能もあるが、それは使わず、メール + パスワード時代に作っていた自前の set_token_cookies() に出口を一本化することにした。

python
# 入口(本人確認)は Google でも Apple でも、出口はこの1関数に集約する
class AppleLoginView(SocialLoginView):        # dj-rest-auth: ログイン用の API を生やす土台
    adapter_class = AppleOAuth2Adapter        # allauth: Apple と実際に話す係
    def get_response(self):
        user = self.user
        refresh = RefreshToken.for_user(user)  # SimpleJWT: JWT の実物を作る
        # ↓ ここが「出口」。既存ログインと同じ関数を通す=Cookie 名も有効期限もズレない
        return set_token_cookies(              # 自前: JWT を Cookie に載せる出口
            Response({"detail": "login ok"}),  # DRF: HTTP レスポンスの器
            refresh,
        )

この短いクラスに、4つのライブラリが同居しています。設定ファイルで詰まったとき「どのライブラリの設定を直せばいいのか」が分からなくなるのは、これが1か所に混ざって見えているからで、行ごとに出所を貼ると読めるようになります(それぞれの係は最後にまとめた)。

出口が2系統あると、Cookie の名前や有効期限が微妙にズレて「片方のログインだけ挙動が違う」という静かなバグになります。

Google と Apple は対称じゃない

「ソーシャルログイン」とひとくくりにしていたけど、Google と Apple は手触りがかなり違った。並べてみて初めて分かった差。

Google Apple
受け取るトークン access_token id_token
ローカルで試せる できる(localhost 可) できない(https 必須)
Client ID 1つで Web / アプリ共通 2つ(Web 用と アプリ用で別)
必要なもの Google Cloud(無料) Apple Developer(年99ドル)

特に効いたのは下2つ。Client ID とは「このアプリは綴Photoです」と名乗る名札のこと。Google は名札1枚で Web もアプリも通るが、Apple は Web 用(Service ID)とアプリ用(Bundle ID)で別の名札を要求します。Google の「1個で両方いける」感覚のまま進めると詰まります。

env の置き場所も Google と Apple で変えた。

鍵の実体はファイルと環境変数。DB のテーブルには無い。ローカル .env ファイルGOOGLE_CLIENT_IDApple の鍵は入れないlocalhost で Google だけ試すディスクのファイル本番の環境変数Render / Vercel の設定画面Google も Apple も置くApple は https 必須APPLE_CERTIFICATE_KEY.p8 ファイルApple Developer で一度だけenv へは \n で1行リポには置かないダウンロードしたファイル試せる場所にだけ置く。読むとき \n を改行に戻す。全部の env を全環境に置かない。

もう一つの罠。.p8 の中身は改行を含むので、env には \n でエスケープして1行で入れて、読むときに実際の改行へ戻します。

python
# .p8 は PEM 形式の秘密鍵 = 元々複数行のテキスト。env は改行を持てないので \n に変換して1行で入れ、読むときに戻す
"certificate_key": os.environ.get("APPLE_CERTIFICATE_KEY", "").replace("\\n", "\n"),

誰が何の係か

詰まったとき効いたのは、ライブラリの役割を分けて覚えること。

  • allauth … 外部サービス(Google / Apple)に本人確認を委託する係
  • dj-rest-auth … 本人確認のあとに JWT を返す API を生やす橋渡し係
  • SimpleJWT … JWT そのものを作る・検証する係(メール + パスワードのときから変わらない)
  • DRF(Django REST Framework) … そもそも API を作るための土台。ログイン専用の部品ではなく、Response のような「HTTP で受け答えする道具」を提供している係

紛らわしいのが DRF と dj-rest-auth が別物なところで、名前が似ているせいで「DRF が JWT を生やしてくれる」と読み違えやすいです。生やすのは dj-rest-auth の方で、DRF はその下にある土台にすぎません。

トークンの本体(SimpleJWT)は前から同じ。腑に落ちると設定ファイルのどの行が何のためにあるかが読めるようになった。


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