Laravel 5.3のSocialiteに含まれていないLINEやYahoo!JAPANなどの認証サービスを利用する方法


## やりたいこと
* 表題の通り、laravel/socialiteで、twitterやfacebookのアカウント認証はできるようになったが、LINEやYahoo!JAPANのアカウントでも認証できるようにしたい。

## 前提
* laravel/socialiteによる外部認証機能を使用していること

	> [Laravel 5.3で外部アカウントを使用した認証機能を実装する方法](http://qiita.com/LowSE01/items/7bb479cfe3a9ba7723d4)

## 導入手順、の前に

* 毎度毎度、以下の長い手順を踏んでいくのは非常に面倒なので、これらをまとめたものをpackagistに公開して、composer requireできるようにしました。

	> [Laravel 5.3のSocialiteに含まれていないLINEやYahoo!JAPANなどの認証サービスをcomposerを使ってサクッと実装する方法](http://qiita.com/LowSE01/items/d07562098ca416076b2c)

## 導入手順

* 基本的に、以下の流れで実装する。
* 認証に使用するサービスのAPI仕様やらを見ながら、Providerをその仕様に合わせてガリガリ書いていけば、大体のサービスにおいては以下の手順でいける。。。と思う。

### 認証に使用するサービスごとにProviderを作成する。

* LINE用プロバイダ

	``` php:app/socialite/Two/LineProvider.php
	<?php
	
	namespace app\Socialite\Two;
	
	use Laravel\Socialite\Two\AbstractProvider;
	use Laravel\Socialite\Two\ProviderInterface;
	use Laravel\Socialite\Two\User;
	
	class LineProvider extends AbstractProvider implements ProviderInterface
	{
		protected function getAuthUrl($state)
		{
			return $this->buildAuthUrlFromBase('https://access.line.me/dialog/oauth/weblogin', $state);
		}
	
		protected function getTokenUrl()
		{
			return 'https://api.line.me/v1/oauth/accessToken';
		}
	
		protected function getUserByToken($token)
		{
			$response = $this->getHttpClient()->get('https://api.line.me/v1/profile', [
				'headers' => [
					'X-Line-ChannelToken' => $token,
				],
			]);
	
			return json_decode($response->getBody(), true);
		}
	
		protected function mapUserToObject(array $user)
		{
			return (new User())->setRaw($user)->map([
				'id'     => $user['mid'],
				'name'   => $user['displayName'],
				'avatar' => $user['pictureUrl'],
			]);
		}
	
		protected function getTokenFields($code)
		{
			return [
				'client_id'     => $this->clientId,
				'client_secret' => $this->clientSecret,
				'code'          => $code,
				'redirect_uri'  => $this->redirectUrl,
				'grant_type'    => 'authorization_code',
			];
		}
	}
	```

* Yahoo!JAPAN用プロバイダ
	
	``` php:app/socialite/Two/YahooProvider.php
	<?php
	
	namespace app\Socialite\Two;
	
	use Laravel\Socialite\Two\AbstractProvider;
	use Laravel\Socialite\Two\ProviderInterface;
	use Laravel\Socialite\Two\User;
	
	use GuzzleHttp\ClientInterface;
	
	class YahooProvider extends AbstractProvider implements ProviderInterface
	{
		protected $scopeSeparator = ' ';
	
		protected $scopes = [
			'openid',
			'profile',
			'email',
		];
	
		protected function getAuthUrl($state)
		{
			return $this->buildAuthUrlFromBase('https://auth.login.yahoo.co.jp/yconnect/v1/authorization', $state);
		}
	
		protected function getTokenUrl()
		{
			return 'https://auth.login.yahoo.co.jp/yconnect/v1/token';
		}
	
		protected function getUserByToken($token)
		{
			$response = $this->getHttpClient()->get('https://userinfo.yahooapis.jp/yconnect/v1/attribute?schema=openid', [
				'headers' => [
					'Authorization' => 'Bearer '.$token,
				],
			]);
	
			return json_decode($response->getBody(), true);
		}
	
		protected function mapUserToObject(array $user)
		{
			return (new User())->setRaw($user)->map([
				'id'         => $user['user_id'],
				'name'       => $user['name'],
				'email'      => $user['email'],
				'birth_year' => $user['birthday'],
				'gender'     => $user['gender'],
			]);
		}
	
		/**
		 * Basic認証が必要なのでOverWriteする。
		 * @param string $code
		 * @return mixed
		 */
		public function getAccessTokenResponse($code)
		{
			$postKey = (version_compare(ClientInterface::VERSION, '6') === 1) ? 'form_params' : 'body';
	
			$basic_auth_key = base64_encode($this->clientId.":".$this->clientSecret);
	
			$response = $this->getHttpClient()->post($this->getTokenUrl(), [
				'headers' => [
					'Authorization' => 'Basic '.$basic_auth_key,
					'Content-Type'  => 'application/x-www-form-urlencoded',
				],
				$postKey  => $this->getTokenFields($code),
			]);
	
			return json_decode($response->getBody(), true);
		}
	
		/**
		 * TokenFieldsに過不足があるのでOverWriteする。
		 * :Basic認証のため不要
		 *    - client_id
		 *    - client_secret
		 * :必須項目追加
		 *    + grant_type
		 * @param string $code
		 * @return array
		 */
		protected function getTokenFields($code)
		{
			return [
				'code'         => $code,
				'redirect_uri' => $this->redirectUrl,
				'grant_type'   => 'authorization_code',
			];
		}
	}
	```
	
### SocialiteManager、SocialiteServiceProviderクラスを作成する。

* Laravel\Socialite\SocialiteManagerクラスを継承して、作成したプロバイダを読み込むSocialiteManagerクラスを実装する

	``` php:app/socialite/SocialiteManager.php
	<?php
	
	namespace app\Socialite;
	
	class SocialiteManager extends \Laravel\Socialite\SocialiteManager
	{
		protected function createYahooDriver()
		{
			$config = $this->app['config']['services.yahoo'];
	
			return $this->buildProvider('app\Socialite\Two\YahooProvider', $config);
		}
	
		protected function createLineDriver()
		{
			$config = $this->app['config']['services.line'];
	
			return $this->buildProvider('app\Socialite\Two\LineProvider', $config);
		}
	}
	```

* Laravel\Socialite\SocialiteServiceProviderを継承して、作成したSocialiteManagerを呼び出すためのサービスプロバイダクラスを実装する。

	``` php:app/socialite/SocialiteServiceProvider.php
	<?php
	
	namespace app\Socialite;
	
	class SocialiteServiceProvider extends \Laravel\Socialite\SocialiteServiceProvider
	{
		public function register()
		{
			$this->app->singleton('Laravel\Socialite\Contracts\Factory', function ($app)
			{
				return new SocialiteManager($app);
			});
		}
	}
	```

### 設定変更

* SocialiteServiceProviderの向き先を、上記で作成したものに変える

	``` php:app/config/config.php
	<?php
	
	return [
	
		// ...
	
		'providers'       => [
	
			// ...
			
			//Laravel\Socialite\SocialiteServiceProvider::class,	//除外
			app\Socialite\SocialiteServiceProvider::class,			//追加
	
			// ...
			
		],
	
		// ...
	
	];
	
	```

* config/services.phpを編集する。

	``` php:config/services.php
	<?php
	
	return [
		
		// ...
		
		'line'   => [
			'client_id'     => env('LINE_CLIENT_ID'),
			'client_secret' => env('LINE_CLIENT_SECRET'),
			'redirect'      => env('LINE_REDIRECT'),
		],
		'yahoo'  => [
			'client_id'     => env('YAHOO_CLIENT_ID'),
			'client_secret' => env('YAHOO_CLIENT_SECRET'),
			'redirect'      => env('YAHOO_REDIRECT'),
		],
		
		// ...
		
	];
	
	```
			
* .env.xxxxxを編集

	``` .env.xxxxx
	# ...
	
	
	LINE_CLIENT_ID=(your-line-client-id)
	LINE_CLIENT_SECRET=(your-line-client-secret)
	LINE_REDIRECT=(your-line-redirect-url)
	
	YAHOO_CLIENT_ID=(your-yahoo-client-id)
	YAHOO_CLIENT_SECRET=(your-yahoo-client-secret)
	YAHOO_REDIRECT=(your-yahoo-redirect-url)
	
	# ...
	```

### 認証実行

* あとは、[Laravel 5.3で外部アカウントを使用した認証機能を実装する方法](http://qiita.com/LowSE01/items/7bb479cfe3a9ba7723d4)に書いた[コントローラの作成](http://qiita.com/LowSE01/items/7bb479cfe3a9ba7723d4#%E3%82%B3%E3%83%B3%E3%83%88%E3%83%AD%E3%83%BC%E3%83%A9%E4%BD%9C%E6%88%90)以下の手順を、それぞれのプロバイダ名に読み替えて実装すれば完成。