外部ログイン後も自サービスのsessionは必要
GoogleログインやGitHubログインを使っても、自サービス側のログイン状態管理は必要です。
OIDCで「この人は誰か」を確認した後、自サービスのユーザーに紐づけ、自サービス用のsessionを発行します。
外部IdPのログイン結果を、そのまま毎リクエストのログイン状態として扱うのではありません。
Googleで本人確認
-> 自サービスの users と紐づけ
-> 自サービスの session_id を発行
-> 以後は自サービスのCookieで認証
全体フロー
OIDCログインの大まかな流れです。
1. ユーザーが「Googleでログイン」を押す
2. 自サービスが state / nonce を作る
3. Googleの認可画面へリダイレクトする
4. Googleが callback URL へ code を返す
5. 自サービスが code を token endpoint で交換する
6. ID Token を検証する
7. 自サービスの users と紐づける
8. 自サービスの session_id を発行する
9. Set-Cookie でブラウザへ保存する
ログイン完了後のレスポンス例:
HTTP/1.1 302 Found
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
Location: /dashboard
以後の認証は、Googleではなく自サービスの session_id を見ます。
state の役割
state は、ログイン開始リクエストとcallbackを対応させる値です。CSRF対策として重要です。
login start:
state=random_abc を保存
Googleへ state=random_abc を送る
callback:
state=random_abc が戻る
保存済みstateと一致するか確認
一致しなければ、ログイン処理を中断します。
state は推測できないランダム値にし、短い期限で保存します。保存先は、サーバー側セッション、暗号化Cookie、一時ストアなどです。
nonce の役割
OIDCでは、ID Tokenのすり替え対策として nonce を使います。
login start:
nonce=random_xyz を保存
認可リクエストに nonce を含める
callback:
ID Token内の nonce が random_xyz と一致するか確認
state はリクエスト対応、nonce はID Token対応、と分けて理解します。
| 値 | 主な役割 |
|---|---|
state | callbackが自分で始めたログインの続きか確認 |
nonce | 返ってきたID Tokenがこのログイン用か確認 |
ID Tokenを検証する
ID Tokenを受け取ったら、最低限次を確認します。
- 署名が正しい
issが期待する発行者audが自サービスのclient_idexpが期限内nonceが一致- 必要なら
email_verifiedを確認
ID Tokenの中にメールアドレスが入っているからといって、検証せずに信用してはいけません。
users との紐づけ
外部ログイン後は、自サービス内のユーザーを決めます。
例:
CREATE TABLE user_identities (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
provider TEXT NOT NULL,
provider_subject TEXT NOT NULL,
email TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (provider, provider_subject)
);
provider_subject は、GoogleなどのIdPが返すユーザー識別子です。メールアドレスは変更される可能性があるため、外部IDの主キーとしては sub を使うのが基本です。
provider = google
provider_subject = ID Token の sub
session発行
ユーザーが確定したら、自サービスのsessionを発行します。
session_id=random
sessions[session_id] = {
userId: user_001,
loginMethod: "google",
authLevel: "oidc",
loginAt: now
}
HTTPレスポンス:
Set-Cookie: session_id=...; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=7200
以後は、通常のCookieセッションと同じです。
SameSiteで詰まるところ
OIDCログインでは、外部IdPから自サービスへ戻るcallbackがあります。
accounts.google.com -> app.example.com/auth/callback
これは別サイトからのトップレベル遷移です。SameSite=Lax なら、GET callbackではCookieが送られることがあります。POST callbackやiframe、別サイト埋め込みを使う構成では、SameSite=None; Secure が必要になることもあります。
詰まりやすい点:
- state保存Cookieがcallbackで送られない
- callbackがPOSTでSameSiteに引っかかる
- フロントとAPIが別siteでCookieが送られない
SecureがなくSameSite=Noneが拒否される- callback URLのドメインが想定と違う
OAuth/OIDCの不具合は、認証ロジックだけでなくCookie属性とリダイレクトの組み合わせで起きます。
外部アクセストークンをsessionに載せすぎない
Google APIを呼ぶためのaccess tokenを、自サービスのブラウザsessionへそのまま露出させるのは避けます。
避けたい設計:
/api/session が Google access_token を返す
ブラウザJSが access_token を保持する
望ましい設計:
ブラウザ -> 自サービスBFF
BFF -> Google API
ブラウザには自サービスのHttpOnly Cookieだけを持たせ、外部APIトークンはサーバー側で管理する設計が安全です。
ログアウト時の注意
自サービスからログアウトしても、Googleからログアウトされるとは限りません。
ログアウトで分けるもの:
| 対象 | 何をするか |
|---|---|
| 自サービスsession | 削除する |
| 自サービスCookie | 削除する |
| 外部refresh token | 必要なら失効する |
| Googleアカウント自体 | 通常はログアウトしない |
「自サービスからログアウト」と「IdPからログアウト」は別物です。
まとめ
OAuth/OIDCログイン後は、外部IdPで本人確認をしたうえで、自サービス側のユーザーに紐づけ、自サービスのsessionを発行します。
stateでcallbackの対応を確認するnonceでID Tokenの対応を確認する- ID Tokenを検証する
subを使って外部IDと紐づける- 自サービスの
session_idを発行する - SameSiteやcallback URLの条件を確認する
- 外部access tokenをブラウザへ露出させない
外部ログインを使っても、session設計がなくなるわけではありません。