Laravelのログイン認証の基本(Authentication)を完全理解する
Laravelで社内システムを構築した場合、ユーザにはログインによるアクセス制限をかけなければなりません。ログインによる認証機能を一から実装しようとすると時間がかかりますがLaravelには認証機能が事前に備わっているため短い時間で認証機能を実装することができます。
認証機能はコマンド一つで追加できるため全く仕組みがわからなくても活用することができます。本文書では認証機能を使う上で最低限知っておいて欲しいことについて解説を加えながら進めていきます。Laravelのバージョンが上がっても基本的な認証部分のコアは変わっていないので長く活用できる知識です。
Laravelの認証にはログインの認証の他にAPIの認証もあります。APIの認証については下記の文書で公開しているのでぜひ参考にしてください。本文書では触れない認証に関するGuardについても同様に下記で説明を行っています。
目次
認証機能について
例えばGoogleのGmailやFacebook, Twitterなどのサービスを利用するためにはユーザIDとパスワードを使用してログイン/サインインを行わなければいけません。サービスの利用許可を行うために利用するチェックの仕組みが認証機能です。認証機能のおかげで認証に必要なユーザIDとパスワードを知っている人しか各サービスに保存されているデータにアクセスすることはできません。
Laravelの認証機能はデフォルトではメールアドレスとパスワードを利用して行います。
環境の構築
Laravel5.8、Macを利用して動作確認を行っています。まずは認証機能を確認するための環境構築を行う必要がありますがLaravelのインストールは完了しているという前提で進めさせてもらいます。
認証機能を使用するためにはユーザ情報を保存するためにLaravelインストール後にデータベースを準備する必要があります。ここでは簡易的なデータベースであるSQLiteを利用します。本番環境であればMySQLデータベースなどを利用することになります。
Laravelをインストールしたディレクトリ直下でtouchコマンドを実行し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とします。
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の初期画面は下記の通りです。
ルーティングファイルの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()を見ただけでは追加されたルーティング情報のURLもどのような処理が行われるのかがわかりません。Auth::routes()によって何が追加されたのかを確認していきます。
AuthファサードのroutesメソッドはIlluminate\Support\Facades\Authファイルに記述されています。
Auth.phpファイル内のroutesメソッドはrouterサービスのauthメソッドを実行しています。
public static function routes(array $options = [])
{
static::$app->make('router')->auth($options);
}
routeサービスのメソッドはIlluminate\Routing\Router.phpファイルに記述されています。この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コマンドの結果のルーティングからも認証に関する情報が追加されていることが確認できます。
viewフォルダにもauthフォルダをはじめ認証に関するフォルダやファイルが多数作成されていることがわかります。認証機能作成前はwelcome.blade.phpしかありませんでした。
このようにphp artisan make:authコマンドを実行しただけでLaravelには認証に関するさまざまな追加や変更が行われていることが確認できます。
/homeへのアクセス
認証機能が動作しているのか確認するためにweb.phpに追加された”/home”にアクセスを行なってみます。認証が機能していなければhome.blade.phpファイルの中身が表示されるはずですが、ログイン画面に自動で切り替わり(リダイレクト)/homeにアクセスすることができません。/homeにアクセスできないのは認証機能が正常に動作しているためです。
ログイン画面が表示されてもシステムにユーザが登録されていなければログインを行うことができません。
しかし、なぜ”/”にはこれまで通りアクセスができて”/home”には認証のアクセス制限がかかっているのでしょう。/homeにアクセスした際に実行されるHomeController.phpファイル見ることで確認することができます。
HomeController.phpにはmiddleware(ミドルウェア)でauthが設定されているためです。このmiddlewareのauth設定により、HomeControllerを経由して行われる処理はすべて認証によるアクセスの制限が行われます。
public function __construct()
{
$this->middleware('auth');
}
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');
}
}
}
route(‘login’)のloginはphp artisan route:listで実行した時に表示されるName列の名前でredirectToメソッドでは認証が完了していないユーザ/loginにリダイレクトされます。
ユーザの登録
/homeにアクセスするためには、ユーザの登録が必要になります。右上にあるRegisterのリンクをクリックするとユーザの登録画面が表示されます。
ブラウザのURLを見るとユーザ登録を行う画面のURLは/registerだとわかります。先ほど実行したphp artisan route:listコマンドの結果/registerにアクセスするとApp¥Http¥Controllers¥AuthフォルダのRegisterController@showRegistrationFormが実行されることがわかりますがshowRegistrationFormメソッドはRegisterController.phpファイルには記述されておらずtraitのRegistersUsersの中に記述されています。
class RegisterController extends Controller
{
use RegistersUsers;
RegisterUsers.phpのshowRegistrationFormメソッドにより、view¥auth¥register.blade.phpのviewファイルの内容がブラウザに表示されていることがわかります。
public function showRegistrationForm()
{
return view('auth.register');
}
このようにURLからルーティングを確認し、実行されているメソッドを調べていくとどのような処理が行われているか、どのようなviewファイルが利用されているかを把握することができます。
register.blade.phpファイルのHTMLが下記のブラウザに表示されている内容と同じか確認を行ってください。
ユーザの登録を行うためユーザ登録画面では、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に入力した文字列が同一かどうかチェックを行なっています。
入力した値に問題がなければ、ユーザの登録は完了し/homeにアクセスすることが可能となります。右上にはユーザ登録時に指定したユーザ名も表示されます。
ログイン後に自動で/homeのページが表示されますが、App¥Http¥Controllers¥AuthLoginControllerファイルに設定が行われています。ログイン後にリダイレクトしたい場合を変更したい場合はこの値を変更することで実現することができます。
protected $redirectTo = '/home';
ユーザのログアウト
システムからユーザをログアウトすることもできます。その場合は、右上にある名前をクリックしてください。クリックするとログアウトのリンクが表示されます。
ログアウトが行われるとトップページに戻ります。再度”/home”にアクセスするとログイン画面が表示されます。このことからログアウトにより、ログインした時に与えられた許可情報がすべて削除されることがわかります。
ユーザのログイン
ログインを行いたい場合は、右上の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ファイルのvalidateLoginメソッドに記述されています。
protected function validateLogin(Request $request)
{
$request->validate([
$this->username() => 'required|string',
'password' => 'required|string',
]);
}
ログイン回数の失敗の数については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
もしリセットを行いたいメールアドレスがシステムに登録されていない場合はエラーメッセージが表示されます。
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>
今回はエラーも表示されず、ユーザ名でログインすることができました。
しかし同じ名前を持つ人が存在していたらどうなるでしょう。
後から登録したユーザはログインを行うことができません。
テーブルを作成する際に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'],
]);
}
上記の設定を行なっておけば、同じ名前のユーザを登録しようとするとエラーが発生します。同じ名前のユーザは登録することができないので、ユーザ名が重複に関する問題が解消されます。
ここまでの説明を読み終えるとLaravelのおけるログイン認証に基本を完全に理解することができたと思います。本文書では認証の処理などのコードの中身には触れていません。さらにより深く認証を理解したい場合は下記の文書もお勧めです。