Laravelの既存AuthからSentinelに移動する方法

準備

  • チュートリアルの通りにblogプロジェクト作成:
  • Authモジュール導入

php artisan make:auth

Sentinelへ異動する方

Sentinelへ異動するとしたらAuthに導入した時に作成されたClassを削除せずに継承したくて一部書き直しました。

Login/Logoutロジック


<?php

namespace App\Http\Controllers\Auth;

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

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest', ['except' => 'logout']);
    }
    public function attemptLogin(Request $request)
    {
    	$credentials = [
    			'email'    =>  $request->input('email'),
    			'password' =>  $request->input('password'),
    	];
    	
    	return Sentinel::authenticate($credentials,  $request->has('remember'));
    }
    public function authenticated(Request $request, $user)
    {
    	$credentials = [
    			'email'    => $request->input('email'),
    			'password' =>  $request->input('password'),
    	];
    	     	 
    	return Sentinel::validateCredentials($user, $credentials);
    }
    
    protected function sendLoginResponse(Request $request)
    {
    	$request->session()->regenerate();
    
    	$this->clearLoginAttempts($request);
    
    	return $this->authenticated($request, Sentinel::getUser())? redirect()->intended($this->redirectPath()):redirect('/home');
    }
    
    
    public function logout(Request $request)
    {
    	Sentinel::logout();
    	 
    	$request->session()->flush();
    
    	$request->session()->regenerate();
    
    	return redirect('/');
    }
    
}

パスワード忘れ


<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Support\Facades\Password;

use Illuminate\Http\Request;

use Sentinel,Reminder;

class ForgotPasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset emails and
    | includes a trait which assists in sending these notifications from
    | your application to your users. Feel free to explore this trait.
    |
    */

    use SendsPasswordResetEmails;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }
    
    public function sendResetLink(array $credentials)
    {
    	Reminder::removeExpired();
    	 
    	// First we will check to see if we found a user at the given credentials and
    	// if we did not we will redirect back to this current URI with a piece of
    	// "flash" data in the session to indicate to the developers the errors.
    	$user = Sentinel::findByCredentials($credentials);
    
    	if (is_null($user)) {
    		return Password::INVALID_USER;
    	}
    
    	$code = "";
    	$exists = Reminder::exists($user);
    	if ($exists) {
    		$code = $exists->code;
    	}
    	else {
    		$reminder = Reminder::create($user);
    		$code = $reminder->code;
    	}
    	// Once we have the reset token, we are ready to send the message out to this
    	// user with a link to reset their password. We will then redirect back to
    	// the current URI having nothing set in the session to indicate errors.
    	   
    	$user->sendPasswordResetNotification($code);

    	return Password::RESET_LINK_SENT;
    }
    
    public function sendResetLinkEmail(Request $request)
    {
    	
    	$this->validate($request, ['email' => 'required|email']);
    	 
    	// We will send the password reset link to this user. Once we have attempted
    	// to send the link, we will examine the response then see the message we
    	// need to show to the user. Finally, we'll send out a proper response.
    	$response = $this->sendResetLink(
    			$request->only('email')
    	);
    
    	return $response == Password::RESET_LINK_SENT ? $this->sendResetLinkResponse($response) : $this->sendResetLinkFailedResponse($request, $response);
    }
}

パスワードリセット


<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

use Illuminate\Support\Facades\Password;
use Illuminate\Http\Request;

use Closure;
use Sentinel,Reminder;

use Log;

class ResetPasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset requests
    | and uses a simple trait to include this behavior. You're free to
    | explore this trait and override any methods you wish to tweak.
    |
    */

    use ResetsPasswords;

    /**
     * Where to redirect users after resetting their password.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }
    public function reset(Request $request)
    {
    	$this->validate($request, $this->rules(), $this->validationErrorMessages());
        	 
    	// Here we will attempt to reset the user's password. If it is successful we
    	// will update the password on an actual user model and persist it to the
    	// database. Otherwise we will parse the error and return the response.
    	
    	$response = $this->doReset(
    			$this->credentials($request), function ($user, $token, $password) {
    				Log::debug($user);
    				Log::debug($token);
    				Log::debug($password);
    				
    				$this->resetPassword($user, $token, $password);
    			}
    	);
    
    	// If the password was successfully reset, we will redirect the user back to
    	// the application's home authenticated view. If there is an error we can
    	// redirect them back to where they came from with their error message.
    	return $response == Password::PASSWORD_RESET? $this->sendResetResponse($response) : $this->sendResetFailedResponse($request, $response);
    }
    private function doReset(array $credentials, Closure $callback)
    {
    	// If the responses from the validate method is not a user instance, we will
    	// assume that it is a redirect and simply return it from this method and
    	// the user is properly redirected having an error message on the post.
    	$user = $this->validateReset($credentials);
    
    	if (! $user instanceof CanResetPasswordContract) {
    		return $user;
    	}
    
    	$password = $credentials['password'];
    	
    	$token =  $credentials['token'];
    
    	// Once we have called this callback, we will remove this token row from the
    	// table and return the response from this callback so the user gets sent
    	// to the destination given by the developers from the callback return.
    	$callback($user, $password, $token);
    
    	return Password::PASSWORD_RESET;
    }
    protected function validateReset(array $credentials)
    {
    	
    	Reminder::removeExpired();
    	
    	$user = Sentinel::findByCredentials($credentials);
    	    	 
    	if (is_null($user)) {
    		return Password::INVALID_USER;
    	}
    
    	if (! $this->validateNewPassword($credentials)) {
    		return Password::INVALID_PASSWORD;
    	}
    	
    	$reminder = Reminder::exists($user);
    	    	
    	if (($reminder==false) || (!is_null($reminder) && ($reminder->code!= $credentials['token']))) {
    		return Password::INVALID_TOKEN;
    	}
    
    	return $user;
    }
    protected function resetPassword($user, $token, $password)
    {
    	
    	$reminder = Reminder::complete($user, $token, $password);
    	
    	Sentinel::login($user);    	
    }
    public function validateNewPassword(array $credentials)
    {
    	if (isset($this->passwordValidator)) {
    		list($password, $confirm) = [
    				$credentials['password'],
    				$credentials['password_confirmation'],
    		];
    
    		return call_user_func(
    				$this->passwordValidator, $credentials) && $password === $confirm;
    	}
    
    	return $this->validatePasswordWithDefaults($credentials);
    }
    
    /**
     * Determine if the passwords are valid for the request.
     *
     * @param  array  $credentials
     * @return bool
     */
    protected function validatePasswordWithDefaults(array $credentials)
    {
    	list($password, $confirm) = [
    			$credentials['password'],
    			$credentials['password_confirmation'],
    	];
    
    	return $password === $confirm && mb_strlen($password) >= 6;
    }
    
}

