socialiteでSNSログインを実装
Laravelで、SNSのアカウントを使ったログインの実装をした時のメモです。
socialiteとは
soscialiteとは、Laravelで簡単にOAuth(SNS認証)を可能にするパッケージツールです。
はじめに、composerを使ってsocialite
をインストールします。
$ composer require laravel/socialite
モデル
各テーブルに紐付くUser.php
モデルとIdentityProvider.php
のリレーションを設定します。
$ php artisan make:model IdentityProvider -m
Userモデルの設定
ログイン時に新規作成するUserテーブルは、挿入できるように追記しておきます。
class User extends Authenticatable
{
use Notifiable;
function IdentityProviders()
{
// IdentityProviderモデルと紐付ける 1対多の関係
return $this->hasMany(IdentityProvider::class);
}
ユーザーに対して各プロバイダーは「1対多」の関係になるのでhasMany
を設定します。
###IdentityProviderモデルの設定
class IdentityProvider extends Model
{
protected $fillable = ['user_id', 'provider_name', 'provider_id'];
function user()
{
return $this->belongsTo(User::class);
}
}
プロバイダーの情報はユーザーに属するのでbelongsTo
を設定します。
マイグレーションの設定
ソーシャルログインは、メールアドレスやパスワードがない場合があるのでnullable()
にしておきます。
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique()->nullable(); // 追記
$table->string('password')->nullable(); // 追記
$table->rememberToken();
$table->timestamps();
});
}
usersに紐付けるプロバイダー情報のテーブルも作成します。
今回はテーブル名をidentity_providers
としました。
class CreateIdentityProvidersTable extends Migration
{
public function up()
{
Schema::create('identity_providers', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users'); // 外部キー制約
$table->string('provider_id');
$table->string('provider_name');
$table->primary(['provider_name', 'provider_id']); // 複合キー
$table->unique(['user_id', 'provider_name']); // ユニーク制限
$table->timestamps();
});
}
users
テーブルのuser_id
にidentity_providers
にid
を紐付けて管理したいので、
$table->foreign('user_id')->references('id')->on('users');
のように、
マイグレーションの外部キー制約を設定します。
ルーティング
ユーザーを認証する準備ができたら、2つのルートを設定します。
1つはユーザをOAuthプロバイダにリダイレクトするためのもので、
もう1つは認証後にプロバイダからのコールバックを受け取るためのものです。
web.phpの設定
web.php
にてログイン画面でボタンをクリックしたときのリンクを設定します。
たとえば、シンプルにGitHubだけで実装する場合には下記のようになります。
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Route::get('login/github', 'Auth\LoginController@redirectToProvider');
Route::get('login/github/callback', 'Auth\LoginController@handleProviderCallback');
複数のプロバイダーで認証を実装する場合は、引数に変数を用いて変換します。
Route::get('/login/{provider}', 'Auth\LoginController@redirectToProvider')->where('social', 'github|google|facebook');
Route::get('/login/{provider}/callback', 'Auth\LoginController@handleProviderCallback')->where('social', 'github|google|facebook');
Controllerの設定
設定した情報からSNS認証ログインを実装します。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Auth;
use Socialite;
use App\User;
use App\IdentityProvider;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = '/home';
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function redirectToProvider($social)
{
return Socialite::driver($social)->redirect();
}
public function handleProviderCallback($provider)
{
try {
$user = Socialite::driver($provider)->user();
} catch (Exception $e) {
return redirect('/login');
}
$authUser = $this->findOrCreateUser($user, $provider);
Auth::login($authUser, true);
return redirect($this->redirectTo);
}
public function findOrCreateUser($providerUser, $provider)
{
$account = IdentityProvider::whereProviderName($provider)
->whereProviderId($providerUser->getId())
->first();
if ($account) {
return $account->user;
} else {
$user = User::whereEmail($providerUser->getEmail())->first();
if (!$user) {
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
]);
}
$user->IdentityProviders()->create([
'provider_id' => $providerUser->getId(),
'provider_name' => $provider,
]);
return $user;
}
}
}
redirectToProvider()
メソッドは、ユーザーをOAuthプロバイダーに送信します。
handleProviderCallback()
メソッドは、プロバイダーからユーザーの情報を取得します。
findOrCreateUser()
メソッドは、ユーザーがまだ存在しない場合にユーザーを作成して、
プロバイダーから取得した情報をidentity_providers
テーブルに登録します。
コンフィグレーション
Socialite
を使用する前に、アプリケーションが使用するOAuthサービスの認証情報を設定します。
services.phpの設定
認証情報を必要とする各プロバイダーの設定を行います。
return [
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/login/github/callback',
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/login/google/callback',
],
'facebook' => [
'client_id' => env('FACEBOOK_APP_ID'),
'client_secret' => env('FACEBOOK_APP_SECRET'),
'redirect' => env('APP_URL') . '/login/facebook/callback',
],
];
app.php
でsocialiteの設定を行います。
'providers' => [
// 追加
Laravel\Socialite\SocialiteServiceProvider::class,
],
'aliases' => [
// 追加
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
],
.envの設定
.envファイルにて、各プロバイダーで取得したクライアントIDとシークレットIDを設定します。
GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
GOOGLE_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
FACEBOOK_APP_ID=xxxxxxxxxxxxxxx
FACEBOOK_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Viewの設定
ログイン画面のテンプレートに追記します。
(※下記の例では、Bootstrapを使用しています。)
<div class="form-group row mt-5">
<label for="name" class="col-sm-4 col-form-label text-md-right">SNSログイン</label>
<div class="col-md-6">
<a href="{{ url('login/google')}}" class="btn btn-danger"><i class="fa fa-google"> Google</i></a>
<a href="{{ url('login/facebook')}}" class="btn btn-primary"><i class="fa fa-facebook"> Facebook</i></a>
<a href="{{ url('login/github')}}" class="btn btn-secondary"><i class="fa fa-github"> GitHub</i></a>
</div>
</div>
デフォルトのログインテンプレートに追記すると、このように表示されます。
これで、各プロバイダーの情報を使ってログインができるようになります。