Laravel5.3でソーシャル連携ウェブサービスの土台を作る(Socialite/Twitter)


これ、「[Laravel5.2でソーシャル連携ウェブサービスの土台を作る。](http://qiita.com/maimai-swap/items/5b2f285f7647690472fc) 」のLaravel5.3バージョンです。

5.3ではroutingの設定がフォルダとmiddleware別のファイルに別れたりしました。
laravel/socialiteも前は生のまんま使ったのですが、今回は、socialiteproviders/twitterという、socialite用のプロバイダのライブラリを使いました。
5.2ではAuthControllerとしてログインや会員周りが一つのコントローラーになっていましたが、5.3では4つのControllerに分割されています。（ForgotPasswordController.php、LoginController.php、RegisterController.php、ResetPasswordController.php）

ちょいちょい変わってそうなので、ひととおり実装しました。

## プロジェクトの初期化

* 最新のLaravelを落とすと5.3になります。(2017/8/23現在5.4が最新です）

```
composer create-project --prefer-dist laravel/laravel アプリ名
```

## Homesteadで仮想環境作る


* laravel用の仮想環境のライブラリをrequireする

```
cd アプリ名
composer require laravel/homestead --dev
```

* 仮想環境用の設定を自動作成

```
php vendor/bin/homestead make
```

* IPとhost名の設定、Hostsの設定

プロジェクト直下にHomestead.yamlってファイルができるので、IPの設定を見る。

```Homestead.yaml
ip: "192.168.10.10"
memory: 2048
cpus: 1
hostname: アプリのドメイン名
name: アプリ名
```

hostnameをアプリのドメイン名を好きな名前に変えて、/etc/hostsに レコードをたす。

```/etc/hosts
192.168.10.10 アプリのドメイン名
```


* 仮想環境起動

```
vagrant up
```

これで待っているとboxのDownloadと仮想マシンの初期化がされます。
Laravelのhomesteadは、ここにホスティングされてます。（一応）
https://atlas.hashicorp.com/laravel/boxes/homestead

* なんかvagrant sshができなかったので、いろいろ調べてたら、Ubuntuの問題っぽかった。これ↓したらなおった。

```Vagrantfile
config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--cableconnected1", "on"]
end
```

参考：[Vagrant で bento/ubuntu-16.04 を vagrant up すると Connection timeout になってしまう](http://qiita.com/chatii0079/items/b60abd6ab2822b49290a)

# ログイン機能を作る

* 最初に.envにアプリのURLを書いておく

```.env
APP_URL=https://アプリのドメイン名
```

https://laravel.com/docs/5.3/authentication

* ユーザー名などで絵文字とか入る可能性があるので、dbのcharsetを``utf8mb4``に変えておく。

```config/database.php:62
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
```

* vagrantに入る

```
vagrant ssh
```

* アプリのディレクトリにcdして、Laravelに最初から用意されている認証用のmigrationとルーティングの設定を実行する

```
cd アプリ名 
php artisan make:auth
php artisan migrate
```

## 出来上がったものを確認して見る

実行するとルーティングができてるので、みて見ると、Controllerとか出来上がってる。

```
php artisan route:list
```

| Domain | Method | URI | Name | Action | Middleware |
|--------|--------|-----|------|------|------|
|        | GET/HEAD | /  ||Closure | web  |
| | GET/HEAD | api/user | | Closure | api,auth:api |
| | GET/HEAD | home | | App\Http\Controllers\HomeController@index | web,auth |
| | GET/HEAD | login | login | App\Http\Controllers\Auth\LoginController@showLoginForm | web,guest |
| | POST | login | | App\Http\Controllers\Auth\LoginController@login | web,guest |
| | POST | logout | logout | App\Http\Controllers\Auth\LoginController@logout | web |
| | POST | password/email | | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail | web,guest |
| | GET/HEAD | password/reset | | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web,guest |
| | POST | password/reset | | App\Http\Controllers\Auth\ResetPasswordController@reset | web,guest |
| | GET/HEAD | password/reset/{token} | | App\Http\Controllers\Auth\ResetPasswordController@showResetForm | web,guest |
| | GET/HEAD | register | register | App\Http\Controllers\Auth\RegisterController@showRegistrationForm | web,guest |
| | POST | register | | App\Http\Controllers\Auth\RegisterController@register | web,guest |


## ざっと動いてるか動作確認

この状態で、アクセス``http://アプリ名/``して見ると、ログインリンクとかついてる。作りやすいな〜。もうソーシャル連携とかいいのでは〜って気分になってくる。

### トップページ


![スクリーンショット 2016-11-28 2.59.51.png](https://qiita-image-store.s3.amazonaws.com/0/30411/5298e289-17e5-213f-879e-5935a6679efc.png "スクリーンショット 2016-11-28 2.59.51.png")
### 会員登録画面

![スクリーンショット 2016-11-28 3.06.05.png](https://qiita-image-store.s3.amazonaws.com/0/30411/751153cd-339d-b976-2ea2-390694869b83.png "スクリーンショット 2016-11-28 3.06.05.png")
### ログイン画面
![スクリーンショット 2016-11-28 3.05.51.png](https://qiita-image-store.s3.amazonaws.com/0/30411/61c78372-4094-4040-d97f-f2ae8cd813f9.png "スクリーンショット 2016-11-28 3.05.51.png")

### パスワードリセット
![スクリーンショット 2016-11-28 3.18.07.png](https://qiita-image-store.s3.amazonaws.com/0/30411/6295fab9-1441-5c86-d25e-f144ebe51557.png "スクリーンショット 2016-11-28 3.18.07.png")


# ソーシャル連携を作る

laravelの公式ライブラリのSocialiteでソーシャル連携を作る
https://github.com/laravel/socialite

## Twitter経由のログインとユーザー登録

ユーザー登録はメアドで登録ってのも全然ありだが、最初はtwitter経由で登録してあとからTwitter連携外すとか、よくやると思うので、そのようにする。

このサイトの言うこと聞いとけば大体できそう。

[Using Twitter Authentication For Login in Laravel 5](http://goodheads.io/2015/08/24/using-twitter-authentication-for-login-in-laravel-5/)

### socialiteのインストール

```
composer require laravel/socialite
```

### ライブラリの読み込み

```config/app.php

'providers' => [
    // Other service providers...

    Laravel\Socialite\SocialiteServiceProvider::class,
],
```

```config/app.php

'aliases' => [
	snip...
    // Other service providers...

    'Socialite' => Laravel\Socialite\Facades\Socialite::class,
],
```

### twitter用のproviderインストール

参考：[Socialite Providers](http://socialiteproviders.github.io/providers/twitter/)

```
composer require socialiteproviders/twitter
```

### ライブラリの読み込み

```config/app.php

'providers' => [
    // Other service providers...
    Laravel\Socialite\SocialiteServiceProvider::class,
    \SocialiteProviders\Manager\ServiceProvider::class, // ←これ追加
],
```

### イベントリスナーにとうろく

```app/Providers/EventServiceProvider.php
    protected $listen = [
//        'App\Events\SomeEvent' => [
//            'App\Listeners\EventListener',
//        ],
        \SocialiteProviders\Manager\SocialiteWasCalled::class => [
            // add your listeners (aka providers) here
            'SocialiteProviders\Twitter\TwitterExtendSocialite@handle',
        ],
    ];

```

最初から書いてある``'App\Events\SomeEvent'...``のところは、よくわからないけどコメントアウトした。


### oauth APIのキーを.envに書く。

oauth APIのキーとかはここからとる。
https://apps.twitter.com/


```.env
TWITTER_CLIENT_ID=XXXXXXX
TWITTER_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXX
```

### configのservice.phpに設定を追加

```config/service.php
    'twitter' => [
        'client_id' => env('TWITTER_CLIENT_ID'),
        'client_secret' => env('TWITTER_CLIENT_SECRET'),
        'redirect' => env('URL') . '/twitter/login'
    ],
```

### twitterのSocialiteFactoryが動くようにする。

AppServiceProvider.phpに起動function作成

```app/Providers/AppServiceProvider.php

    private function bootTwitterSocialite()
    {
        $twitter = $this->app->make('Laravel\Socialite\Contracts\Factory');
        $twitter->extend(
            'spotify',
            function ($app) use ($twitter) {
                $config = $app['config']['services.twitter'];
                return $twitter->buildProvider(TwitterProvider::class, $config);
            }
        );
    }
    
```

### twitter用のControllerを作る。

```app/Http/Controllers/Auth/TwitterAuthController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Laravel\Socialite\Facades\Socialite;

class TwitterAuthController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest', ['except' => 'logout']);
    }

    public function redirectToProvider()
    {
        return Socialite::with('twitter')->redirect();
    }

    public function handleProviderCallback()
    {
        $user = Socialite::with('twitter')->user();
        // $user->token;
        // 初めて来た人はユーザー登録、すでにIDがあるひとは、とってくる
        $authUser = $this->findOrCreateUser($user);
        // その後ログイン
        Auth::login($authUser, true);
        return redirect('home');
    }
    
    /**
     * Return user if exists; create and return if doesn't
     *
     * @param $twitterUser
     * @return User
     */
    private function findOrCreateUser($twitterUser)
    {
        $authUser = User::where('twitter_id', $twitterUser->id)->first();

        if ($authUser){
            return $authUser;
        }
        
        // ここは後で書き直します。
    }
}
```

### ツイッター経由のloginとcallbackのルーティング追加


```routes/web.php
Route::get('twitter/login', 'Auth\TwitterAuthController@redirectToProvider');
Route::get('twitter/callback', 'Auth\TwitterAuthController@handleProviderCallback');
```

### ツイッターのアカウント情報を保存するテーブルを作る

ログイン後にaccess_tokenとsecret、ユーザーIDなどを入れるテーブルを作る。

```
php artisan make:migration create_twitter_users_table
```

マイグレーションファイルの編集

```database/migrations/日付_create_twitter_users_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTwitterUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('twitter_users', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id');
            $table->bigInteger('twitter_user_id');
            $table->string('email')->nullable();
            $table->string('name');
            $table->string('nickname');
            $table->string('avatar',1024);
            $table->string('token',1024);
            $table->string('token_secret',1024);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('twitter_users');
    }
}

```

カラム名がこの感じになってるのは、socialiteprovidersのなかみるとわかるけど、
``vendor/socialiteproviders/twitter/src/Server.php``
いろんなプロバイダでフィールドが違うのを共通化してる。

もしかして、provider_accounts ってTableつくればよかっただけかも、、。provider_nameとか入れて。

```
php artisan migrate
```

modelにrelationを入れておく

```app/User.php
    public function twitter_users()
    {
        return $this->hasMany('App\TwitterUser');
    }
```

```app/TwitterUser.php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class TwitterUser extends Model
{

    /**
     * Get the user that owns the twitter user.
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }

}

```

保存するフィールドのmassassignmentを設定しておく。

```app/TwitterUser.php
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['twitter_user_id','email','name',
        'nickname','avatar','token','token_secret',];
```

## 準備完了なので、ControllerのfindOrCreateUserを書き直します。

こんな感じ。

```app/Http/Controllers/Auth/TwitterAuthController.php
    
    /**
     * Return user if exists; create and return if doesn't
     *
     * @param $twitterUser
     * @return User
     */
    private function findOrCreateUser($twitterUser)
    {
        $twitterUser = TwitterUser::where('twitter_user_id', $twitter_account->id)->first();
        if($twitterUser) {
            $authUser = $twitterUser->user;
            if ($authUser){
                return $authUser;
            }
            throw new \Exception("twitter userがいるけどuserテーブルに紐づいていない");
        }
        
        $user = User::create([
            'name' => $twitter_account->name,
            'email' => str_random(16)."@example.com", //仮で入れる
            'password' => bcrypt(str_random(16)), //仮で入れる
        ]);

        $twitter_user = new TwitterUser([
            'twitter_user_id' => $twitter_account->id,
            'email' => $twitter_account->email,
            'name' => $twitter_account->name,
            'nickname' => $twitter_account->nickname,
            'avatar' => $twitter_account->avatar,
            'token' => $twitter_account->token,
            'token_secret' => $twitter_account->tokenSecret,
        ]);

        $user->twitter_users()->save($twitter_user);
        return $user;
    }
}
```

これで、``https://あぷりのURL/twitter/login``にアクセスすると、新規ユーザーを作成し、ログインまたは既存ユーザーでtwitteridがあったら普通にログイン。
ができるようになります。

twitter経由のログインの場合、メールアドレスとパスワードを仮で設定してあるので、メアドからログインできないようになっていますので適宜ユーザビリティ〜あげちゃってください。

## ソースコードはgithubにアップしました。
https://github.com/maimai-swap/laravel5.3-twitter-oauth
ダウンロードしてそのまま使えると思います。