前置作業
安裝 PHP
請依照欲使用的 Laravel 版本來安裝 PHP,比較舊的 Laravel 用新的 PHP 會有問題;反之亦然。
安裝 composer
composer 是 PHP 的套件管理器,請自行至 composer 官網依照環境對應的指示下載安裝。
一個 Laravel project
如果還沒有 Laravel 專案的請使用下列方法開一個吧:
composer create-project laravel/laravel {your-project-name}
別忘記把一些資料庫相關設定加進去,否則就跑不動囉。
本教學文範例
開始之前先給大家本次教學文範例的程式碼倉庫吧:
https://github.com/hms5232/laravel-api-socialite-fb-login
範例採用 Apache 授權(如有變更,以程式碼倉庫最新狀態為準),希望大家取之於社群,也別忘回饋於社群。教學相長。
初始狀態我設定在已經修改好一些專案環境的情況,此時大概是這個提交的狀態:https://github.com/hms5232/laravel-api-socialite-fb-login/commit/25150cc948f21f8a94e75071416b88a1086da5b3
那就準備開始吧~
前置作業(Auth 相關)
是的沒看錯,因為本次範例使用的 Laravel 8 沒有 make:auth
指令了(查了資料說是從 Laravel 6 開始移出去獨立的),為了方便快速建立相關的 Controller 及資料夾,故要做一些前置步驟(當然你覺得沒差要自己寫也可以,只是我比較懶惰希望可以快速產生)。如果已經有 Auth 相關的 Controller 的話,可以直接跳至下一步。
這邊我們使用 Laravel 官方套件 ui:
composer require laravel/ui --dev
php artisan ui vue --auth
安裝完之後,應該就有 Auth
資料夾及相關的登入及註冊 Controller。
安裝與設定 Socialite
Socialite 是 Laravel 官方的套件,支援 Google、Github、Twitter、Gitlab、Facebook、linkedin、bitbucket 等網站的第三方登入(可能依版本不同有不同的支援度,請以官方文件為主)。當然這種作法可能有缺點,但這邊不討論,有興趣可以查看這篇大大寫的文章。
安裝方式很簡單:
composer require laravel/socialite
如果安裝過程發生錯誤,代表 Laravel 版本太就不被最新版 Socialite 支援,請去官方 Github 查 release log 確認最後支援的版本並重新嘗試安裝。
例如:Laravel 5.7 及 5.8 在 Socialite 5 之後不再被支援,故只能使用 4.4.1 或以下之版本:
composer require laravel/socialite:4.4.1
安裝好了之後接著要為 Socialite 做一些設定,請在 .env
及 .env.example
中加入下面幾項設定:
FB_CLIENT_ID=
FB_CLIENT_SECRET=
# 重新導向的網址請依照自己的環境及 URI 填寫
FB_REDIRECT=http://localhost:8000/auth/facebook-login-callback
內容要填入什麼呢?晚點就知道啦。
接著要註冊 Socialite,請至 config/app.php
新增:
Laravel\Socialite\SocialiteServiceProvider::class,
如果有需要的話也可以順便設定 alias:
'Socialite' => Laravel\Socialite\SocialiteServiceProvider::class,
然後在 config/services.php
新增:
'facebook' => [
'client_id' => env('FB_CLIENT_ID'),
'client_secret' => env('FB_CLIENT_SECRET'),
'redirect' => env('FB_REDIRECT'),
],
注意:key 一定要是 Socialite 所指定的名稱!支援哪些請至官方文件查詢。
Facebook 設定
這邊步驟有經驗的人來說可能簡單,不過有 GUI 大致上還算簡單,不要被一堆文字搞混亂即可。另外,因為臉書不斷在改版,畫面可能會有些許不同,但邏輯是相同的。
首先,先去 FB 開發人員專區(沒註冊過開發人員的會被要求註冊)建立一個新的應用程式:
接著填寫應用程式名稱、聯絡的電子信箱地址等資訊,這邊不困難就不截圖囉。填寫完成後就完成建立新的應用程式了。
下一步應該也很顯而易見,就是設定我們要的 Facebook login 啦:
接下來會要你選擇是要在什麼類型的 Client 端使用 FB 登入,因為本文範例是使用網頁,所以選擇「網站」
下一步會要你輸入網址,這邊應為是開發用的,請依照開發環境設定即可,正式上線前都可以修改不用擔心
接下來的步驟可以不用理會,如果有需要的同學可以自己去看那些東西寫的是什麼,是否有符合自己的需要。
將應用程式的設定加入 Socialite
這個看圖就能懂了吧,不多說嚕
撰寫 API 及商業邏輯
如果對 OAuth 不熟的朋友,可以先去了解下這個到底是啥,沒興趣的話,至少知道使用者去 FB 登入,以及之後的跳轉都是 GET 進行的(但回呼之後前後端需要 POST 溝通一次才能加上 credentials 寫入登入狀態)。
資料庫新增欄位記錄
這部份看大家的需求,通常我會建議記住,方便之後有需要可以操作。
首先下個指令來產生一個 migration :
php artisan make:migration AddFbIdColumnToUsersTable
然後寫一下加入要新增的欄位:
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('fb_id', 30)->nullable()->comment('使用者的臉書 ID'); // 加入這行
});
}
別忘了跑 php artisan migrate
才會新增。
還有記得將欄位寫入 model 否則沒辦法記錄進去資料庫:
protected $fillable = [
// 省略
'fb_id',
];
撰寫 API 與商業邏輯
由於這些都是吃 session 的,所以 route 都會寫在 route/web.php
中。
首先,原本的東西都沒用,通通註解吧~然後加上兩條 route URI:
// FB 登入
Route::get('/auth/facebook-login', [App\Http\Controllers\Auth\LoginController::class, 'fbLogin']);// FB 登入 callback
Route::get('/auth/facebook-login-callback', [App\Http\Controllers\Auth\LoginController::class, 'fbLoginCallback']);
修改 app\Http\Controllers\Auth\LoginController.php
來告訴前端需要轉去哪裡登入 FB 以及之後回呼回來的處理:
// 省略
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Laravel\Socialite\Facades\Socialite;
// 省略
// 註解 middleware
// 省略
/*
|--------------------------------------------------------------------------
| Facebook Login
|--------------------------------------------------------------------------
*/
public function fbLogin(Request $request)
{
$redirect_url = Socialite::driver('facebook')
->scopes(['email']) // 額外要求要使用者的電子信箱地址
->redirect()->getTargetUrl();
return response()->json(['target' => [$redirect_url]], 302);
}
public function fbLoginCallback(Request $request)
{
if (is_null($request['code']) || is_null($request['state'])){
return response()->json(['messages' => ['授權失敗']], 401);
}
Session::put('state', $request['state']); // 不寫入 session 就要 stateless
try{
$fb_user = Socialite::driver('facebook')->user();
} catch (\Exception $exception){
Log::error($exception);
return response()->json(['messages' => [strval($exception)]], 401);
}
// 確認使用者是否已經使用此方法註冊過
if (User::where('facebook_id', $fb_user->getId())->exists()){ // 有
// 登入
Auth::guard('web')->login(User::where('facebook_id', $fb_user->getId())->first());
} else if (User::where('email', $fb_user->getEmail())->exists()){ // 有相同 email 的使用者
// 更新使用者資料
DB::transaction(function () use ($fb_user){
User::where('email', $fb_user->getEmail())->update([
'facebook_id' => $fb_user->getId()
]);
});
Auth::guard('web')->login(User::where('email', $fb_user->getEmail())->first());
}else { // 沒找到
// 自動註冊
DB::transaction(function () use ($fb_user){
User::create([
'name' => $fb_user->getName(),
'email' => $fb_user->getEmail(),
'password' => Hash::make(uniqid('FB_')),
'facebook_id' => $fb_user->getId()
]);
});
Auth::guard('web')->login(User::where('email', $fb_user->getEmail())->where('facebook_id', $fb_user->getId())->first());
}
return response()->json(['messages' => ['登入成功!']], 200);
}
不好意思排版有些歪掉,建議上範例的 Github repo 看比較不傷眼。
簡單說明一下, fbLogin
這個 function 是前端跟我 API 說,使用者想要使用 FB 登入,API 這邊就會產生一組重新導向的網址給前端,前端轉過去給使用者登入 FB 後,FB 會丟 callback 回來,此時 GET 的網址就是剛剛在 .env
中設定的環境變數 FB_REDIRECT
設定的,因此這邊要看各位怎麼實作前後端溝通來改變這個流程(總不能回呼直接呼叫 API ,那使用者就看著 200 的 json 畫面發呆吧)
FB 丟回來之後會收到 code
和 state
兩個參數(失敗的話就變成 error_code
和 error_message
了),之後傳到後端再拿著去和 FB 要使用者資料,要到之後就是你的商業邏輯處理部分囉。
範例是寫成:
- 有符合的
fb_id
:直接登入。 - 沒有
fb_id
但有重複的 email:幫使用者連結帳號後登入。 - 以上都沒有:註冊新帳號後登入。
參考資料
請見 Repo 的 README.md
。
結語
一開始有這個需求的時候覺得很複雜就沒研究,直到最近才撿回來認真研究,發現其實都有套件幫你包好一堆事情了,果然人懶惰的話就有一堆藉口XD 感謝大家這次收看,前端好難。
慣例的聲明
本文為作者親手撰寫,如有轉載請註明出處。有任何問題歡迎直接留言,或是在 repo 發 issue/discussion 。若有幫助的話,歡迎幫忙拍手、按讚或是星星/fork一下 repo,有想斗內一下的也歡迎~