Laravelでシステムを構築した場合、通常はアクセスできるユーザを制限を制限しなければなりません。認証機能を一から実装しようとすると時間がかかりますがLaravelには認証機能が備わっているので簡単に実装することができます。

認証機能はコマンド一つで追加できるため、全く仕組みがわからなくても活用することができます。本文書では認証機能を使う上で最低限知っておいて欲しいことについて解説を加えながら説明を行なっています。

Laravelの認証には、ログインの認証の他にAPIの認証もあります。APIの認証については下記の文書に記述しています。本文書では触れない認証に関するGuardについても下記で説明を行っています。

認証機能について

GoogleのGmailやFacebook, Twitterなどのサービスを利用するためにはユーザIDとパスワードを使用してログインを行わなければいけません。ユーザIDとパスワードをチェックする仕組みが認証機能です。Laravelの認証機能は他のサービスと同様にデフォルトではメールアドレスをパスワードを利用します。

環境の構築

Laravel5.8、MACを利用して動作確認を行っています。まずは認証機能を確認するための環境構築を行います。

認証機能を使用するためには、Laravelインストール後にデータベースを準備する必要があります。簡単にデータベースが作成できるSQLiteを利用します。

Laravelインストールフォルダで下記のコマンドを実行しSQLiteデータベースファイルを作成します。


$ touch database/database.sqlite

次に.envファイルを開き、初期設定のデータベスのMysqlの設定からSQLiteの設定に変更します。

【編集前の.envファイル】


DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

【編集後の.envファイル】

DB_CONNECTION以外のDBに関する情報を削除します。


DB_CONNECTION=sqlite

データベースの設定が完了したので、usersテーブルの作成を行います。php artisan migrateコマンドを実行します。


$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.01 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0 seconds)

コマンド実行後、usersテーブルだけではなくpassword_resetsテーブルも作成されることがわかります。usersテーブルはシステムにログインするユーザ情報を保存するテーブル、password_resetsはユーザがパスワードを忘れた時に行うパスワードリセットに関する情報を保存するテーブルです。後ほどパスワードリセットを行なった時にpassword_resetテーブルにどのような情報が保存されるか説明します。

認証に利用するデータベースとテーブルの作成を行えば認証機能に必要な環境構築は完了です。

認証機能の設定

認証機能の作成前の状態

認証機能作成前と作成後の状態を比較することでどのような変化がLaravel上で行われているのか確認していきます。主にLaravelの初期画面、ルーティングファイル、Viewファイルの3つの変化を確認します。

開発サーバを起動してブラウザでインストールしたLaravelにアクセスします。


$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>

インストール直後のLaravelの初期画面は下記の通りです。

Laravelインストール後の画面
Laravelインストール後の画面

ルーティングファイルのweb.phpの中身を確認します。”/”へのルーティングのみ記述されています。


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

viewsフォルダの中には、初期画面の内容が記述されているwelcome.blade.phpのみ存在している状態です。

認証機能の作成

認証機能を作成するためには、php artisan make:authコマンドを実行する必要があります。実行するとAuthentication scaffolding generated successfullyとメッセージが表示されるだけでどのようなファイルが作成されたかやどこのファイルに設定変更が行われたのかもこれだけではわかりません。


 $ php artisan make:auth
Authentication scaffolding generated successfully.

認証機能の作成後の状態

認証機能作成後に再度ブラウザで”/”にアクセスすると右上に認証に関するLOGINとREGISTERリンクが追加されていることが確認できます。

認証機能作成後のトップページ
認証機能作成後のトップページ

また、ルーティングファイルweb.phpには認証に関する情報(Auth::routes())と新たに”/home”へのルーティングが追加されています。


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

Auth::routes();

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

Auth::routes()では何が記述されているのかも確認しておきます。

AuthファサードのroutesメソッドはIlluminate\Support\Facades\Authファイルに記述されています。

routesメソッドは下記のようにrouterサービスのauthメソッドが実行されています。


public static function routes(array $options = [])
{
    static::$app->make('router')->auth($options);
}

