Cartalyst/SentinelとLaravel/Socialteを連携させる

Cartalyst/Sentinelにもソーシャルアカウントと連携する機能があるのですが、有料のパッケージとなっています。Sentinel自体、非常に使いやすいので、お金のある方はそちらを使用されたほうが良いと思います。

LaravelにもLaravel/Socialteというパッケージが用意されています。認証システムごとLaravel製に乗り換えてもよかったのですが、慣れているSentryも使いたかったので、SentinelとLaravel/Socialiteを連携させてみました。

すでにLaravelとSentinelの導入は終わっているものとします。

マニュアルに従い、composerでlaravel/socialiteをインストールします。

composer require laravel/socialite
'providers' => [
    // その他のサービスプロバイダ…
    Laravel\Socialite\SocialiteServiceProvider::class,
],
'aliases' => [
    'Socialite'  => Laravel\Socialite\Facades\Socialite::class,
],

config/services.phpに各ソーシャルアカウントの情報を記録します。ファイル内に直接secret keyなどを記載するのは好ましくないので、.envファイル内に記載するようにします。

    'google'   => [
        'client_id'     => getenv('GOOGLE_CLIENT_ID'),
        'client_secret' => getenv('GOOGLE_CLIENT_SECRET'),
        'redirect'      => getenv('GOOGLE_URL'),
    ],
    'facebook' => [
        'client_id'     => getenv('FACEBOOK_CLIENT_ID'),
        'client_secret' => getenv('FACEBOOK_CLIENT_SECRET'),
        'redirect'      => getenv('FACEBOOK_URL'),
    ],
    'twitter'  => [
        'client_id'     => getenv('TWITTER_CLIENT_ID'),
        'client_secret' => getenv('TWITTER_CLIENT_SECRET'),
        'redirect'      => getenv('TWITTER_URL'),
    ],
GOOGLE_CLIENT_ID=あなたのCLIENT_ID
GOOGLE_CLIENT_SECRET=あなたのCLIENT_SECRET
GOOGLE_URL=あなたのCALLBACK_URL

FACEBOOK_CLIENT_ID=あなたのCLIENT_ID
FACEBOOK_CLIENT_SECRET=あなたのCLIENT_SECRET
FACEBOOK_URL=あなたのCALLBACK_URL

TWITTER_CLIENT_ID=あなたのCLIENT_ID
TWITTER_CLIENT_SECRET=あなたのCLIENT_SECRET
TWITTER_URL=あなたのCALLBACK_URL

usersテーブルを変更します。本当はマイグレーションファイルを作成して、カラムを変更するべきなのですが、Sentinelのマイグレーションを実行する前だったので、直接変更してしまいました。必須ではない独自の項目なども入っています。適宜読み替えてください。

        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('email')->nullable();
            $table->string('password')->nullable();
            $table->text('permissions')->nullable();
            $table->timestamp('last_login')->nullable();
            $table->string('name')->nullable();
            $table->string('prof', 1000)->nullable();
            $table->string('photo', 500)->nullable();
            $table->string('nickname', 40)->nullable();
            $table->string('google_id')->nullable();
            $table->string('facebook_id')->nullable();
            $table->string('twitter_id')->nullable();
            $table->timestamps();
            $table->softDeletes();
            $table->engine = 'InnoDB';
            $table->unique('email');
        });

コールバックURLなどを有効にするためrouteの設定を行います。

Route::get('login/{provider}', [
    'as'   => 'redirect',
    'uses' => 'AuthController@redirectToProvider',
]);
Route::get('/login/{provider}/callback', [
    'as'   => 'callback',
    'uses' => 'AuthController@handleProviderCallback',
]);

どっかのページに、適当にログインリンクを作成します。

<a href="login/google">グーグルアカウントでログイン</a>

コントローラーを作成します

<?php namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Cartalyst\Sentinel\Checkpoints\NotActivatedException;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use App\Http\Controllers\Controller;
use App\User;

use Socialite;
use Validator;
use Redirect;
use Response;
use Sentinel;
use Session;

class AuthController extends Controller
{
    public function redirectToProvider($provider = null)
    {
        if (!config("services.$provider")) {
            Response::make("Not Found", 404);
        } //just to handle providers that doesn't exist
        return Socialite::driver($provider)->redirect();
    }

    /* 4 cases:
        1.  User register for the first time with Oauth
        2.  User sign in with Oauth, but is already registered
        3.  User sign in with Oauth, but has already used another Oauth service
        4.  User sign with an already used Oauth service
    */
    public function handleProviderCallback($provider = null)
    {
        try {
            $user = Socialite::driver($provider)->user();
        } catch (Exception $e) { // Cannot retrieve OAuth user data
            return Redirect::to('login');
        }
//        dd($user);
        $password = str_random(10);
        $OAuthUser = $this->findOrCreateUser($user, $provider, $password);

        try {
            $user = Sentinel::findById($OAuthUser->id);

            if (Sentinel::authenticateOauth($user)) {
                Sentinel::login($user);

                return Redirect::route('home')->with('success', 'Welcome <b>' . $user->email . '!</b>');
            } else {
                return Redirect::route('login')->with('error', 'Cannot authenticate.');
            }
        } catch (NotActivatedException $e) {

            return Redirect::route('login')->with('error', $e->getMessage());
        }
    }


    private function findOrCreateUser($user, $provider, $password)
    {
        if ($userExist = User::where('email', '=', $user->email)->first()) {
            if ($userProvider = User::where($provider . '_id', '=',
                $user->id)->first()
            ) { // User is already registered with this oauth service
                return $userProvider;
            } else { // User exists but has never used this service provider before
                // Update user with new provider_id
                $new_provider = $provider . '_id';
                $userExist->$new_provider = $user->id;
                $userExist->save();

                return $userExist;
            }
        } else {// Register and activate new user and proceed to authentication. Return password.
            $credentials = [
                'email'           => $user->email,
                'password'        => $password,
                $provider . '_id' => $user->id,
                'photo'           => $user->avatar,
                'nickname'        => $user->nickname,
                'name'            => $user->name,
            ];

            $user = Sentinel::register($credentials, true);

            return $user;
        }
    }
}

Sentinelにファンクションを追加します。

public function authenticateOauth($user, $remember = false, $login = true)
{
    $response = $this->fireEvent('sentinel.authenticating', $user, true);

    if ($response === false) {
        return false;
    }

    if (! $this->cycleCheckpoints('login', $user)) {
        return false;
    }

    if ($login === true) {
        $method = $remember === true ? 'loginAndRemember' : 'login';

        if (! $user = $this->{$method}($user)) {
            return false;
        }
    }

    $this->fireEvent('sentinel.authenticated', $user);

    return $this->user = $user;
}

これでいけるはずです。

スポンサーリンク
レクタングル大