Socialiteを使ってLaravelでTwitterログイン機能を実装

# 環境
 - PHP 7.2.18
 - Laravel Framework 5.5.45
 - Socialite 3.3
 
# はじめに
今回は、LaravelでSNSログインする際にめんどくさいことを色々やってくれるライブラリの`Socialite`を使って、Twitterログインを実装した時の話を書きます。
※かなり前（投稿から約4ヶ月前）に詰まった話なので、記憶は曖昧ですが書き記します。

# まずはTwitter Developersからアプリの作成
Twitterログイン機能を作るには、まず今回の実装用にTwitter側でアプリを立てておかなければなりません。
そのアプリを作成するためには、以下のサイトにログインしてアプリを新規作成してください。
[Twitter Developers](https://developer.twitter.com/)

アカウントやアプリの細かい作成や設定方法は、[こちらのサイト](https://note.mu/koushikagawa/n/ne3b15a0d7489)で詳しく説明されているのでそちらをご参照ください。


# Socialiteのインストールと設定
### Socialiteってなんぞ…？
現在、SNSログインを実装しようとする際、基本的に認証認可はOAuthかOpenIdConnectというプロトコルを利用します。これは、安全面を加味した堅固なプロトコルだからです。このプロトコルを利用して認証認可の実装を行うには、少々複雑な実装をしなくてはなりません。でも、昨今のwebサービスでは欠かせないのがSNSログイン。そこで、Laravelではその実装のお手伝いをしてくれるライブラリが存在していて、それが`Socialite`というわけです！
### インストール

```
$ composer require laravel/socialite
```
### config/app.php の設定
Socialiteを利用するためにサービスプロバイダへの追加とaliasをはっておきます。

 - サービスプロバイダへの追加

```php:config/app.php
'providers' => [
.
.
/**
* Socialite
*/
Laravel\Socialite\SocialiteServiceProvider::class,
.
.
],
```

 - aliasをはる

```php:config/app.php
'aliases' => [
.
.
'Socialite' => Laravel\Socialite\Facades\Socialite::class,
.
.
],
```

### `client_id`や`client_secret` の設定
OAuthやOpenIdConnectに必要な、`client_id`や`client_secret`を設定

```php:config/services.php
return[
.
.
    /**
     * socialite Settings
     */
    'twitter' => [
        'client_id'     => env('TWITTER_API_KEY'),
        'client_secret' => env('TWITTER_API_SECRET'),
        'redirect'      => env('TWITTER_CALLBACKURL'),
    ],
.
.
]
```
```php:.env
# Twitter
TWITTER_API_KEY= Twitter Developers で作成したアプリのAPI key
TWITTER_API_SECRET= Twitter Developers で作成したアプリのAPI secret key
TWITTER_CALLBACKURL= Twitterから戻ってくる際に叩いて欲しいURL(Twitter Developersにも設定する必要あり)
TWITTER_ACCESS_TOKEN= Twitter Developers で作成したアプリのAccess token
TWITTER_ACCESS_TOKEN_SECRET= Twitter Developers で作成したアプリのAccess token secret
```
これでSocialiteの準備は一旦完了！

# Twitter認証認可処理の実装
今回は面倒臭いので「ログイン処理」は割愛して「認証認可」までをします。
※ユーザー情報引っ張ってくるくらいまで
順を追って処理を書いていきます。
今回の処理フローを超簡単な図にするとこんな感じ↓

<img width="788" alt="スクリーンショット 2019-08-12 6.30.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183984/6a84e2fe-a72e-859a-52f2-2944101f1a58.png">

### 1.Socialiteのドライバーにリダイレクトさせる

```php:SnsBaseController.php
<?php

namespace App\Http\Controllers;

use Socialite;

class SnsBaseController extends Controller
{
  // 1. Socialiteのドライバーにリダイレクトさせる
  public function getAuth($provider) {
    return Socialite::driver($provider)->redirect();
  }
}
```
### 2.Twitterから戻ってくる際のエンドポイント作成

```php:SnsBaseController.php
<?php

namespace App\Http\Controllers;

use Socialite;

class SnsBaseController extends Controller
{
  // 1. Socialiteのドライバーにリダイレクトさせる
  public function getAuth($provider) {
    return Socialite::driver($provider)->redirect();
  }
  
  // 2. Twitterから戻ってくる際のエンドポイント作成
  public function authCallback($provider) {

  }
}
```

### 3.ユーザー情報の取得（ここでめちゃくちゃ詰んだ！）

```php:SnsBaseController.php
<?php

namespace App\Http\Controllers;

use Socialite;

class SnsBaseController extends Controller
{
  // 1. Socialiteのドライバーにリダイレクトさせる
  public function getAuth($provider) {
    return Socialite::driver($provider)->redirect();
  }
  
  // 2. Twitterから戻ってくる際のエンドポイント作成
  public function authCallback($provider) {
    // 3. ユーザー情報の取得（ここでめちゃくちゃ詰んだ！）
    $user_info = $this->getProviderUserInfo($provider);
    dd($user_info); //デバック用
  }

  // 3. ユーザー情報の取得（ここでめちゃくちゃ詰んだ！）
  private function getProviderUserInfo($provider){
    switch ($provider) {
        case 'twitter':
            return Socialite::driver($provider)->user();
        default:
            return null
    }
  }
}

```
### 4.ルーティング設定

```php:web.php
//Twitter
Route::get('sns/{provider}/login', 'SnsBaseController@getAuth');
Route::get('sns/{provider}/callback', 'SnsBaseController@authCallback');
```

# 今回詰んだ部分の話
前章の3番目の「ユーザー情報の取得」部分の処理で大きく詰まりました（1週間ほど）

**エラー箇所**

```php
Socialite::driver($provider)->user();
```

色んなサイトをみても、この実装で書かれているのがほとんでした。
この処理で実装して動かしてみても、「セッションが引き継げてない」みたいなエラーしか返してくれませんでした。（以下、エラー）

```
Type error: Argument 1 passed to League\OAuth1\Client\Server\Server::getTokenCredentials() must be an instance of League\OAuth1\Client\Credentials\TemporaryCredentials, null given, called in /vendor/laravel/socialite/src/One/AbstractProvider.php on line 113
```
`oauth.temp`が取得できないというエラーでした

「一連の処理のどこかでhttp通信とhttps通信が混合しているのか？」とか、「自分のCentOSのネットワーク設定が悪さしているのか？」とか色々考えていたんですが、どれも関係なく途方にくれました。
Twitterではなくて、googleの情報を引っ張ってくる際は、

```php
Socialite::driver($provider)->stateless()->user();
```
のように、`stateless()`メソッドを間に挟めばセッションの引き継ぎを行なってくれるらしいのですが、Twitterだとこのメソッド自体存在しないらしく`undefined method`が出てしまいました。

いろいろ試した結果下記コードに修正すると動きました。

```php
Socialite::driver($provider)->userFromTokenAndSecret(env('TWITTER_ACCESS_TOKEN'), env('TWITTER_ACCESS_TOKEN_SECRET'));
```

この書き方は、access_tokenとaccess_token_secretを取得できている際に直接指定して情報取得するやり方です。
ただ、現在ではどちらの書き方でも動いているので、何が原因だったのか未だにわからずです。。。

###結論
~~このような困難を経て最終的に`TWITTER_ACCESS_TOKEN`と`TWITTER_ACCESS_TOKEN_SECRET`を使ったユーザー情報取得方法にたどり着いたわけですが、~~
最終的に何が問題だったのかわからないまま何処かのタイミングで上記エラーが解消されました。。。

各SNSで使うメソッドが違ったり古い情報に左右されたりなど、ライブラリ頼りでは混乱が多いですね。。。（現に1週間詰まった）
特にSNS周りだと、各々のSNSのバージョンアップだったり仕様変更だったりが割と早めなのでライブラリもそれに対応していくのが大変で昔の書き方だと動かないとかが多いんでしょうね…

今後はSNSログイン周りをライブラリに任せないで自分で実装しようと思いました。頭では処理フローを理解できているつもりですが、色々複雑なので次なる挑戦として頑張りたいと思います！