さらにIlluminate\Routing\Router.phpファイルを確認するとroutesメソッドは認証に関するルーティングであることがわかります。このroutesメソッドにより、Laravelに認証に関するルーティングが追加されます。


public function auth(array $options = [])
{
    // Authentication Routes...
    $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
    $this->post('login', 'Auth\LoginController@login');
    $this->post('logout', 'Auth\LoginController@logout')->name('logout');

    // Registration Routes...
    if ($options['register'] ?? true) {
        $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
        $this->post('register', 'Auth\RegisterController@register');
    }

    // Password Reset Routes...
    if ($options['reset'] ?? true) {
        $this->resetPassword();
    }

    // Email Verification Routes...
    if ($options['verify'] ?? false) {
        $this->emailVerification();
    }
}

Router.phpのauthメソッドの中でルーティングが記述されていることがわかったので、php artisan route:listで認証に関するルーティングが追加されているのか確認します。


 $ php artisan route:list

loginやlogoutなどphp artisan route:listコマンドの結果のルーティングからも認証に関する情報が追加されていることが確認できます。

Authのルーティングの確認
Authのルーティングの確認

viewフォルダにもauthフォルダをはじめ認証に関するフォルダやファイルが多数作成されていることがわかります。認証機能作成前はwelcome.blade.phpしかありませんでした。

新しいviewファイルが追加
新しいファイルやフォルダが作成される

このようにphp artisan make:authコマンドを実行しただけでLaravelには認証に関するさまざまな追加や変更が行なっていることが確認できます。

/homeへのアクセス

認証機能が動作しているのか確認しているためにweb.phpに追加された”/home”にアクセスを行なってみます。認証が機能していなければhome.blade.phpファイルの中身が表示されるはずですが、ログイン画面に自動で切り替わり(リダイレクト)/homeにアクセスすることができません。/homeにアクセスできないのは認証機能が正常に動作しているためです。

Laravelログイン画面
Laravelログイン画面

ログイン画面が表示されてもシステムにユーザが登録されていなければログインを行うことができません。

しかし、なぜ”/”にはこれまで通りアクセスができて”/home”には認証のアクセス制限がかかっているのでしょう。HomeController.phpファイルを見るとすぐにわかります。

HomeController.phpには、middlewareでauthが設定されているためです。このmiddlewareのauth設定により、HomeControllerを経由して行われる処理はすべて認証によるアクセスの制限が行われます。


public function __construct()
{
    $this->middleware('auth');
}
本当にmiddlewareの設定がアクセス制限を行なっているか確認するためにmiddlewareの行を削除すれば、/homeにアクセスできることが確認できます。

middlewareのauthについてはapp¥Http¥Kernel.phpに記述されています。


protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ・
    ・
];

App¥Http¥Middleware¥Authenticateファイルを見るとredirectToメソッドしかありませんが、その他のメソッドは、Illuminate¥Auth¥Middleware¥Authenticateの中に記述されています。


namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    protected function redirectTo($request)
    {

        if (! $request->expectsJson()) {
            return route('login');
        }
    }
}

ユーザの登録

/homeにアクセスするためには、ユーザの登録が必要になります。右上にあるRegisterのリンクをクリックするとユーザの登録画面が表示されます。

ユーザ登録画面
ユーザ登録画面

ブラウザのURLを見るとユーザ登録を行う画面のURLは/registerだとわかります。先ほど実行したphp artisan route:listコマンドの結果/registerにアクセスするとApp¥Http¥Controllers¥AuthフォルダのRegisterController@showRegistrationFormが実行されることがわかります。showRegistrationFormメソッドはtraitのRegistersUsersの中に記述されています。

showRegistrationFormメソッドにより、view¥auth¥register.blade.phpのviewファイルの内容がブラウザに表示されていることがわかります。


public function showRegistrationForm()
{
    return view('auth.register');
}

このようにURLからルーティングを確認し、実行されているメソッドを調べていくとどのような処理が行われているかやどのようなviewファイルが利用されているかを把握することができます。

