9.

Laravel Socialite「InvalidStateException」原因と対処|セッション state 不一致

編集
この記事の要点
  • Laravel Socialite (Google / Facebook / Twitter / GitHub 認証) の InvalidStateException
  • 原因: セッションに保存した state パラメータと、コールバック時の state が不一致
  • 対処1: ->stateless() を付けて state チェックを無効化(モバイル / SPA 向け)
  • 対処2: セッションドライバの設定確認(特に複数サーバ環境)
  • 対処3: コールバック URL のドメインとセッション Cookie の domain 設定の整合性

エラー内容

Laravel\Socialite\Two\InvalidStateException

  No message
  in vendor/laravel/socialite/src/Two/AbstractProvider.php:nnn
  at AbstractProvider->getUser()

Google / Facebook / GitHub 等の OAuth 認証のコールバック処理で発生します。OAuth 2.0 の state パラメータ検証に失敗している状態です。

原因: OAuth state パラメータ

OAuth 2.0 では CSRF 対策として state パラメータを使います:

  1. クライアント → 認可サーバへリダイレクトする際、ランダムな state をセッションに保存し、URL にも付与
  2. 認可サーバ → コールバックでクライアントに戻すとき、同じ state を返す
  3. クライアント → セッションの state と URL の state が一致するか確認 (不一致なら CSRF 攻撃の可能性)

このマッチングが失敗するのが InvalidStateException。原因は:

  • セッションが切れた(コールバックまで時間が空きすぎ)
  • セッション Cookie がコールバック先に届かない(ドメイン違い・SameSite 設定)
  • 複数サーバ環境でセッションが共有されていない(ロードバランサ)
  • ブラウザのプライバシー設定でサードパーティ Cookie ブロック
  • キャッシュされた古いコールバック URL を再送信

対処1: stateless() で state チェックを無効化

API モードや SPA など、セッションを使わない場合は state チェック自体をオフに:

// app/Http/Controllers/Auth/LoginController.php

use Laravel\Socialite\Facades\Socialite;

public function redirectToProvider() {
    return Socialite::driver('google')->stateless()->redirect();
}

public function handleProviderCallback() {
    $user = Socialite::driver('google')->stateless()->user();
    // ... ユーザ作成 or ログイン
}

注意: stateless にすると CSRF 対策が無くなるので、別の方法(JWT, nonce 検証等)で守る必要があります。

対処2: セッションドライバ設定の確認

.env でセッションドライバを確認:

# 単一サーバなら file / database / redis いずれも OK
SESSION_DRIVER=file

# 複数サーバ環境(ロードバランサ配下)では shared なドライバ必須
SESSION_DRIVER=redis
# または
SESSION_DRIVER=database

# Cookie ドメインを正しく
SESSION_DOMAIN=.example.com  # サブドメイン跨ぎなら先頭ドット

# HTTPS 環境では Secure 必須
SESSION_SECURE_COOKIE=true

# OAuth コールバック後に新セッションでなく既存セッションを引き継ぐ
SESSION_LIFETIME=120

対処3: コールバック URL の整合性

Developer Console(Google / Facebook 等)に登録したコールバック URL と、アプリの実 URL が一致しているか:

登録 URL実際の URL結果
https://app.example.com/auth/google/callbackhttps://app.example.com/auth/google/callback
https://app.example.com/auth/google/callbackhttps://www.app.example.com/auth/google/callback× (サブドメイン違い)
https://app.example.com/auth/google/callbackhttp://app.example.com/auth/google/callback× (プロトコル違い)
https://app.example.com/auth/google/callbackhttps://app.example.com/auth/google/callback/×〜△ (末尾スラッシュ)

対処4: SameSite Cookie の調整

外部認可サーバから戻ってくる際、SameSite=Strict だと Cookie が送られず state チェック失敗:

// config/session.php
'same_site' => 'lax',  // 'strict' から 'lax' に
// または 'none' (Secure と組み合わせ必須)

対処5: HTTPS 統一

OAuth は本番では HTTPS が前提。HTTP と HTTPS が混在するとセッション Cookie が共有されずエラー:

// AppServiceProvider.php
use Illuminate\Support\Facades\URL;

public function boot() {
    // 本番では強制 HTTPS
    if (config('app.env') === 'production') {
        URL::forceScheme('https');
    }
}

対処6: 直前の処理ログを確認

public function handleProviderCallback(Request $request) {
    \Log::info('OAuth Callback', [
        'state_url' => $request->state,
        'state_session' => session()->get('state'),
        'has_code' => $request->has('code'),
        'all_query' => $request->query(),
    ]);

    try {
        $user = Socialite::driver('google')->user();
    } catch (\Laravel\Socialite\Two\InvalidStateException $e) {
        \Log::error('InvalidState', ['e' => $e->getMessage()]);
        return redirect('/login')->with('error', 'セッションが切れました');
    }
    // ...
}

原因の切り分けチェックリスト

  1. ログインからコールバックまでの時間を測る(5 分以上空くと session lifetime に引っかかる)
  2. ブラウザのシークレットウィンドウで試す(拡張機能の影響を除外)
  3. HTTP Cookie 設定を SameSite=Lax に変更
  4. セッションドライバを database にして DB の sessions テーブルを目視
  5. 別ブラウザで再試行
  6. HTTPS を本番では必須

関連エラー

  • Symfony\Component\HttpKernel\Exception\HttpException — ルート未定義
  • Client Error: 401 Unauthorized — Developer Console の Client ID / Secret が違う
  • Mismatch redirect_uri — 登録済みコールバック URL と実 URL が違う
  • Tokens not yet received — リダイレクトせずに直接コールバック URL にアクセスした
編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. SQLSTATE[HY000] [1045] Access denied for user 'homestead'@'localhost'
  2. Add [~] to fillable property to allow mass assignment on [App\~].
  3. PHP Parse error: syntax error, unexpected 'class' (T_CLASS), expecting identifier (T_STRING) or variable (T_VARIABLE) or '{' or '$' in ~
  4. Changing columns for table "~" requires Doctrine DBAL; install "doctrine/dbal"
  5. MethodNotAllowedHttpException No message
  6. Class 'Doctrine\DBAL\Driver\PDOMySql\Driver' not found
  7. production.ERROR: No application encryption key has been specified.
  8. Dotenv values containing spaces must be surrounded by quotes.
  9. Laravel \ Socialite \ Two \ InvalidStateException
  10. The page has expired due to inactivity. Please refresh and try again.
  11. Failed to clone https://github.com/symfony/thanks.git via https, ssh protocol
  12. Illegal offset type
  13. Cannot access protected property Illuminate\Http\Request::$...
  14. Emitted value instead of an instance of Error
  15. 画像保存時にInternal Server Error
  16. Failed to authenticate on SMTP server with username ...
  17. PostTooLargeException
  18. Database hosts array is empty.
  19. Invalid request (Unsupported SSL request)
  20. does not comply with psr-4 autoloading standard. Skipping.
  21. MySQLのSTR_TO_DATE関数を使用するとnullが返却される問題