LoginSignup
2
1

More than 3 years have passed since last update.

laravel 認証試す

Posted at

Laravel8系で公式が用意している認証を試す。

Laravelプロジェクトを作成

$ composer create-project laravel/laravel

laravel uiを追加

composer require laravel/ui

laravel uiではなくLaravel BreezeやJetstreamを使うよう案内されているがとりあえずlaravel uiを使う

This legacy package is a very simple authentication scaffolding built on the Bootstrap CSS framework. While it continues to work with the latest version of Laravel, you should consider using Laravel Breeze for new projects. Or, for something more robust, consider Laravel Jetstream.

laravel uiをインストールするとコマンドが二つ使用可能になる。
ui:authでlogin等に使うviewを作成し、ui:contollersでLogincontroller等を作成する。

$ php artisan list
省略...
 ui
  ui:auth              Scaffold basic login and registration views and routes
  ui:controllers       Scaffold the authentication controllers
$ php artisan ui:auth
$ php artisan ui:controllers

route追加

ログイン後にリダイレクトされるHomeController@indexとauthに必要なrouteを追加

web.php
Route::middleware('auth:web')->group(function () {
    Route::get('/home', [\App\Http\Controllers\HomeController::class, 'index']);
});
\Illuminate\Support\Facades\Auth::routes();

webサーバー起動

dbセットアップの後php artisan serveでwebサーバーを起動する。

http://127.0.0.1:8000/
スクリーンショット 2021-01-17 20.46.22.png

右上でログイン/ログアウトできる。

authミドルウェアを読む

Kernel.phpのrouteMiddlewareにauthが登録されている。authというkeyでAuthenticate::classが使用される。

Kernel.php
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,

Authenticate.phpはIlluminate\Auth\Middleware\Authenticateを継承している。

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware

Illuminate\Auth\Middleware\Authenticate を読む

handle関数ではauthenticate関数が実行される。

    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        return $next($request);
    }

指定されたguardsを全てcheckし、guardが通ればshouldUseを実行。全てチェックに失敗すればunauthenticatedを実行する。

    // 第二引数のguardsは `Route::middleware('auth:web')` で指定した時のwebが与えられる。
    protected function authenticate($request, array $guards)
    {
        // 引数が指定されていない場合はnullを持つ配列にする。
        // $this->auth->guardでnullを渡すとconfigのauth.phpのdefault guardが使用される。
        if (empty($guards)) {
            $guards = [null];
        }


        foreach ($guards as $guard) {
            // `$this->auth` で参照されるインスタンスはコンストラクタでDIされる。
            // Authの型はIlluminate\Foundation\Applicationで指定されており、\Illuminate\Auth\AuthManager::classになる。
            // $this->auth->guard関数でguard文字列からguardの実態を返す
            // check関数で現在のユーザーが認証されているかどうかを判断します。
            if ($this->auth->guard($guard)->check()) {
                // 認証済みのguardがあればデフォルトドライバーとして登録する
                return $this->auth->shouldUse($guard);
            }
        }

        // 未認証の場合はunauthenticatedを実行
        $this->unauthenticated($request, $guards);
    }

guardの実態は↓の命名規則で決まる \Illuminate\Auth\AuthManager の関数で返されるインスタンスか、カスタムガード登録時に指定したインスタンスになる。


        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

unauthenticated関数

unauthenticatedでは認証エラーをスローする。redirectToでリダイレクトさせるpathを指定することが可能。

    protected function unauthenticated($request, array $guards)
    {
        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

session driver

guardにsessionを指定するとAuthManager.phpのcreateSessionDriver関数が実行され、SessionGuardインスタンスが作成される。

SessionGuardのcheck関数で現在のユーザーが認証されているかどうかを判断する。check関数自体はGuardHelpers traitで実装されている。


    public function check()
    {
        return ! is_null($this->user());
    }

$this->user()はGuardHelpersを実装したクラスで実装されている。

session driverのuser関数

セッションからuserを取り出して返す。


    public function user()
    {
        if ($this->loggedOut) {
            return;
        }

        // If we've already retrieved the user for the current request we can just
        // return it back immediately. We do not want to fetch the user data on
        // every call to this method because that would be tremendously slow.
        if (! is_null($this->user)) {
            return $this->user;
        }

        // getName()はガードに付けられた名前で、基本的にはwebになる
        $id = $this->session->get($this->getName());

        // First we will try to load the user using the identifier in the session if
        // one exists. Otherwise we will check for a "remember me" cookie in this
        // request, and if one exists, attempt to retrieve the user using that.
        if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }

        // If the user is null, but we decrypt a "recaller" cookie we can attempt to
        // pull the user data on that cookie which serves as a remember cookie on
        // the application. Once we have a user we can return it to the caller.
        if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
            $this->user = $this->userFromRecaller($recaller);

            if ($this->user) {
                $this->updateSession($this->user->getAuthIdentifier());

                $this->fireLoginEvent($this->user, true);
            }
        }

        return $this->user;
    }

