put('oauth_state', $state); $redirect = Socialite::driver('google') ->scopes(['openid', 'profile', 'email']) ->with(['access_type' => 'offline', 'prompt' => 'consent', 'state' => $state]) ->redirect(); // Socialite 在 redirect() 內自己寫了一個 random state 進 session('state')。 // 用我們的 state 覆蓋,確保 user() 的內建驗證與 URL 中的 state 一致。 session()->put('state', $state); return $redirect; } /** * 處理 Google 回調 */ public function handleGoogleCallback(Request $request) { $requestState = $request->input('state'); $sessionState = session()->pull('oauth_state'); if (!$requestState || !$sessionState || !hash_equals($sessionState, $requestState)) { return redirect(config('app.frontend_url') . '/login?error=oauth_failed'); } try { // 獲取 Google 用戶資訊(Socialite 內建 state 驗證此時也會通過) $googleUser = Socialite::driver('google')->user(); // 查找相關的社交帳號 $socialAccount = SocialAccount::where('provider', 'google') ->where('provider_id', $googleUser->getId()) ->first(); if ($socialAccount) { // 已存在社交帳號,直接獲取用戶 $user = $socialAccount->user; // 如果用戶不是會員,拒絕登入 if ($user->role !== 'member') { return response()->json([ 'status' => false, 'message' => '只有會員可以使用 Google 登入' ], 403); } } else { // 檢查是否有相同 email 的用戶 $user = User::where('email', $googleUser->getEmail())->first(); if ($user) { // 已存在用戶,但沒有連結社交帳號 // 檢查是否為會員 if ($user->role !== 'member') { return response()->json([ 'status' => false, 'message' => '只有會員可以使用 Google 登入' ], 403); } } else { // 建立新用戶 $user = User::create([ 'name' => $googleUser->getName(), 'email' => $googleUser->getEmail(), 'password' => Hash::make(Str::random(24)), 'role' => 'member', // 強制為會員角色 'is_active' => true, ]); // 建立會員資料 try { MemberProfile::create([ 'user_id' => $user->id, // 可以選擇性地從 Google 獲取更多資訊 ]); } catch (\Exception $e) { // 記錄錯誤,但不中斷整個登入流程 \Log::error('Google 登入建立會員資料失敗: ' . $e->getMessage()); } } // 建立社交帳號連結 $socialAccountData = [ 'user_id' => $user->id, 'provider' => 'google', 'provider_id' => $googleUser->getId(), 'provider_email' => $googleUser->getEmail(), 'access_token' => $googleUser->token, 'expires_in' => $googleUser->expiresIn ?? null, ]; // 確保如果有 refreshToken 就正確地儲存 if (!empty($googleUser->refreshToken)) { $socialAccountData['refresh_token'] = $googleUser->refreshToken; } $socialAccount = SocialAccount::create($socialAccountData); } // 生成 Sanctum token $token = $user->createToken('google-auth')->plainTextToken; return redirect(config('app.frontend_url') . '/auth/callback#token=' . $token); } catch (\Exception $e) { return redirect(config('app.frontend_url') . '/login?error=oauth_failed'); } } }