ユーザ登録画面では、Nameにはユーザ名、E-mail Addressにはメールアドレス、パスワードを入力して、Registerボタンをクリックします。

ユーザ登録画面
ユーザ登録画面

パスワード条件を満たさなければエラーメッセージが表示されます。

The password must be at least 8 charactors.(8文字が必要)

ユーザ登録エラーメッセージ
ユーザ登録エラーメッセージ

入力した値についての条件は、Http¥Controllers¥AuthのRegisterController.phpに記述されています。


protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'string', 'min:8', 'confirmed'],
    ]);
}

name, email, passwordは必須で、nameは最大255文字、emailは文字列の長さに加え、emailの形式とusersテーブルで一意である必要があります。つまり、@がないとエラーになり、一度登録されているメールアドレスは再度登録できないようになっています。passwordについては8文字制限がついており、passwordとconfirm passwordに入力した文字列が同一かどうかチェックを行なっています。

パスワードに関しては文字数制限しかないため8文字であればpasswordといった簡単な文字列でも登録可能です。セキュリティを考えるとより複雑な制限を行うValidationを追加する必要があります。

入力した値に問題がなければ、ユーザの登録は完了し/homeにアクセスすることが可能となります。右上にはログインしているユーザ名も表示されます。

/homeへのアクセス
/homeへのアクセス

ログイン後に自動で/homeのページが表示されますが、App¥Http¥Controllers¥AuthLoginControllerファイルに設定が行われています。


protected $redirectTo = '/home';

ユーザのログアウト

システムからユーザをログアウトすることもできます。その場合は、右上にある名前をクリックしてください。クリックするとログアウトのリンクが表示されます。

logoutメニューの表示
logoutメニューの表示

ログアウトがおこわなれるとトップページに戻ります。再度”/home”にアクセスするとログイン画面が表示されます。このことからログアウトにより、ログインした時に与えられた許可がすべて削除されることがわかります。

ログアウトの処理はルーティングからLoginController.phpファイルのlogoutメソッドが実行されることがわかります。実際の処理は、traitであるAuthenticatesUsersに記述されています。

ユーザのログイン

ログインを行いたい場合は、右上のLoginリンクをクリックするかmiddlewareでauthが設定されているページ(/home等)にアクセスすると自動でログイン画面にリダイレクトされます。

認証機能作成後のトップページ
認証機能作成後のトップページ

ログイン画面ではメールアドレスとパスワードを入れる必要があります。Loginボタンを押すと入力した値のチェックが行われます。

ログイン画面で項目に入力
ログイン画面で項目に入力

もし、入力した値に間違いがある場合は、エラーメッセージが表示されます。These credentials do not match our recodes.(登録されている情報と異なります)

ログインの入力が間違っていた場合
ログインの入力が間違っていた場合

入力を何度も間違ってしまうと一定時間ログインが行えなくなります。

Too many login attempts. Please try again in 51 seconds.(ログイン試行回数が多いので51秒後に再度実行してください)

ログインが何度も失敗した場合
ログインが何度も失敗した場合

メールアドレスとパスワードのチェックは、Illuminate¥Foundation¥Auth¥AuthenticatesUsersファイルに記述されています。

ログイン回数の失敗の数についてはIlluminate¥Foundation¥Auth¥ThrottlesLoginsファイルの中のmaxAttemptsメソッドで記述されています。5と設定されている値を変更すると入力間違いで一定時間ログインができなくなるまでの回数を多くすることも少なくすることもできます。またdecayMinutesメソッドの1を変更すると次のログインまでの時間を長くすることができます。1は1分を表しており5を設定すると5分の設定となります。


public function maxAttempts()
{
    return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5;
}

public function decayMinutes()
{
    return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1;
}

”ログインしたユーザ”が再度/loginにアクセスするとどうなるか確認します。/loginにアクセスすると/homeにリダイレクトされ、ログインしたユーザはログイン画面を見ることはできません。

ログインが完了しているユーザに再度ログイン画面がでることはおかしなことなので、このような処理があることは正しい動作です。

これはLoginController.phpのmiddlewareでguestが設定されているためです。