ユーザー登録


<?php

namespace App\Http\Controllers\Auth;

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;

use Sentinel;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/home';

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

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ]);
    }
    
    public function register(Request $request)
    {
    	$validator = $this->validator($request->all());
    
    	if ($validator->fails()) {
    		$this->throwValidationException(
    				$request, $validator
    		);
    	}
    	
    	Sentinel::authenticateAndRemember($this->create($request->all()));
    
    	return redirect($this->redirectPath());
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return User
     */
    protected function create(array $data)
    {
    	
    	$credentials = [
    			'email'    => $data['email'],
    			'password' => $data['password'],
    	];
    	
    	return Sentinel::registerAndActivate($credentials);

    }
}

ユーザーモデル

SentinelはSentinelなりにユーザーモデル持っていますので、Authのユーザーモデルを全面書き直してSentinelに使われているモデルを継承するのは必要となります。


<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Cartalyst\Sentinel\Users\EloquentUser;

use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;


class User extends EloquentUser implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Notifiable;
    use Authenticatable, Authorizable, CanResetPassword;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

Sentinel設定ファイル

Modelの変更にしたがってSentinelの設定ファイルの該当設定項目を変更しなければなりません


    /*
    |--------------------------------------------------------------------------
    | Users
    |--------------------------------------------------------------------------
    |
    | Please provide the user model used in Sentinel.
    |
    */

    'users' => [

       // 'model' => 'Cartalyst\Sentinel\Users\EloquentUser',
       'model' => 'App\User',

    ],

その他

Middleware

以下のコメントで確認してみたらAuthが登録したルートはauth/guestという二つのmiddlewareに守られています。


$ php artisan route:list

スクリーンショット 2017-01-18 11.54.06.png

Sentinelへ異動しましたのでauthに提供しているmiddlewareをそのまま使うわかがないでしょう。そういう訳で新しいmiddlwareを作成することにしました。


$php artisan make:middleware SentinelAuth

そしてSentinelAuthのロジックを以下のように追加する(※説明:認証されていないユーザからのアクセスは全てをloginページへリダイレクトさせること)

<?php

namespace App\Http\Middleware;

use Closure;
use Sentinel as Auth;

class SentinelAuth
{
    public function handle($request, Closure $next, ...$guards)
    {
        if(!Auth::check())
        	return redirect('/login');
        return $next($request);
    }
}

Kernelで登録したmiddlewareを切り替える


    protected $routeMiddleware = [
        //'auth' => \Illuminate\Auth\Middleware\Authenticate::class, // Comment out
        'auth' => \App\Http\Middleware\SentinelAuth::class, //追加
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];

見た内容の通り「guest」というmiddlewareの実施クラスはRedirectIfAuthenticatedで当ファイルを見て見たら少しい修正しないと行けないところがあります


<?php

namespace App\Http\Middleware;

use Closure;
//use Illuminate\Support\Facades\Auth; //Comment out
use Sentinel as Auth; //追加

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if(Auth::check()){
            return redirect('/home');
        }
        return $next($request);
    }
}

Viewの修正

以下の2つViewの修正は必要になります。


  @if (Route::has('login'))
                <div class="top-right links">
                    <!-- Auth::check ()ではなく Sentinel::check()にします -->
                    @if (Sentinel::check()) 
                        <a href="{{ url('/home') }}">Home</a>
                    @else
                        <a href="{{ url('/login') }}">Login</a>
                        <a href="{{ url('/register') }}">Register</a>
                    @endif
                </div>
  @endif
<!-- Auth::guest ()ではなく Sentinel::guest()にします -->
   @if (Sentinel::guest())
       <li><a href="{{ url('/login') }}">Login</a></li>
       <li><a href="{{ url('/register') }}">Register</a></li>
   @else

終わり

修正したところは結構多いですが自ら作るのに比べてコード量はある程度削減できたと思いましてそれにコードをなるべく多くリユースしたのでテスト作業も減られたはずです。