【Laravel】Laravel5.6をソーシャルログインに完全対応する【Socialite】


Webシステムへのユーザー登録は現状メールアドレスとパスワードによる認証が主流ですが、
メールの確認や登録フォーム入力の手間、パスワード紛失のリスクなどがありユーザーにとっても開発者にとっても面倒です。

TwitterやGoogleなどの既存アカウントによるソーシャルログインは、上記のような手間を解決しスムーズな登録が行えます。
また、初期値としてユーザー名やアイコン画像などの情報をシステム側へ自動インポート可能です。

今回は特にメジャーなTwitterとGoogleアカウントによるソーシャルログインをLaravelで実装します。

# 環境

* CentOS7
* PHP7.2
* MySQL5.6
* Laravel5.6

# 準備

* TwitterとGoogleの各APIキーを取得します。
* Twitterは最近開発ユーザーが承認制になったようで、利用目的などの軽い審査があります。
* Googleの方はウェブアプリケーション向けのプロジェクトを作成し、Google+のAPIを有効にします。

# Authパッケージの追加
```
	$ php artisan make:auth
```

# Socialiteのインストール

```
	$ composer require laravel/socialite
```

# 設定ファイルの編集

```php:laravel/config/app.php

	'providers' => [
		…
		Laravel\Socialite\SocialiteServiceProvider::class,
	],
	…
	'aliases' => [
		…
		'Socialite' => Laravel\Socialite\Facades\Socialite::class,
	],
```

```php:laravel/config/services.php

	'twitter' => [
		'client_id'     => env('TWITTER_KEY'),
		'client_secret' => env('TWITTER_SECRET'),
		'redirect'      => env('TWITTER_CALLBACKURL'),
	],

	'google' => [
		'client_id'     => env('GOOGLE_CLIENT_ID'),
		'client_secret' => env('GOOGLE_CLIENT_SECRET'),
		'redirect'      => env('GOOGLE_CALLBACKURL'),
	],
```

```php:laravel/.env

	TWITTER_KEY="CONSUMER KEY"
	TWITTER_SECRET="CONSUMER KEY(Secret)"
	TWITTER_CALLBACKURL="CALLBACK URL"

	GOOGLE_CLIENT_ID="CLient ID"
	GOOGLE_CLIENT_SECRET="CLIENT ID(Secret)"
	GOOGLE_CALLBACKURL="CALLBACK URL"
```

# マイグレーション

* デフォルトのUserテーブルはメール認証向けのためソーシャルログイン向けに編集します。
* パスワード関連のテーブルは不要なためマイグレーションファイルごと削除します。

```php:laravel/database/migrations/XXXX_create_users_table.php
	public function up()
	{
 		Schema::create('users', function (Blueprint $table) {
			$table->increments('id');
			$table->string('name');
			$table->integer('status')->default(0);
			$table->string('avatar');
			$table->string('login_type');
			$table->string('login_id')->unique();
			$table->rememberToken();
			$table->timestamps();
		});
	}
```

```
	$ php artisan migrate
```

# モデル

* インターフェイス「Illuminate\Contracts\Auth\Authenticatableを」をUserモデルに実装します。
* Userモデルだけはappディレクトリ直下に置くのが無難です。(Authパッケージで想定されているため)

```php:laravel/app/User.php

	namespace App;

	use Illuminate\Database\Eloquent\Model;
	use Illuminate\Notifications\Notifiable;
	use Illuminate\Contracts\Auth\Authenticatable as IAuthenticatable;
	use Illuminate\Auth\Authenticatable;

	class User extends Model implements IAuthenticatable
	{
	    use Authenticatable, Notifiable;

	    protected $fillable = [
	        'name', 'status', 'bio', 'avatar', 'login_type', 'login_id',
	    ];

	    protected $hidden = [
	        'password', 'remember_token',
	    ];

	}
```

# コントローラ

* パスワードと登録関連のコントローラは不要なので削除します。
* Loginコントローラを編集します。
* findOrCreate()では、DBを検索して存在すれば更新、なければ登録という処理を行っています。

```php:laravel/app/Http/Controllers/Auth/LoginController.php

    public function __construct()
    {
        $this->middleware('guest')->except(['logout', 'postLogout']);
    }

	public function index()
	{
		\Auth::logout();
		return redirect()->to('/');
	}

	// ログアウト
	public function postLogout()
	{
		\Auth::logout();
		return redirect()->to('/');
	}

	// Google認証ページを表示
	public function getGoogleRedirect()
	{
		return Socialite::driver('google')->redirect();
	}

	// Twitter認証ページを表示
	public function getTwitterRedirect()
	{
		return Socialite::driver('twitter')->redirect();
	}

	// Googleのコールバック
	public function getGoogleCallback()
	{
		try{
			$login_info = Socialite::driver('google')->stateless()->user();
		}catch(Exception $ex){
			return redirect('login/google');
		}
		$auth_user = $this->findOrCreate("GP", $login_info);
		\Auth::login($auth_user, true);

		return redirect()->to('/home');
	}

	// Twitterのコールバック
	public function getTwitterCallback()
	{
		try{
			$login_info = Socialite::driver('twitter')->user();
		}catch(Exception $ex){
			return redirect('login/twitter');
		}
		$auth_user = $this->findOrCreate("TW", $login_info);
		\Auth::login($auth_user, true);

		return redirect()->to('/home');
	}

	// ユーザー情報を返す。存在しない場合は新規登録
	protected function findOrCreate(string $login_type, $user_info)
	{
		$user = User::where('login_type', $login_type)->where('login_id', "$login_type{$user_info->id}")->first();

		if(! $user){
			// 存在しなければ新規登録
			$user = new User();
			$user->name       = $user_info->name;
			$user->status     = 1;
			$user->login_type = $login_type;
			$user->login_id   = "$login_type{$user_info->id}";
			$user->avatar     = $user_info->avatar_original;
			$user->save();
		}else{
			// ユーザー情報更新
			$user->name   = $user_info->name;
			$user->avatar = $user_info->avatar_original;
			$user->save();
		}

		return $user;
	}

```

# ビュー

* こちらもパスワードや登録関連のファイルは不要なので削除します。

# ルーティング

```php:laravel/routes/web.php

	Route::get('/', function () {
	    return view('welcome');
	})->name('login');

	Route::get('login/google',          'Auth\LoginController@getGoogleRedirect');
	Route::get('login/google/redirect', 'Auth\LoginController@getGoogleRedirect');
	Route::get('login/google/callback', 'Auth\LoginController@getGoogleCallback');

	Route::get('login/twitter',          'Auth\LoginController@getTwitterRedirect');
	Route::get('login/twitter/redirect', 'Auth\LoginController@getTwitterRedirect');
	Route::get('login/twitter/callback', 'Auth\LoginController@getTwitterCallback');

	Route::post('login/logout', 'Auth\LoginController@postLogout')->name('logout');

	Route::get('/home', 'HomeController@index')->name('home');

```

# まとめ

気を付ける点としては、GoogleのコールバックURLは登録したものと.envに記述するものを一致させる必要があります。

また、Loginコントローラのコンストラクタでlogin/logout/を認証から除外する記述をしています。
この記述がないとユーザーがログアウトできなくなってしまいます。

以上です。