public function __construct()
{
    $this->middleware('guest')->except('logout');
}

middlewareのguestの中身をapp¥http¥Kernel.phpファイルで確認します。


protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, // here
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];

App¥Http¥Middleware¥RedirectIfAuthenticatedのファイルのhandleメソッドの中でログインが完了すると/homeにredirectするように記述されているためです。別の場所に変更するとログインした状態で/loginにアクセスすると指定した場合にリダイレクトされます。


class RedirectIfAuthenticated
{
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            return redirect('/home');
        }

        return $next($request);
    }
}

パスワードを忘れた場合

登録したユーザがパスワードを忘れた場合は、パスワードリセットを行うことができます。

Loginボタンの右側にあるForgot Your Password?リンクをクリックします。

パスワードを忘れた場合
パスワードを忘れた場合

メールアドレスを入力するフォームが表示されるので、メールアドレスを入力して、Send Password Rest Linkボタンをクリックします。

リセットパスワード入力画面
リセットパスワード入力画面

Laravelでメールサーバの設定が完了している場合は、登録したメールアドレスに向けてリセットするためのリンクが記述されたメールが送信されます。メールの設定を行なっていない場合は、.envファイルを開き動作確認のためにメールの内容をログに吐き出すように設定変更を行います。

.envファイルを開いてMAIL_DRIVERに設定されているsmtpをlogに変更してください。


MAIL_DRIVER=log
.envファイルを更新後設定値が反映されない場合はphp artisan config:cacheコマンドを実行してキャッシュの削除を行います。

もし、リセットを行いたいメールアドレスがシステムに登録されていない場合はエラーメッセージが表示されます。そのメールアドレスはシステムに未登録なので、登録を行えばそのメールアドレスでログインが可能となります。

We can’t find a user with that e-mail address.(入力したメールアドレスを持つユーザは存在しません)

システムに登録されていないメールアドレス入力
システムに登録されていないメールアドレス入力

システムに登録されているメールアドレスであれば下記の画面が表示されます。

We have e-mailed your password reset link(パスワードリセットリンクのメールを送信しました)。ユーザのメールアドレス宛にメールが送信されます。

リセットパスワードのメール送信
リセットパスワードのメール送信

メールの送信先をMAIL_DRIVER=logに設定した場合は、strorage/logsフォルダの下にあるlaravel.logファイルを確認してください。メールの設定を行なっている場合は受信ボックスに下記の内容のメールが届きます。

リセットパスワードメール
リセットパスワードメール

パスワードの有効時間が60分と記述されています(This password reset link will expire in 60 minutes)が、設定は、config.php¥auth.phpファイルで行われています。


'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
    ],
],

開発サーバを利用して動作確認を行なっている場合は、Reset Passwordボタンを押すとhttp://localhostへのアクセスでエラーになる場合があります。その場合は、URLをphp artisan serveコマンドを実行した時に表示されるhttp://127.0.0.1:8080に変更してください。

新しいパスワードを入力する画面
新しいパスワードを入力する画面

新しいパスワードを入力して、Reset Passwordボタンをクリックしてください。

入力したパスワードに問題がなければパスワードリセット完了画面が表示されます。

パスワードリセット完了画面
パスワードリセット完了画面

ユーザにReset Passwordがメールが送信されるとpassword_resetsテーブルに新たな行が登録されます。password_resetsには、email, token, created_atの3つの列で構成されています。


sqlite> select * from password_resets;
email|token|created_at
johndoe@example.com|$2y$10$Hr9MMZsKOu0WC17nJMAv6.PeBpW8fRXnx3oeYJ9ocbc5JZIClBMp.|2019-07-25 07:34:57

追加された行はパスワードのリセットが完了すると削除されます。php artisan migrateで作成されるpassword_resetsテーブルはこのように利用されています。

viewファイル内での認証を活用

すぐには気づかないかもしれませんが”/”にアクセスした場合にログインしているユーザとしていないユーザでは右上に表示されているリンクが異なります。

ログインしているユーザの場合は、右上にはHomeへのリンクが表示されます。

