想要在網站使用 Google (Google+)、Facebook 的登入,在任何頁面登入,登入完成後,都要自動導向登入前看的頁面,常見的作法就是帶 from 的參數給 G / FB,然後 G / FB 再將參數帶回來即可。
不過這個作法在新版採用 Strict Mode 的情況下,只能在 G / FB 設定固定的網址,不能在產生網址時直接帶入,要怎麼解決呢?
Google、Facebook oAuth2 登入如何帶回傳的參數(state)
先說說研究的結論,state 的設定在下面文章都有寫,不過因為 Facebook SDK 需要使用 session,若 G /FB 都需要使用的話,建議要帶回來的值,都自己寫入 session 就好了。
Google'Facebook 底層都是走 oAuth 的標準,基本上都有下述這四個參數:(節錄自此篇:手動建立登入流程 - Facebook 登入)
https://www.facebook.com/v2.12/dialog/oauth? client_id={app-id} &redirect_uri={redirect-uri} &state={state-param}此端點的必要參數如下:
- client_id。位於應用程式主控板的應用程式編號。
- redirect_uri。要將登入用戶重新導向到的網址。此網址會擷取「登入」對話方塊的回應。如果是在桌面版應用程式內的 WebView 中使用,這必須設為 https://www.facebook.com/connect/login_success.html。您可以在應用程式主控板中確認您為應用程式設定的網址正確無誤。在應用程式主控板左側的導覽功能表中,點擊產品下方的 Facebook 登入,然後點擊設定。在用戶端 OAuth 設定部分,驗證有效的 OAuth 重新導向 URI。
- state。此為您應用程式建立來保持要求和回呼間狀態的字串值。此參數應該用來防止跨網站偽造要求,且會原封不動地傳回到您的重新導向 URI。
範例
https://www.facebook.com/v2.12/dialog/oauth? client_id={app-id} &redirect_uri={"https://www.domain.com/login"} &state={"{st=state123abc,ds=123456789}"}
從說明文件裡面有說到,state 本身是為了防止 CSRF 用得,不過另外是可以來帶我們需要的值,在登入完成 redirect 回來的參數裡面,會有 code 和 state 被帶回來。
- 註:於產生出來點擊登入的連結,就可以看到 state 的參數值,可以先確認是否有塞成功。
先整理 Google 與 Facebook state 的設定方式
Google+ 與 Facebook 的文件都很少關於 state 設定的說明,所以在這邊順便把追 Code 的經過做點紀錄。
Facebook 設定 state 值的作法
- $helper->getPersistentDataHandler()->set('state', $state); // 主要寫法是這樣
- 建議寫法,部份參考 G+ state 文件
<?php $s = sha1(openssl_random_pseudo_bytes(1024)); $_SESSION['FBRLHstate'] = $s; // 這個變數自己隨意定義,之後接收回傳值自己驗證即可 $helper->getPersistentDataHandler()->set('state', jsonencode(['state' => $s, 'from' => '/')); // 所以可以在此帶值進去 ?>
或
<?php $helper->getPersistentDataHandler()->set('state', jsonencode(['state' => $SESSION['FBRLH_state'], 'from' => '/')); ?>
- 不過上述作法修改後,state 的驗證部份就得自己做,並且要注意 SDK 要怎麼處理
下述為 Facebook state 追 Code 的一些紀錄
- src/Facebook/Helpers/FacebookRedirectLoginHelper.php
- $state = $this->getState(); // $this->getInput('state'); = $GET['state']
- $savedState = $this->persistentDataHandler->get('state');
- $this->persistentDataHandler->set('state', null);
- // persistentDataHandler = class FacebookSessionPersistentDataHandler
- src/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php
- 猜想 state 大致寫法
- use Facebook\PersistentData\FacebookSessionPersistentDataHandler;
- use Facebook\PersistentData\PersistentDataInterface;
- $fb->persistentDataHandler->set('state', '{"from": "/"}');
- src/Facebook/Helpers/FacebookRedirectLoginHelper.php
- $state = $this->persistentDataHandler->get('state') ?: $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRFLENGTH);
- $this->persistentDataHandler->set('state', $state);
- $SESSION[$this->sessionPrefix . $key] = $value; // 最後將值 set 到 session 去 ($SESSION['FBRLHstate'])
- src/Facebook/Helpers/FacebookRedirectLoginHelper.php # 247
- \hashequals
- PHP 有內建的:PHP: hashequals - Manual,Facebook SDK 有判斷若不存在則用自己寫的
- src/Facebook/polyfills.php
- function hash_equals($knownString, $userString)
- 直接對這個 return true; 應該就可以避開這個驗證,不過別亂用這種作法。 XD
- 註:Facebook SDK 裡面有參數會組合傳過去的 referer_url,然後去跟 Facebook 官方設定的參數做比對,不在上面的 code 裡面。
Google+ 設定 state 值的作法
- Google 預設沒有設定 state,不過避免 CSRF,官方還是有建議範例,參考範例:
<?php $s = sha1(openssl_random_pseudo_bytes(1024)); $_SESSION['GPLUS_state'] = $s; // 這個變數自己隨意定義,之後接收回傳值自己驗證即可 $gclient->setState(json_encode(['state' => $s, 'from' => '/'])); ?>
下述為 Google state 追 Code 的一些紀錄
-
- src/Google/Client.php
- $this->getAuth()->setState($state);
- $this->getAuth()->authenticate($code, $crossClient);
- public function getAuth()
$class = $this->config->getAuthClass();
$this->auth = new $class($this);
- src/Google/Config.php
'auth_class' => 'Google_Auth_OAuth2' - src/Google/Auth/OAuth2.php # Google_Auth_OAuth2
- class Google_Auth_OAuth2 extends Google_Auth_Abstract
- public function setState($state) {
$this->state = $state;
} - if (isset($this->state)) {
$params['state'] = $this->state;
} - 產生 Login url (帶入 state)
- 找驗證部份
- if ($code && $gclient->authenticate($code)) {
- src/Google/Client.php
public function authenticate($code, $crossClient = false) {
return $this->getAuth()->authenticate($code, $crossClient);
} - $token = $gclient->getAccessToken();
public function getAccessToken() {
$token = $this->getAuth()->getAccessToken();
} - getAuth 是 Google_Auth_OAuth2 (src/Google/Auth/OAuth2.php) 的 getAuth()
public function getAccessToken() {
return json_encode($this->token);
} - => Google+ 使用 code、client_id、client_secret 驗證,不需要 state
- src/Google/Client.php