LaravelではSocialiteパッケージを使用してソーシャルログインを行うことができます。
ソーシャルログインに成功したときのユーザ管理ですが、blogはqiitaの記事などを見ていると、users.passwordをnullableにして、SNSサイトから取得したメールアドレスでusersテーブルを作る例をよく見かけます。
ですが、usersテーブルへの情報のもたせ方については、SNSサイトに登録されているメールアドレスがある場合の振る舞いをどうするかや、複数SNSサイトのソーシャルログインを受け付けるか否かなど、作成するWebサービスの仕様によって実装が変わってきます。
今回は複数SNSサイトのソーシャルログインを許容し、SNSサイトに登録されているメールアドレスで紐付けるアカウントを決定する場合の実装方法について説明します。
また、本記事はLaravelのSocialiteパッケージに関する記事をベースにしているため、事前にSocialiteパッケージのドキュメントを確認しておいてください。
参考
- https://laravel.com/docs/7.x/socialite
- https://www.sitepoint.com/easily-add-social-logins-to-your-app-with-socialite
作業手順
usersテーブルのemail, passwordをnullableにする。
Laravelがデフォルトで用意しているusersテーブルはemail, passwordがNOT NULLになっているので、まずこれらをnull許容にする。新しくmigrationファイルを作成しup()で下記の処理を記載する。
blogサイトによっては、既存のmigrationファイルを編集する旨の記載があるが、変更したことの履歴を明確にするため別のmigrationファイルを作るほうが望ましい。
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('email')->nullable()->change();
$table->string('password')->nullable()->change();
});
}
Eloquentでカラムの変更を行えるようにするため、下記のパッケージのインストールが必要となるかもしれない。
$ composer require doctrine/dbal
linked_social_accountsテーブルを作る
linked_social_accoutテーブルを下記の構造で作成する。このテーブルは、usersテーブルに対して1:nの関係になる。provider_nameカラムには、'facebook'や'google'など、認証されたサイトを登録する。provider_idには認証されたサイトから渡されたid情報を登録する。
Schema::create('linked_social_accounts', function (Blueprint $table) {
$table->increments('id');
$table->bigInteger('user_id');
$table->string('provider_name')->nullable();
$table->string('provider_id')->unique()->nullable();
$table->timestamps();
});
なぜ1:1ではなく1:nかというと、特定のユーザ(usersレコード)に対してgoogle id、facebook idなど複数のSNSアカウントを紐付け可能とする。これを、もし1つの"SNSアカウントとだけ紐付け可能"としたいのであれば、usersテーブルにprovider_name
とprovider_id
カラムを追加しても良い。
userとlinked_social_accountsとのリレーションを指定
Eloquentのリレーション機能を利用する場合、app/Userモデルに対してリレーションを追加する。
前述したとおり1:nの関係なのでhasManyとなる。
// app/user.php
public function accounts(){
return $this->hasMany('App\LinkedSocialAccount');
}
controllerの登録
Laravelの公式サイトにあるSocialiteの説明に従って実装すると、SNSサイトからの認証成功時にhandleProviderCallback()メソッドが呼ばれる。ここでは以下のような形でhandleProviderCallback()メソッドを実装する。
引数の$providerは後述のroute定義より渡され、具体的には'facebook'や'google'といった値が入ることを想定している。このコードでは行っていないが$providerの値が連携対象のSNSサイトであるかのチェックを行うのも良い。
認証が成立したらSocialAccountsServiceクラスでユーザ登録、もしくは既存のユーザに対するSNS認証情報の追加を行う。
// app/Http/Controllers/Auth/SocialAccountController.php
<?php
public function redirectToProvider(string $provider)
{
return \Socialite::driver($provider)->redirect();
}
public function handleProviderCallback(\App\SocialAccountsService $accountService, string $provider)
{
try {
$user = \Socialite::with($provider)->user();
} catch (\Exception $e) {
return redirect('/login');
}
$authUser = $accountService->findOrCreate(
$user,
$provider
);
auth()->login($authUser, true);
return redirect()->to('/home');
}
}
ルーティング
作成したControllerに対するルーティングを追加する。
// routes/web.php
Route::get('login/{provider}', 'Auth\SocialAccountController@redirectToProvider');
Route::get('login/{provider}/callback', 'Auth\SocialAccountController@handleProviderCallback');
Viewへログインリンクを追加
ユーザ作成ページへSNSログインの導線を作成する。
<a href="/login/facebook" class="btn btn-default">Facebookでログイン</a>
<a href="/login/google" class="btn btn-default">Googleでログイン</a>
ユーザ登録処理
以下のような形でSocialAccountServiceクラスを作成する。
<?php
namespace App;
use Laravel\Socialite\Contracts\User as ProviderUser;
class SocialAccountService
{
public function findOrCreate(ProviderUser $providerUser, $provider)
{
// linked_social_accountsへすでにユーザ登録済みかチェック
$account = LinkedSocialAccount::where('provider_name', $provider)
->where('provider_id', $providerUser->getId())
->first();
if ($account) {
// すでにユーザ登録済みの場合はusersテーブルの情報を返す
return $account->user;
}
// SNSサイトから渡されたemailですでにユーザ作成済みかチェック
$user = User::where('email', $providerUser->getEmail())->first();
if (!$user) {
// 未作成ならここで作成する
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
]);
}
// 取得(or作成)したusersテーブルに紐づくlinked_social_accountsのレコードを1行追加
$user->accounts()->create([
'provider_id' => $providerUser->getId(),
'provider_name' => $provider,
]);
// 取得したusersテーブルの情報を返す
return $user;
}
}
ソーシャルログインが成功した後に、メールアドレスではなくprovider_idでユーザ検索していることに注意。emailで検索するとSNSサイト側でメールアドレスが変わったときにユーザ情報が取れなくなる。
このクラスのfindOrCreate()は、作成するWebサービスの仕様に応じてカスタマイズが必要となる場合がある。
本実装の場合、複数SNSサイトでのソーシャルログイン利用で、SNSサイトごとにメールアドレスが異なっていた場合紐付けができないことに注意。メールアドレスが異なっていても紐付けを許容する場合は、ログイン後に紐付け処理を行わせるなどのオペレーションが必要となるかもしれない。
usersが存在しない場合、本メソッドでUser::create()をコールして登録している。SNSログインの場合は、作成するWebサービスでのパスワード管理が不要なのでpassword列に値はセットしていない。この例では、emailのカラムにSNSサイトに登録されているアドレスをセットしている。作成するWebサービスにメールアドレス提供するか否かはソーシャルサイト側のログイン画面で同意確認が行われている。
個人情報保護などリスク管理の管理で、email情報をあえてサイトに持たせたくない場合は、ここでemailをセットしなくても良い。この場合、複数SNSサイトのアカウント紐付けができないので、ログイン後に紐付け処理を行わせるなどの処置が必要となる。