昨天串接好 Line 第三方登入,今天就要來測試看看是否串接正常。
每次測試的時候都會莫名令人心跳加速(誤)
此專案是前後端分離,沒有做前端畫面,所以我是利用瀏覽器進行測試。
使用者會透過瀏覽器來進行 Line 登入操作,所以使用瀏覽器進行第三方登入測試會比較接近實際狀況。
如果串接失敗,我是利用 Postman 模擬前端在串接過程中查找問題。
如果在本地測試,送出第三方登入請求的網址跟 Line 的 callback 網址,建議都使用 http://localhost 而不是 http://127.0.0.1 ,因為這兩個網址對於第三方平台來說是不同的網站。
在前端頁面上,會有一個按鈕,例如「使用 Line 登入」。
這個按鈕的點擊事件應該會導向我們的 redirectToLine() 方法。
(假設你已經設定好 Line 登入的路由)。
當使用者點擊「使用 Line 登入」按鈕時,後端會透過:
Socialite::driver('line')->redirect()
將使用者導向到 Line 的登入頁面。
使用者在 Line 的授權頁面上,輸入 Line 帳號和密碼,並同意授權應用程式讀取他們的資料。
授權成功後,handleLineCallback() 此方法會讓 Line 將使用者重定向你的 callback URL。
而後端會透過:
Socialite::driver('line')->user()
來取得使用者資料。
在 handleLineCallback() 中,伺服器會檢查使用者是否已經存在於資料庫中,如果沒有,則會自動幫他創建新帳號,並發送 JWT Token 來讓使用者登入成功。
完成所有後端處理後,伺服器會將 JWT Token 回傳給前端,使用者就可以完成登入並進入系統。
送出第三方登入請求的網址後,會出現大家都熟悉的 Line 登入頁面:
正常登入授權後,會取得我這個 Line 帳號的資料:
顯示出以上的內容即為測試成功!
如果失敗就會直接出現錯誤頁面或是以上畫面根本沒出現使用者的資料!
就需要一一排查錯誤的地方在哪裡。
進行測試時,登入後一直無法取得使用者的資料。
明明可以正常登入,但資料內容都是 null(超崩潰)
這時候只要出現的東西不是你所想要的,來~深呼吸,再慢慢吐氣。
先檢查一下所有串接設定是否都是正確的!
(再繼續思考)
因為取得的使用者資料是 null
所以可以確認問題出現在這行程式碼:
Socialite::driver('line')->user()
再利用 dd 查看後發現:
Socialite::driver('line')
在這裡就沒有資料進來了XD
GET http://localhost:8000/login/line/callback?code=example_code&state=example_state
使用者確認授權後,會顯示像上面的網址,會隨機生成 code & state 參數,並儲存到 session,後續會再拿出來驗證。
在測試環境中,code 通常是實際從 Line 登入後獲得的授權碼。
手動模擬時可以隨便使用一個假設值來測試回調邏輯,但這不會進行真正的登入。
Line 的 OAuth 2.0 授權碼是短期有效的,通常只能使用一次。
每次測試新的流程時,都需要重新取得有效的 code。
確認後續 $state 值驗證一致,才會給我使用者的資訊!
所以我自己猜測是 $state 值不一致的問題。
我甚至其中還懷疑是不是根本一開始沒有正確產生 $state 值並儲存在 session XD
所以我先了解這行程式碼到底是怎麼操作的過程!
Socialite::driver('line')->redirect()
這行程式碼是會生成一個隨機的 state 參數 -> 存在session -> 再生成 OAuth2 認證 URL 並重定向到Line的登入頁面。
我先了解 SocialiteProviders\Line 這個套件裡面的 class Provider extends AbstractProvider 裡面每一個方法的作用是什麼!
並且確認Socialite::driver('line')->redirect()
這行是否呼叫 class Provider extends AbstractProvider 裡面的 getAuthUrl()
方法。
接著用 Log 確認:
protected function getAuthUrl($state)
{
Log::info('getAuthUrl method called with state:', ['state' => $state]);
session(['oauth_state' => $state]);
Log::info('State stored in session:', ['state' => session('oauth_state')]);
$authUrl = $this->buildAuthUrlFromBase('https://access.line.me/oauth2/v2.1/authorize', $state);
Log::info('Generated Auth URL:', ['url' => $authUrl]);
return $authUrl;
}
然後在 LineLoginController 的 handleLineCallback 方法也是同樣用 Log 確認:
public function handleLineCallback(Request $request)
{
$state = request('state'); // 從請求中取得 state 参数
Log::info('Callback state parameter:', ['state' => $state]);
// 從 Session 中取得之前保存的 state 參數
$savedState = session('oauth_state');
Log::info('Saved state parameter:', ['state' => $savedState]);
// 驗證 state 參數是否匹配
if ($state !== $savedState) {
// 處理 state 不匹配的情況
Log::error('Invalid state parameter detected.');
abort(403, 'Invalid state parameter.');
}
$lineUser = Socialite::driver('line')->user();
dd($lineUser);
[2024-09-09 14:03:39] local.INFO: getAuthUrl method called with state: {"state":"HCZgbeFhIX5DSyBqCsVTDrGhBdlGBSM3ga3EItXe"}
[2024-09-09 14:03:39] local.INFO: State stored in session: {"state":"HCZgbeFhIX5DSyBqCsVTDrGhBdlGBSM3ga3EItXe"}
[2024-09-09 14:03:39] local.INFO: Generated Auth URL: {"url":"https://access.line.me/oauth2/v2.1/authorize?client_id=2006178091&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Flogin%2Fline%2Fcallback&scope=openid+profile+email&response_type=code&state=HCZgbeFhIX5DSyBqCsVTDrGhBdlGBSM3ga3EItXe"}
[2024-09-09 14:03:42] local.INFO: Callback state parameter: {"state":"2LCmnoCjwI5k8MpTAZngbZ8nyLZZAIo46FyvrOLE"}
[2024-09-09 14:03:42] local.INFO: Saved state parameter: {"state":"HCZgbeFhIX5DSyBqCsVTDrGhBdlGBSM3ga3EItXe"}
[2024-09-09 14:03:42] local.ERROR: Invalid state parameter detected.
只有在 callBack 方法裡,從請求裡取得的 $state 值是不同的!
其他三個 $state 值都是一樣的。
登登!
問題來源終於找到了!
我突然發現使用瀏覽器測試時,我是直接拿 Postman 測試產生的網址丟到瀏覽器。
範例:
http://localhost:8000/login/line/callback?code=example_code&state=example_state
而且菜雞仔如我,都給他用上一步重新登入!(要多荒唐)
因為 $state 值是隨機生成的!
但使用 Log 查看後發現每次我 callBack 取得的 $state 值永遠都是一樣的。
所以我就重新在瀏覽器貼上網址:
http://localhost:8000/login/line
重新登入後就成功了哦!(真是可喜可樂)
希望大家不會遇到跟我一樣的問題!
菜雞仔如我,果然經驗都是累積出來的。(拭淚)