本文為 Laravel 7 — Socialite in Action ( Social Media Login Integration with Facebook, Twitter, LinkedIn, Google) 之更新簡化版本。省略大部分說明只提供步驟紀錄。詳細說明請參考原文。
Create Project
建立專案之後,請建立資料庫和更新 .env
。下面以 Postgre SQL 為例
.evn
的部分如下:
1 2 3 4 5 6 DB_CONNECTION =pgsqlDB_HOST =127.0.0.1DB_PORT =5432DB_DATABASE =demoDB_USERNAME =rootDB_PASSWORD=
安裝 Jetstream 1 2 3 4 5 $ composer require laravel/jetstream $ php artisan jetstream:install inertia --teams $ npm install && npm run dev $ php artisan vendor:publish --tag=jetstream-views
安裝 Socialite 1 2 3 $ composer require laravel/socialite
修改 database schema 詳細原因請參考 Laravel 7 — Socialite in Action ( Social Media Login Integration with Facebook, Twitter, LinkedIn, Google)
1 2 $ composer require doctrine/dbal $ php artisan make:migration edit_columns_in_users_table
在新增的 migration 檔案中調整如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public function up ( ) { Schema::table('users' , function (Blueprint $table ) { $table ->dropUnique(['email' ]); $table ->string('password' )->nullable()->change(); $table ->json('social' )->nullable(); $table ->softDeletes(); $table ->unique(['email' , 'deleted_at' ]); }); } public function down ( ) { Schema::table('users' , function (Blueprint $table ) { $table ->dropUnique(['email' , 'deleted_at' ]); $table ->dropSoftDeletes(); $table ->dropColumn(['social' ]); $table ->string('password' )->change(); $table ->string('email' )->unique()->change(); }); }
然後執行
Model 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 use Illuminate \Database \Eloquent \SoftDeletes ;class User extends Authenticatable { use Notifiable ; use SoftDeletes ; protected $casts = [ 'email_verified_at' => 'datetime' , 'social' => 'array' , ]; public function setEmailAttribute ($value ) { $this ->attributes['email' ] = strtolower($value ); } }
Fortify 有兩個 Fortify 相關的檔案須修正,原因是我們現在支援 SoftDeletes
,注意 email
的規則 unique
須置換為 unique:users,email,NULL,id,deleted_at,NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php class CreateNewUser implements CreatesNewUsers { use PasswordValidationRules ; public function create (array $input ) { Validator::make($input , [ 'email' => ['required' , 'string' , 'email' , 'max:255' , 'unique:users,email,NULL,id,deleted_at,NULL' ], ])->validate(); return DB::transaction(function ( ) use ($input ) { return tap(User::create([ 'name' => $input ['name' ], 'email' => $input ['email' ], 'password' => Hash::make($input ['password' ]), ]), function (User $user ) { $this ->createTeam($user ); }); }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php class UpdateUserProfileInformation implements UpdatesUserProfileInformation { public function update ($user , array $input ) { Validator::make($input , [ 'email' => ['required' , 'email' , 'max:255' , 'unique:users,email,NULL,id,deleted_at,NULL' ], ])->validateWithBag('updateProfileInformation' ); } }
取得平台憑證 Client ID 和 Secret 將您需要的資訊補在 .env
,下面只是局部平台的範例
1 2 3 4 5 6 7 8 9 10 11 12 FACEBOOK_CLIENT_ID =FACEBOOK_CLIENT_SECRET =FACEBOOK_CALLBACK_URL =/login/facebook/callbackGOOGLE_CLIENT_ID =GOOGLE_CLIENT_SECRET =GOOGLE_CALLBACK_URL =/login/google/callbackLINKEDIN_CLIENT_ID =LINKEDIN_CLIENT_SECRET =LINKEDIN_CALLBACK_URL =/login/linkedin/callbackTWITTER_CLIENT_ID =TWITTER_CLIENT_SECRET =TWITTER_CALLBACK_URL =/login/twitter/callback
Services 設定 在 config/services.php
加入設定,您可能注意到 scopes
的部分,但在官方文件沒有關於這段。的確這是額外的設定,我覺得將它們放在一起比較合適您也可以放到 .env
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 'ses' => [ 'key' => env('AWS_ACCESS_KEY_ID' ), 'secret' => env('AWS_SECRET_ACCESS_KEY' ), 'region' => env('AWS_DEFAULT_REGION' , 'us-east-1' ), ], 'facebook' => [ 'client_id' => env('FACEBOOK_CLIENT_ID' ), 'client_secret' => env('FACEBOOK_CLIENT_SECRET' ), 'redirect' => env('FACEBOOK_CALLBACK_URL' ), 'scopes' => ['email' , 'public_profile' ], ], 'google' => [ 'client_id' => env('GOOGLE_CLIENT_ID' ), 'client_secret' => env('GOOGLE_CLIENT_SECRET' ), 'redirect' => env('GOOGLE_CALLBACK_URL' ), 'scopes' => [ 'https://www.googleapis.com/auth/userinfo.email' , 'https://www.googleapis.com/auth/userinfo.profile' , 'openid' , ], ], 'linkedin' => [ 'client_id' => env('LINKEDIN_CLIENT_ID' ), 'client_secret' => env('LINKEDIN_CLIENT_SECRET' ), 'redirect' => env('LINKEDIN_CALLBACK_URL' ), 'scopes' => ['r_emailaddress' , 'r_liteprofile' ], ], 'twitter' => [ 'client_id' => env('TWITTER_CLIENT_ID' ), 'client_secret' => env('TWITTER_CLIENT_SECRET' ), 'redirect' => env('TWITTER_CALLBACK_URL' ), 'scopes' => [], ],
Auth Controller 這是本文最重要的段落,也可能是您一直在尋找的部分。我們將建立一個 Controller 來處理 OAuth 回呼的部分
1 $ php artisan make:controller Auth/LoginController
檔案建立之後,下面是完整的 app/Http/Controllers/Auth/LoginController.php
程式碼,雖然有點長,但方便您直接複製貼上並完整理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 <?php namespace App \Http \Controllers \Auth ;use App \Http \Controllers \Controller ;use App \Providers \RouteServiceProvider ;use Illuminate \Support \Facades \Auth ;use Illuminate \Http \Request ;use Socialite ;use App \Models \User ;use App \Models \Team ;class LoginController extends Controller { protected $redirectTo = RouteServiceProvider::HOME; public function redirectToProvider (string $provider ) { try { $scopes = config("services.$provider .scopes" ) ?? []; if (count($scopes ) === 0 ) { return Socialite::driver($provider )->redirect(); } else { return Socialite::driver($provider )->scopes($scopes )->redirect(); } } catch (\Exception $e ) { abort(404 ); } } public function handleProviderCallback (string $provider ) { try { $data = Socialite::driver($provider )->user(); return $this ->handleSocialUser($provider , $data ); } catch (\Exception $e ) { return redirect('login' )->withErrors(['authentication_deny' => 'Login with ' .ucfirst($provider ).' failed. Please try again.' ]); } } public function handleSocialUser (string $provider , object $data ) { $user = User::where([ "social->{$provider} ->id" => $data ->id, ])->first(); if (!$user ) { $user = User::where([ 'email' => $data ->email, ])->first(); } if (!$user ) { return $this ->createUserWithSocialData($provider , $data ); } $social = $user ->social; $social [$provider ] = [ 'id' => $data ->id, 'token' => $data ->token ]; $user ->social = $social ; $user ->save(); return $this ->socialLogin($user ); } public function createUserWithSocialData (string $provider , object $data ) { try { $user = new User; $user ->email = $data ->email; $user ->name = $data ->name; $user ->social = [ $provider => [ 'id' => $data ->id, 'token' => $data ->token, ], ]; $user ->markEmailAsVerified(); $team = Team::forceCreate([ 'user_id' => $user ->id, 'name' => $user ->name."'s Team" , 'personal_team' => true , ]); $user ->current_team_id = $team ->id; $user ->save(); return $this ->socialLogin($user ); } catch (Exception $e ) { return redirect('login' )->withErrors(['authentication_deny' => 'Login with ' .ucfirst($provider ).' failed. Please try again.' ]); } } public function socialLogin (User $user ) { auth()->loginUsingId($user ->id); return redirect($this ->redirectTo); } }
Routes 在 routes/web.php
,注意 Laravel 8 的路由設定語法有些改變。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php use Illuminate \Support \Facades \Route ;use App \Http \Controllers \Auth \LoginController ;Route::middleware(['auth:sanctum' , 'verified' ])->get('/dashboard' , function ( ) { return Inertia\Inertia::render('Dashboard' ); })->name('dashboard' ); Route::get('/login/{provider}' , [LoginController::class, 'redirectToProvider' ]) ->name('social.login' ); Route::get('/login/{provider}/callback' , [LoginController::class, 'handleProviderCallback' ]) ->name('social.callback' );
Views 最後在 resources/views/auth/
補上登入的按鈕則完成。使用 php artisan serve
測試看看吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @php $providers = [ 'google' => [ 'bgColor' => '#ec462f' , 'icon' => 'fab fa-google' , ], 'facebook' => [ 'bgColor' => '#1877f2' , 'icon' => 'fab fa-facebook-f' , ], 'linkedin' => [ 'bgColor' => '#2969b1' , 'icon' => 'fab fa-linkedin-in' , ], 'twitter' => [ 'bgColor' => '#41aaf1' , 'icon' => 'fab fa-twitter' , ]; @endphp @foreach ($providers as $provider => $params ) <a class ="block py -3 px -4 mb -5/2 rounded -sm text -white text -center font -bold hover :no -underline hover :opacity -75" href =" {{ route('social.login' , ['provider' => $provider ]) }}" style=" background-color: {{ $params ['bgColor' ] }}; min-height: 48 px;" > <i class=" tw-float -left tw-inline-block tw-h-5 {{ $params ['icon' ] }}"></i> Login with {{ ucwords($provider ) }} </a> @endforeach