ログイン処理を追う

laravel ui でログイン用のコントローラーが作成される。
具体的な処理はAuthenticatesUsersで実装されている

LoginController.php
class LoginController extends Controller
{
    use AuthenticatesUsers;

ログイン処理では
1. requestのvalidateチェック
2. ログイン試行回数チェック
3. attemptLoginでログインを試す
4. ログインに失敗したらログイン試行回数をインクリメント

AuthenticatesUsers.php
    public function login(Request $request)
    {
        $this->validateLogin($request);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

attemptLogin

requestから認証に必要な情報を取得してguardに渡してログインを試す

    protected function attemptLogin(Request $request)
    {
        // `$this->guard()`では`Auth::guard()`が実行され、configのauth.phpのデフォルトガード(で指定されているドライバー)が返される。
        // credentials関数でuserNameとpasswordだけ抽出する
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

SessionGuardのattempt

デフォルトでWebガードが指定されているのでSessionGuardのattemptを見る。
$this->provider->retrieveByCredentialsでuserを取得し、$this->hasValidCredentials($user, $credentials)で認証している。
認証に通ればlogin関数を実行する


    public function attempt(array $credentials = [], $remember = false)
    {
        $this->fireAttemptEvent($credentials, $remember);

        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

        // If an implementation of UserInterface was returned, we'll ask the provider
        // to validate the user against the given credentials, and if they are in
        // fact valid we'll log the users into the application and return true.
        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);

            return true;
        }

        // If the authentication attempt fails we will fire an event so that the user
        // may be notified of any suspicious attempts to access their account from
        // an unrecognized user. A developer may listen to this event as needed.
        $this->fireFailedEvent($user, $credentials);

        return false;
    }

プロバイダーはCreatesUserProviders traitのcreateUserProvider関数で作成しており、Eloquentプロバイダーの場合はEloquentUserProvider.phpインスタンスが使用される。
EloquentUserProviderのretrieveByCredentials関数は以下になっている。
引数のcredentialsを使ってqueryを組み立てていることが分かる。ポイントとしてここではqueryにパスワードは含めない。
email/passwordでログインする場合はここではemailのみを使ったqueryになる。
ちなみにretrieve(リトリーブ)は取り出すと言う意味。retryと文字が似ているが関係ない。

    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            Str::contains($this->firstCredentialKey($credentials), 'password'))) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->newModelQuery();

        foreach ($credentials as $key => $value) {
            // パスワードはここではチェックしない
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }

hasValidCredentials関数で認証情報と一致しているかチェックする
条件は
1. userが取得できている
2. providerを使った秘密情報のvalidateに成功する

    protected function hasValidCredentials($user, $credentials)
    {
        $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);

        if ($validated) {
            $this->fireValidatedEvent($user);
        }

        return $validated;
    }

validateCredentials関数は以下になっており、ユーザーが指定したパスワードとハッシュ化して保存してあるuserレコードのパスワードを一致判定している。


    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }

SessionGuardのlogin処理

providerによるuserの取得と認証検証に成功するとlogin処理が実行される。
ここでは以下が行われる。
1. sessionの更新
2. remember処理
3. $userを認証済みuserとして登録

    public function login(AuthenticatableContract $user, $remember = false)
    {
        $this->updateSession($user->getAuthIdentifier());

        // If the user should be permanently "remembered" by the application we will
        // queue a permanent cookie that contains the encrypted copy of the user
        // identifier. We will then decrypt this later to retrieve the users.
        if ($remember) {
            $this->ensureRememberTokenIsSet($user);

            $this->queueRecallerCookie($user);
        }

        // If we have an event dispatcher instance set we will fire an event so that
        // any listeners will hook into the authentication events and run actions
        // based on the login and logout events fired from the guard instances.
        $this->fireLoginEvent($user, $remember);

        $this->setUser($user);
    }

sessionの更新

    protected function updateSession($id)
    {
        $this->session->put($this->getName(), $id);

        $this->session->migrate(true);
    }

sessionのキーは↓の書式になる

    public function getName()
    {
        return 'login_'.$this->name.'_'.sha1(static::class);
    }
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1