Laravel Socialite 實作前後端分離的第三方登入 API

閱讀時間約 20 分鐘
本文使用網站的 FB 登入做示範
採用 Laravel 8 + Socialite 5
使用 Session 記錄狀態
不同版本可能會有些許語法及方法上的差異,請自行調整

前言

最近因為碰到需要實作 OAuth 第三方登入的需求,只好把之前隨便看看的東西撿回來研究並實作。不過我找到多數現存的中文文章都是前後端整合的寫法,在成功做出來後,寫一篇自己記錄一下兼分享給日後有需要的人參考。

前置作業

安裝 PHP

請依照欲使用的 Laravel 版本來安裝 PHP,比較舊的 Laravel 用新的 PHP 會有問題;反之亦然。

安裝 composer

composer 是 PHP 的套件管理器,請自行至 composer 官網依照環境對應的指示下載安裝。

一個 Laravel project

如果還沒有 Laravel 專案的請使用下列方法開一個吧:
composer create-project laravel/laravel {your-project-name}
別忘記把一些資料庫相關設定加進去,否則就跑不動囉。

本教學文範例

開始之前先給大家本次教學文範例的程式碼倉庫吧:
範例採用 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 確認最後支援的版本並重新嘗試安裝。
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 要使用者資料,要到之後就是你的商業邏輯處理部分囉。
範例是寫成:
  1. 有符合的 fb_id :直接登入。
  2. 沒有 fb_id 但有重複的 email:幫使用者連結帳號後登入。
  3. 以上都沒有:註冊新帳號後登入。

參考資料

請見 Repo 的 README.md。

結語

一開始有這個需求的時候覺得很複雜就沒研究,直到最近才撿回來認真研究,發現其實都有套件幫你包好一堆事情了,果然人懶惰的話就有一堆藉口XD 感謝大家這次收看,前端好難。

慣例的聲明

本文為作者親手撰寫,如有轉載請註明出處。有任何問題歡迎直接留言,或是在 repo 發 issue/discussion 。若有幫助的話,歡迎幫忙拍手、按讚或是星星/fork一下 repo,有想斗內一下的也歡迎~

avatar-img
4會員
16內容數
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液