ログインしたユーザの場合
ログインしたユーザの場合

ログインしていないゲストユーザの場合は、LOGINとREGISTERへのリンクが表示されます。

ゲストユーザとしてアクセス
ゲストユーザとしてアクセス

この違うはどのように行なっているのかwelcome.blade.phpを確認します。

@authというディレクティブがあり、@authにより条件分岐されています。@authは認証が行われてログインしているユーザであればtrueになり、ログインされてなければfalseで@elseの中のコードが実行されます。下記のコードからも右上のリンクの変化は@authディレクティブで制御されています。


@if (Route::has('login'))
    <div class="top-right links">
        @auth
            <a href="{{ url('/home') }}">Home</a>
        @else
            <a href="{{ route('login') }}">Login</a>

            @if (Route::has('register'))
                <a href="{{ route('register') }}">Register</a>
            @endif
        @endauth
    </div>
@endif

@authだけではなく@guestというディレクティブも存在します。@guestはログインしていなければ実行されます。

ログイン後に右上に出ている名前は、{{ Auth::user()->name }} を使用して表示しています。

ログインしているユーザのidが取得したい場合はAuth::user()->id,Auth::id()で取得することができます。Auth::user()についてはさまざまな場所で使用されているので使用方法をこの機会に理解しておきましょう。

カスタマイズ

ユーザ名でログインを行う

デフォルトではEmailアドレスを使ってログインを行なっていますが、ユーザ名に変更することも可能です。

ログインに使用する情報はtraitのAuthenticatesUsersの中で行なっています。入力した内容をチェックするのはvalidateLoginメソッドで行なっています。


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

validateLoginメソッドの中身を見ると$this->username()というメソッドがあります。


protected function validateLogin(Request $request)
{
    $request->validate([
        $this->username() => 'required|string',
        'password' => 'required|string',
    ]);
}

このusernameメソッドの中身は下記のようにemailが指定されています。


public function username()
{
    return 'email';
}

これをnameにします。

Login画面を開いて、E-mail Addressにユーザ名を入れてLoginボタンを押すとエラーが発生します。input要素のtypeがemailになっているのでブラウザ側で値のチェックを行い、@が入っていないのでエラーになっているためです。

ブラウザ側の制御なのでブラウザによっては@チェックは行われない場合があります。
ログインをユーザ名で行う
ログインをユーザ名で行う

views¥auth¥login.blade.phpを開いて、E-mail Addressの入力部分を変更します。input要素のtypeをemailからtextに変更は忘れずに行う必要があります。


<div class="form-group row">
    <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

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

        @error('email')
            <span class="invalid-feedback" role="alert">
                <strong>{{ $message }}</strong>
            </span>
        @enderror
    </div>
</div>

emailの箇所をnameに変更します。


<div class="form-group row">
    <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

    <div class="col-md-6">
        <input id="name" type="name" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>

        @error('name')
            <span class="invalid-feedback" role="alert">
                <strong>{{ $message }}</strong>
            </span>
        @enderror
    </div>
</div>
ユーザ名でログイン
ユーザ名でログイン

今回はエラーも表示されず、ユーザ名でログインすることができました。

/homeへのアクセス
/homeへのアクセス

しかし同じ名前を持つ人が存在していたらどうなるでしょう。

後から登録したユーザはログインを行うことができません。

テーブルを作成する際にemailはunique設定を行なっており、登録時のValidationでもusersテーブル内で一意であることをチェックしていました。


$table->string('email')->unique();

protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'string', 'min:8', 'confirmed'],
    ]);
}

ユーザ名を使用する際はpasswordと同じようにmigrationファイルでname列にもunique設定を行い、validationでもnameにunique設定を行う必要があります。


$table->string('name')->unique();

protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => ['required', 'string', 'max:255','unique:users'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'string', 'min:8', 'confirmed'],
    ]);
}

上記の設定を行なっておけば、同じ名前のユーザを登録しようとするとエラーが発生します。同じ名前のユーザは登録することができないので、ユーザ名が重複に関する問題が解消されます。