[備忘録] Laravel5.5をSocialiteでTwitterログインをやろうとしたらハマった[Laravel]

# 概要

Laravel5.5でTwitterログインが必要なサイトを作る過程でQiitaの情報を鵜呑みにしてたらハマりましたのでまとめることにしました。最終的にはYouTubeの動画で解決しましたのでそれをまとめたいと思います。
ちなみにLaravelのローカルサーバーで挙動を確認することはできませんので情報が全くありません。
(できるのかもしれないけどTwitterAPIでの登録URLでローカルサーバーのアドレスは受け付けてくれなかった)

## Laravelのプロジェクトファイルをインストールする

```terminal
$ composer create-project --prefer-dist laravel/laravel twitterlogin "5.5.*"
```

## ユーザー認証の設定

```terminal
$ php artisan make:auth

[結果]
Authentication scaffolding generated successfully.
```

## mysqlのDBの作成

```terminal
$ mysql -u root

mysql>
```

```terminal
CREATE DATABASE `データベースの名前` DEFAULT CHARACTER SET utf8;

[結果]
Query OK, 1 row affected (0.00 sec)
```

## マイグレーション

```.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=データベースの名前
DB_USERNAME=root
DB_PASSWORD=
```

```terminal
$ php artisan migrate

SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes (SQL: alter table `users` add unique `users_email_unique`(`email`))  

SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes 
```

解決方法はこちら
(参考)
[Laravel5.5でphp artisan migrateした時にエラー「1071 Specified key was too long; max key length is 767 bytes」が発生](https://akamist.com/blog/archives/982)

```App/Provider/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema; // ← 追記

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191); // ← 追記
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
```

これを設定した後に

```terminal
$ php artisan migrate:fresh

[結果]
Dropped all tables successfully.
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

```

を実行すれば良い

## Socialiteのインポート

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

```config/app.php

    'providers' => [
(省略)

        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
        Laravel\Socialite\SocialiteServiceProvider::class,　// ← 追記
    ],


    'aliases' => [
(省略)

        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,
        'Socialite' => Laravel\Socialite\Facades\Socialite::class,　// ← 追記
    ],
```

## LoginControllerの実装

`App\Http\Controllers\Auth\LoginController.php`においてSocialLoginの機能を実装します。

```LoginController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

use Socialite; // <- 追記
use Auth; // <- 追記
use App\User; // <- 追記

class LoginController extends Controller
{
(省略)

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

//  追加
    public function socialLogin($social)
    {
        return Socialite::driver($social)->redirect();
    }

//  追加
    public function handleProviderCallback($social)
    {
        $userSocial = Socialite::driver($social)->user();
        $user = User::where(['email' => $userSocial->getEmail()])->first();
        if ($user) {
            Auth::login($user);
            return redirect()->action('HomeController@index');
        } else {
            return view('auth.register', [
                'name' => $userSocial->getName(),
                'email' => $userSocial->getEmail()
            ]);
        }
    }
}

```

## Routeの設定

```web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

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

// 追加
Route::get('/login/{social}', 'Auth\LoginController@socialLogin')
    ->where('social', 'twitter|facebook|linkedin|google|github');

// 追加
Route::get('/login/{social}/callback', 'Auth\LoginController@handleProviderCallback')
    ->where('social', 'twitter|facebook|linkedin|google|github');
```

## Twitterログインボタンの設置

`login.blade.php`と`register.blade.php`にTwitterのログインボタンを設置します。

```register.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Register</div>

                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('register') }}">
                        {{ csrf_field() }}

                        <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
                            <label for="name" class="col-md-4 control-label">Name</label>

                            <div class="col-md-6">
                                @if(!empty($name))
                                    <input id="name" type="text" class="form-control" name="name" value="{{ $name }}" required autofocus>
                                @else
                                    <input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus>
                                @endif

                                @if ($errors->has('name'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>

                            <div class="col-md-6">
                                @if(!empty($email))
                                    <input id="email" type="email" class="form-control" name="email" value="{{ $email }}" required>
                                @else
                                    <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>
                                @endif

                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Register
                                </button>
                            </div>
                        </div>

                        <div class="form-group">
                            <label class="col-md-4 control-label">Or Register with</label>
                            <div class="row">

                            </div>
                            <div class="col-md-8 col-md-offset-2">
                                <a href="{{ url('login/twitter') }}" class="btn btn-social-icon btn-twitter"><i class="fa fa-twitter"></i> Twitter</a>
                            </div>
                        </div>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

```

```login.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Login</div>

                <div class="panel-body">
                    <form class="form-horizontal" method="POST" action="{{ route('login') }}">
                        {{ csrf_field() }}

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required autofocus>

                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <div class="checkbox">
                                    <label>
                                        <input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> Remember Me
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-8 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    Login
                                </button>

                                <a class="btn btn-link" href="{{ route('password.request') }}">
                                    Forgot Your Password?
                                </a>
                            </div>
                        </div>

                        <div class="form-group">
                            <label class="col-md-4 control-label">Or login with</label>
                            <div class="row">

                            </div>
                            <div class="col-md-8 col-md-offset-2">
                                <a href="{{ url('login/twitter') }}" class="btn btn-social-icon btn-twitter"><i class="fa fa-twitter"></i> Twitter</a>
                            </div>
                        </div>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

```

<img width="812" alt="スクリーンショット 2018-11-06 20.52.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/d56c8310-01e5-fe34-d083-ea8db712729c.png">


## twitterのAPIキーを実装する

`config\service.php`にtwitterのクライアントidなどを設定します。

```service.php
    'sparkpost' => [
        'secret' => env('SPARKPOST_SECRET'),
    ],

    'stripe' => [
        'model' => App\User::class,
        'key' => env('STRIPE_KEY'),
        'secret' => env('STRIPE_SECRET'),
    ],
    
    // 追加する
    'twitter' => [
        'client_id' => 'your client id ', // <- Consumer API keys (API keys)
        'client_secret' => 'your client secret key', // <- Consumer API keys (API secret key)
        'redirect' => 'twitter callback url'
    ],
```

これでやっとSocialiteでTwitterログインができるようになりました。

## 注意

今の時点でTwitterのAPIの設定でcallback url に`localhost`のurlが設定できませんので開発環境でデバッグすることができませんでした。
つまり、`http://127.0.0.1:8000`の指定ができませんでした。
ネットの情報では`http://127.0.0.1:8000`に指定できる前提が多かったですね。


そのため、わざわざテストのためにherokuにデプロイして確認していました。
callbackのurlをつど`http://[herokuのアプリ名]/login/twitter/callback`という感じにTwitterのAPIのcallbackUrlを変更して開発していました。
例えば、herokuのアプリ名を`twittersample`とかにすると
`http://twittersample.herokuapp.com`になるので
`http://twittersample.herokuapp.com/login/twitter/callback`といった感じに設定します。

(参考)
[[備忘録] Laravelをherokuにデプロイする(データベースはPostgreSQL) [メモ]](https://qiita.com/tamappe/items/14a4e6890ecef4d49d1f)

このcallbackurlが正しいのか判断できず何度もLaravelのプロジェクトファイルを削除したりherokuのアプリを削除したりハマっていました。
こんなしょうもない部分で他の人もハマって欲しくないので今回の記事を共有したいと思いました。

本当はハマらないのが一番いいです。