You are here
Home > Posts tagged "ユーザー認証"

ユーザー認証(1)下準備

ララベルを使用したユーザー認証のプログラムを紹介していきます。

ユーザー認証と言っても、いろいろ考えることがたくさんあります。

認証の対象としては、フロントエンドとして会員認証のためのログインがあるし、フロントエンドがあればもちろんバックエンドがあり、そこでは会員と違う管理者を認証する必要もあります。また、最近ではFacebookやTwitterなどのソーシャルウェブサイトのログインを自分のウェブサイトの会員認証のために利用することも可能です。

また、ユーザー認証に関わる機能としては、

と、いろいろあります。どれもアプリの開発には必要な機能です。

嬉しいことに、ララベルでは、画面のテンプレートファイル(ブレード)以外は、これらの機能をカバーするプログラムがほとんど揃っています。今回からこれらの機能を見ていきましょう。

アプリの設定

まず、このためにララベルのプロジェクトを作成します。新プロジェクトの名前は、demo-auth。以下をコマンドラインで実行してください。

laravel new demo-auth

あるいは、

~/.composer/vendor/bin/laravel new demo-auth

次にデータベースを作成してください。mysql, sqlite、pgsqlなどお好きなものでDBを作成してください。

DB作成後は、設定ファイルの編集です。まず、サンプルの設定ファイルを改名して、この環境でのアプリの設定ファイルとします。

mv .env.example .env

.envのファイルで編集が必要なのは今のところ、以下のDB設定の部分です。

DB_HOST=localhost
DB_DATABASE=データベースの名前
DB_USERNAME=データベースユーザーの名前
DB_PASSWORD=データベースのパスワード

その後、以下を実行してください。

php artisan key:generate

それにより、.envのファイルの

APP_KEY=SomeRandomString

の「SomeRandomString」に値が「s0IWRnfPlNAiPl7Bwl1MX8V7i14loYyK」のようなランダム値に変更されます。この値は、アプリのすべての暗号化に使用されるので、一度設定したら2度と変えないように。

これで環境の準備は完了です。

DBテーブルの作成

この作業には、すでにdemo-authディレクトリで作成された以下のファイルが重要となります。これらをまず見てみましょう。


app/User.php
config/auth.php

まず、2のconfig/auth.php

return [

  // ドライバーにはエロクエントを使用します。オプションとしては、'database'も可能です。
    'driver' => 'eloquent',

  // ドライバーでエロクエントを使用するなら、そのクラスをここで指定。ファイルは app/User.phpです。
  // ::classは、namespaceとともにクラス名を返します。
    'model' => App\User::class,

  // ドライバーがdatabaseならテーブル名を指定。
    'table' => 'users',

    // パスワードを忘れたときにリセットするための情報。
    // emailは、views/emails/password.blade.phpを送信するメールとテンプレート(作成必要あり)とします。
    // DBテーブルは、password_resetsを使います。
    // リセットのトークンは発行されてから60分間で時間切れとなります。
    'password' => [
        'email'  => 'emails.password',
        'table'  => 'password_resets',
        'expire' => 60,
    ],
];

今回はドライバーにエロクエントを使用するので、モデルファイルのapp/User.phpを見てみましょう。

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
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 Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract
{
    // このクラスの関数の定義は、以下のtraitを使用しています。
    use Authenticatable, Authorizable, CanResetPassword;

    // 「users」が使用するDBテーブルとなります。
    protected $table = 'users';

  // これらが入力フィールドという仮定。
    protected $fillable = ['name', 'email', 'password'];

    // JSONで排除するフィールド名
    protected $hidden = ['password', 'remember_token'];
}

ということで、DBテーブルとして必要なのは、userspassword_resetsの2つとなります。実際のプロジェクトで、DBテーブル名や、テーブルのフィールド名に違いがあれば、これらが編集必要なファイルですね。

さて、それらのテーブルを定義しているのが、以下のファイル。


database/migrations/2014_10_12_000000_create_users_table.php
database/migrations/2014_10_12_100000_create_password_resets_table.php

それらは以下をコマンドラインで実行して作成します。

php artisan migrate

下準備は、以上で完了です。次は、会員登録画面、ログイン画面を作成します。

ユーザー認証(2)ユーザーの登録

ユーザーの登録は、重複回避ーDB重複エラーで、一部分紹介しました。ここでは、一部と言わず全部をカバーしてみましょう。しかも、ララベル5.1がサンプルとして提供するプログラムをもとに。

ララベルが提供するユーザー認証のサンプルは、ララベルの作者、テイラーが作成したプログラムでありますが、すべてのパーツが揃っていて、親切な説明がなされているというものではありません。コントローラで使用するブレイドのファイルもないし、ブレイドのファイル名やパス名もドキュメントにはありません。また、プログラムでは、トレイトが多用されているために、まず参照されているファイルを見て、次の参照ファイルを見て、さらに次へ・・という、理解には探偵作業が必要です。ちょっと腰を据えて取り組んでみましょう。

まず、最前線のファイルのリストから、

app/Http/routes.php
app/Http/Controllers/Auth/AuthController.php

そう、これだけなのです。もちろんブレイドファイルも作成する必要ありますが、これだけでユーザーの登録とログイン画面ができてしまいます。それぞれ中身を見てみましょう。

まず、routes.php。これは、画面でアクセスするプログラムの回路図のようなもので、ここでURLとコントローラを結びつけます。

// 会員登録
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');

// ログイン・ログアウト
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');

//会員登録後、ログイン後に飛ばされるホーム画面
Route::get('/home', function () {
    return view('auth.home');
});

次に、AuthController.php。このひとつで会員登録とログインをやってしまいます。いったいどうやっているのか見てみましょう。(オリジナルの英語のコメントはスペースのために省いています)


namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    public function __construct()
    {
        // ログアウト以外は、guestのミドルウェアを通す。
    // ここですでに認証されているかどうかを判断。
        $this->middleware('guest', ['except' => 'getLogout']);
    }

  // 入力チェックのルーチン
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:6',
        ]);
    }

  // DBにレコードを作成。
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

これを最初に見て思うのは、routes.phpで使用されているコントローラのメソッドはどこにある??

getRegister
postRegister
getLogin
postLogin
getLogout

これらのメソッドのことです。もしかして、Controllerのクラスを継承しているので、Controller.phpで定義されている?
しかし、そちらはもっと短い(以下)!


namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

abstract class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

それならいったいどこで?と探偵作業の始まりです。

となると、AuthController.phpの13行目のトレイト?

use AuthenticatesAndRegistersUsers

見てみましょう。


namespace Illuminate\Foundation\Auth;
trait AuthenticatesAndRegistersUsers
{
    use AuthenticatesUsers, RegistersUsers {
        AuthenticatesUsers::redirectPath insteadof RegistersUsers;
    }
}

またしてもトレイト!

use AuthenticateUsers, RegisterUsers

RegisterUsers.phpを見てみましょう(ログイン関連は、AuthenticateUsers.phpですがそれは次回に)。


namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
trait RegistersUsers
{
    use RedirectsUsers;

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

    public function postRegister(Request $request)
    {
        $validator = $this->validator($request->all());
        if ($validator->fails()) {
            $this->throwValidationException(
                $request, $validator
            );
        }
        Auth::login($this->create($request->all()));
        return redirect($this->redirectPath());
    }
}

これですね、探していたのは!

getRegisterでは、auth.registerがブレードの場所ということわかります。つまり、

resources/views/auth/register.blade.php

を作成すれば、会員登録の画面ができあがりです。

postRegisterの関数では、先にAuthController.phpで定義した、validatorcreateがここで参照されていますね。

Auth::login($this->create($request->all()));

ここでは、DBレコードを作成して返される、Userのインスタンスをもとに、ログインも行っていしまいます。つまり、登録したら自動ログインして、ホームにリダイレクトということです。

しかし、いったいどこへリダイレクト?

それも、もちろんRedirectUsersのトレイトなのです。


namespace Illuminate\Foundation\Auth;

trait RedirectsUsers
{
    public function redirectPath()
    {
        if (property_exists($this, 'redirectPath')) {
            return $this->redirectPath;
        }

        return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
    }
}

デフォルトは、/homeですね。また、Authocontrollerにおいて、redirectToの上書きも可能ということです。

最後に、登録画面のブレードファイルには、以下のようなHTMLがあればOKです。

<form method="post" action="auth/register">
{!! csrf_field() !!}
  <div>
    ログイン:
    <input type="email" name="email" value="{{ old('email') }}" required autofocus>
  </div>
  <div>
     パスワード:
     <input type="password" name="password" required>
  </div>
  <div>
     パスワードの確認:
     <input type="password" name="password_confirmation" required>
  </div>
  <div>  
     名前:
     <input type="text" name="name" value="{{ old('name') }}" required>
  </div>
  <div>
     <button type="submit">保存</button>
  </div>
</form>

次回は、ログインの方を深く見てみましょう。

ユーザー認証(3)ログイン・ログアウト

前回のユーザー登録では、登録後は自動的に認証され、ユーザーはあたかもすでにログインしたような状態となります。

しかし、デフォルトの設定の2時間のアイドルを過ぎると、ログアウトされてしまいます。そうなると必要なのはユーザー認証のためのログイン画面です。

ログイン画面に必要なのは、

app/Http/Controllers/AuthController.php

のコントローラ。前回にはこのコントローラは、登録画面に使われましたが、コントローラ中の以下のトレイトの使用により、

use AuthenticatesAndRegistersUsers

さらに、

そのトレイトの定義で使われているトレイト、

use AuthenticatesUsers

により、ログインが機能します。その定義を見てみましょう。以下のコードは説明のために、ログインスロットルなどの部分を省いています。


namespace Illuminate\Foundation\Auth;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Lang;

trait AuthenticatesUsers
{
    use RedirectsUsers;

    // ログイン画面の表示
    public function getLogin()
    {
        // app/resources/views/auth/authenticate.blade.phpがあるならそれを使用
        if (view()->exists('auth.authenticate')) {
            return view('auth.authenticate'); 
        }

        // authenticate.blade.phpがないなら、login.blade.phpを使用
        return view('auth.login');
    }

  // ログインボタンを押したら以下を実行
    public function postLogin(Request $request)
    {
        // 入力バリデーション
        $this->validate($request, [
            $this->loginUsername() => 'required', 'password' => 'required',
        ]);

        $credentials = $this->getCredentials($request);

        if (Auth::attempt($credentials, $request->has('remember'))) {
       // 認証成功後の処理:リダイレクトとか
            return $this->handleUserWasAuthenticated($request);
        }

    // 認証失敗なら、ログイン画面にエラーを表示
        return redirect($this->loginPath())
            ->withInput($request->only($this->loginUsername(), 'remember'))
            ->withErrors([
                $this->loginUsername() => $this->getFailedLoginMessage(),
            ]);
    }

    // 認証成功後の処理。目的の画面にリダイレクト
    protected function handleUserWasAuthenticated(Request $request)
    {
        if (method_exists($this, 'authenticated')) {
            return $this->authenticated($request, Auth::user());
        }
        return redirect()->intended($this->redirectPath());
    }

  // ユーザーのクレデンシャル、つまりemailとpasswordをゲット
    protected function getCredentials(Request $request)
    {
        return $request->only($this->loginUsername(), 'password');
    }
    
    // ログアウト、つまりクッキーの削除。そしてリダイレクト。デフォルトはルートディレクトリへ
    public function getLogout()
    {
        Auth::logout();
        return redirect(property_exists($this, 'redirectAfterLogout') ? $this->redirectAfterLogout : '/');
    }
    
  ...

postLogin()では、入力バリデーションの後に、Auth::attemptが実行されます。そこでは、config/auth.phpで指定したドライバー(Eloquent)を使用して、DBにEメールと暗号化されたパスワードがマッチするかどうかチェックして結果を返します。マッチしたなら、そこでセッションを作成して認証された事実を残します。認証関連のイベントでフックされているコードもそこで実行されます。

その後に実行される、handleUserWasAuthenticated()では、redirect()->intended()により、アクセスを試みた初期のURLへリダイレクトします。

例えば、認証以前に、

http://localhost/demo-auth/public/password/edit

へアクセスを試みると、まず認証のためにログイン画面へリダイレクトされて、ログイン成功後には、上のURLへ自動的に移動します。デフォルトでは、$this->redirectPath()へ移行します。

ログアウトは、ログインのようにボタンとかは必要とせずに、現在ログインしているならログアウトのURLにアクセスするだけで、ログインのセッションを無効とします。get Login()Auth::logout()がその処理を行います。

上記のコントローラで使用されるテンプレート、login.blade.phpには、以下のようなフォームが含まれます。

<form method="POST" action="/auth/login">
    {!! csrf_field() !!}

    <div>
        Eメール
        <input type="email" name="email" value="{{ old('email') }}">
    </div>

    <div>
        パスワード
        <input type="password" name="password" id="password">
    </div>

    <div>
        <input type="checkbox" name="remember"> 次回から入力を省略
    </div>

    <div>
        <button type="submit">ログイン</button>
    </div>
</form>

さて、これでログイン画面は動作しますが、このフォームで使用される「次回から入力を省略」のチェックボックスは何でしょう?

ここがオフでは、ログインのセッションは2時間で時間切れとなり、再度ログインが必要となります。ここをオンとしてログインが成功となると現在の設定では5年間は、ログインなしで認証が必要な画面へのアクセスが可能となります。Laravelではセキュリティを上げるために、ここがオンのときはトークンを作成してDBに保存し、クッキーの値とマッチするかのチェックを行っています。

さて、ユーザーの認証の部分ができたところで、次回は、どうやってLaravelがこの認証の情報をもとにプライベート画面を保護するかを見てみましょう。

ユーザー認証(4)認証でページを保護

ユーザー認証の目的は、ユーザー本人であることを確認し、ユーザーのプライベートの情報を他に見られることを防ぐことです。

Laravelでは、アプリにおけるすべてのルートを設定するroutes.phpのファイルにおいて、ミドルウェアを利用して保護するページを指定します。

Route::get('/home', ['middleware' => 'auth', function () {
    return view('auth.home');
}]);

上の例では、/homeを保護するために、ミドルウェアのauthを使用しています。authは、以下のKernel.phpで登録されているサービスです。


namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    protected $middleware = [
   ...
    ];

    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    ];
}

そこでバインドされているクラスは、以下のAuthenticate.phpで定義されています。


namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Authenticate
{
    protected $auth;

    public function __construct(Guard $auth)
    {
        $this->auth = $auth;
    }

    public function handle($request, Closure $next)
    {
        if ($this->auth->guest()) {
            if ($request->ajax()) {
                return response('Unauthorized.', 401);
            } else {
                return redirect()->guest('auth/login');
            }
        }
        return $next($request);
    }
}

handle()内での、$this->auth->guest()は、ユーザーがゲスト、つまり認証されていないユーザーであるかどうかをチェックします。認証されていないなら、auth/loginにリダイレクトして、ログイン画面が表示されます。

routes.phpでのページ保護は、Route::groupで行うこともできます。例えば、以下のように複数のルートをまとめることができます。わかりやすいですね。

Route:: group(['prefix' => 'member', 'middleware' => 'auth'], function() {
    Route::get('index', 'MemberController@getIndex');
    Route::get('password', 'MemberController@getPassword');
    Route::post('password', MemberController@postPassword');
    Route::get('profile', 'MemberController@getProfile');
    Route::post('profile', 'MemberController@postProfile');
    Route::get('logout', 'MemberController@getLogout');
});

さらに、認証の保護しないページにおいて、すでに認証されているならスキップしてリダイレクトさせることも可能です。
以下では、ログインや登録画面にアクセスしたときにすでにログインしているなら、/homeにリダイレクトされます。ミドルウェアにおいて、guestが使用されていることに注意してください。このミドルウェアも先のKernel.phpで登録されています。

Route::group(['middleware' => 'guest'], function() {
    Route::get('login', 'LoginController@getLogin');
    Route::post('login', 'LoginController@postLogin');
    Route::get('signup', 'SignupController@getSignup');
    Route::post('signup', 'SignupController@postSignup');
});

これらのミドルウェアは、routes.phpだけでなくコントローラの中でも実行できます。例えば、前回まで使用してきたAuthController.php


namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    public function __construct()
    {
        $this->middleware('guest', ['except' => 'getLogout']);
    }

  ...
}

guestのミドルウェアをコンストラクタで使用することにより、ログアウト(getLogout)以外のメソッド、つまり会員登録(getRegisterpostRegister)とログイン(getLoginpostLogin)のメソッドにおいて、すでにログインしているなら/homeにリダイレクトします。

ユーザー認証(5)パスワードリセット

ユーザーログインがあるなら、パスワードを忘れることがあるのは当然。忘れたらなら、通常はログイン(たいていはEメール)を入力して、パスワードのリセットのリンクを受け取り、リンク先の画面で新規のパスワードを設定します。新しいパスワードを作成して送信してくるサイトもあります。しかし、良く利用するサイトなら、やはり自分が覚えられるパスワードを設定したいです。

そう、この機能もLaravelで提供しています。

まずは、この機能に必要なものをリスト。

  • パスワードリセットリンクを送信してもらう画面
  • パスワードリセットリンクを含むメールのテンプレート
  • 送信するパスワードリセットリンクの有効期限を設定し保持するDBテーブル
  • パスワードリセットリンク先の画面で、新規パスワードを設定する画面

以上です。

パスワードリセットリンクを送信してもらう画面

routes.phpの設定から見てみましょう。

Route::get('password/email','Auth\PasswordController@getEmail');
Route::post('password/email', 'Auth\PasswordController@postEmail'); 

PasswordController.phpは、AuthController.phpと同様にlaravelインストール時に app/Http/Controllers/Authに存在します。

中身は以下にあるように、これまたAuthController.phpと同様にトレイトで構成されているのでほぼ空状態。

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;

class PasswordController extends Controller
{
    use ResetsPasswords;

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

ミドルウェアguestが使用されているので、すでにログインしているなら使用できません。

テンプレートは、reources/views/auth/password.blade.phpのファイルとします。

<form method="POST" action="{!! url() !!}/password/email">
    {!! csrf_field() !!}

    <div>
        Eメール
        <input type="email" name="email" value="{{ old('email') }}">
    </div>

    <div>
        <button type="submit">パスワードリセットリンクを送信</button>
    </div>
</form>

これで画面の表示までは完了。次は、送信ボタンを押したときの処理。以下の3つ作業が必要です。

  • 入力バリデーション。Eメールのフォーマットのチェックだけでなく、ユーザーとしてそのEメールがDBに存在するかのチェック
  • ユニークなトークンを発行し、EメールとともにDBに保存
  • パスワードリセットの画面のURLとトークンを合わせてリンクとして、ユーザーのEメールに送信

この3つの作業が行われているか、PasswordControllerで使用されるトレイトのResetsPasswordsを見てみましょう。


namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

trait ResetsPasswords
{
    public function getEmail()
    {
        return view('auth.password');
    }

    public function postEmail(Request $request)
    {
        $this->validate($request, ['email' => 'required|email']);
        $response = Password::sendResetLink($request->only('email'), function (Message $message) {
            $message->subject($this->getEmailSubject());
        });
        switch ($response) {
            case Password::RESET_LINK_SENT:
                return redirect()->back()->with('status', trans($response));
            case Password::INVALID_USER:
                return redirect()->back()->withErrors(['email' => trans($response)]);
        }
    }
  ...

postEmailがあり画面の入力を受け取り処理しています。入力したEメールのバリデーションがあります。しかしここでは、その他の作業は皆、Password::sendRsetLinkに任せています。もうちょっと追及してみましょう。

Password::sendResetLinkは、PasswordのクラスはFacadeで、実際は以下のPasswordBrokerのクラスが使用されています。sendResetLinkの定義がありますね。


namespace Illuminate\Auth\Passwords;
use Closure;
use UnexpectedValueException;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class PasswordBroker implements PasswordBrokerContract
{
    protected $tokens;
    protected $users;
    protected $mailer;
    protected $emailView;
    protected $passwordValidator;

    public function __construct(TokenRepositoryInterface $tokens,
                                UserProvider $users,
                                MailerContract $mailer,
                                $emailView)
    {
        $this->users = $users;
        $this->mailer = $mailer;
        $this->tokens = $tokens;
        $this->emailView = $emailView;
    }

    public function sendResetLink(array $credentials, Closure $callback = null)
    {
        $user = $this->getUser($credentials);
        if (is_null($user)) {
            return PasswordBrokerContract::INVALID_USER;
        }
        $token = $this->tokens->create($user);
        $this->emailResetLink($user, $token, $callback);
        return PasswordBrokerContract::RESET_LINK_SENT;
    }

    public function emailResetLink(CanResetPasswordContract $user, $token, Closure $callback = null)
    {
        $view = $this->emailView;
        return $this->mailer->send($view, compact('token', 'user'), function ($m) use ($user, $token, $callback) {
            $m->to($user->getEmailForPasswordReset());
            if (! is_null($callback)) {
                call_user_func($callback, $m, $user, $token);
            }
        });
    }
...

$this->getUserでDBテーブルusersに入力したEメールのレコードを取得して、$this->tokens->createでトークンを作成しDBに保存し、$this->emailResetLinkでパスワードリセットリンクを含むメールを送信します。マッチするDBレコードが存在しないならエラーコードを返して画面にエラーを表示となります。

ここにおいてメール送信に関していくつか。

まず、メール送信が行われるので、.envにおいてMAIL_DRIVERなどの設定が必要です。
さらに、config/mail.phpにおいて、fromの設定が必要です。

...
   'from' => ['address' => null, 'name' => null],
...

上のnullには、例えば、’support@gmail.com’、’サポート’のような具体的な値に置き換える必要あります。

次に、送信メールのテンプレートをresources/views/emails/password.blade.phpとして作成する必要あります。

以下のリンクをクリックして、パスワードのリセットができます。

{{ url('password/reset/'.$token) }

パスワードリセットリンク先の画面で、新規パスワードを設定する画面

さて、次はこのリンク先のパスワードリセット画面です。

まずは、routes.phpの設定から、

Route::get('password/reset/{token}', 'Auth\PasswordController@getReset');
Route::post('password/reset', 'Auth\PasswordController@postReset');

こちらもまた、PasswordControllerですね。

テンプレートは、reources/views/auth/reset.blade.phpのファイルとします。hiddenのtokenを忘れなく。

<form method="POST" action="{!! url() !!}/password/reset">
    {!! csrf_field() !!}
   <input type="hidden" name="token" value="{{ $token }}">
    <div>
        Eメール
        <input type="email" name="email" value="{{ old('email') }}">
    </div>

    <div>
        パスワード
        <input type="password" name="password">
    </div>

    <div>
        パスワードの確認
        <input type="password" name="password_confirmation">
    </div>

    <div>
        <button type="submit">パスワードをリセット</button>
    </div>
</form>

さて、この画面でボタンをクリックしたときの処理は、

  • 入力バリデーション。Eメールやパスワードの値チェック。EメールがDBに存在するかのチェック
  • DBのパスワードの値を暗号化して更新

でしょうか?見てみましょう。

まずは、先ほど出てきたトレイトのResetPasswords.php

...
    public function getReset($token = null)
    {
        if (is_null($token)) {
            throw new NotFoundHttpException;
        }
        return view('auth.reset')->with('token', $token);
    }
..
    public function postReset(Request $request)
    {
        $this->validate($request, [
            'token' => 'required',
            'email' => 'required|email',
            'password' => 'required|confirmed|min:6',
        ]);
        $credentials = $request->only(
            'email', 'password', 'password_confirmation', 'token'
        );
        $response = Password::reset($credentials, function ($user, $password) {
            $this->resetPassword($user, $password);
        });
        switch ($response) {
            case Password::PASSWORD_RESET:
                return redirect($this->redirectPath())->with('status', trans($response));
            default:
                return redirect()->back()
                            ->withInput($request->only('email'))
                            ->withErrors(['email' => trans($response)]);
        }
    }
...

入力バリデーションには、トークンが必須ですね。そして、Password::resetで処理を行い、その結果$responseが成功なら、指定のページにリダイレクト。エラーなら、同画面でエラー表示。

Password::resetの中身ですね、問題は。

こちらも、先のPasswordResetBroker.phpでコードされています。


...
    public function reset(array $credentials, Closure $callback)
    {
         $user = $this->validateReset($credentials);
        if (! $user instanceof CanResetPasswordContract) {
            return $user;
        }
        $pass = $credentials['password'];

        call_user_func($callback, $user, $pass);
        $this->tokens->delete($credentials['token']);
        return PasswordBrokerContract::PASSWORD_RESET;
    }

    protected function validateReset(array $credentials)
    {
        if (is_null($user = $this->getUser($credentials))) {
            return PasswordBrokerContract::INVALID_USER;
        }
        if (! $this->validateNewPassword($credentials)) {
            return PasswordBrokerContract::INVALID_PASSWORD;
        }
        if (! $this->tokens->exists($user, $credentials['token'])) {
            return PasswordBrokerContract::INVALID_TOKEN;
        }
        return $user;
    }

    public function validateNewPassword(array $credentials)
    {
        list($password, $confirm) = [
            $credentials['password'],
            $credentials['password_confirmation'],
        ];
        if (isset($this->passwordValidator)) {
            return call_user_func(
                $this->passwordValidator, $credentials) && $password === $confirm;
        }
        return $this->validatePasswordWithDefaults($credentials);
    }
...

resetでは、$this->validateResetで、ユーザーレコードの有無。新規パスワードのバリデーション、そしてトークンの存在と有効期限内かのチェックを行い、callbackでDBレコードのパスワードの更新を行います。そして最後に使用したトークンの削除となります。なるほど、削除しないと再度のリセットが可能になるので必要なわけですね。

ユーザー認証(6)ログインスロットル

スロットルと聞くと、どうしてもバイクのアクセルを想像してしまいます。どちらかというとスピードを出すために。スロットルは正確にはスピードを抑圧する意味で、「スロットル全開」というと、抑制なしで最高のスピードを出すということになります。

ここでのスロットルは、パスワードを意図的に変えて不正にログインしようという試み、たいていはプログラムによる機械的な攻撃、を抑える目的のセキュリティ対策です。具体的には、例えば過去5分間に5回ログインを失敗すると、次回のログインには10分間待たなければならないという仕組みです。もちろんそれですべて防御できるとは言えないですが、少なくとも抑制にはなります。

嬉しいことにこれも、Laravelに機能があります。

スロットル機能はログインで行われるので、以前に説明したログインのプログラムを再度見てみましょう。

まず、AuthControler

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    ...

ThrottlesLoginsのトレイトがありますね。そして、AuthenticatesAndRegistersUsersのトレイトの定義で使用されている、AuthenticatesUsersのトレイト、以前の紹介ではスロットルの部分を省略したので、今度はそこの部分を含めて見てみましょう。以下のコードのコメントを見てください。

namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;ろぐ
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Lang;

trait AuthenticatesUsers
{
    use RedirectsUsers;

    ...

    public function postLogin(Request $request)
    {
        $this->validate($request, [
            $this->loginUsername() => 'required', 'password' => 'required',
        ]);

        // ThrottlesLoginsは、上のAuthControllerのトレイトして使われているから、$throttleには値を含みます。
        $throttles = $this->isUsingThrottlesLoginsTrait();

    //ここで指定のログインの失敗回数を超えているなら、
       //画面にエラーメッセージを表示して次のログインを指定の時間待たせます。
        if ($throttles && $this->hasTooManyLoginAttempts($request)) {
            return $this->sendLockoutResponse($request);
        }

        //入力の値でログインを試みます。
        $credentials = $this->getCredentials($request);
        if (Auth::attempt($credentials, $request->has('remember'))) {
            return $this->handleUserWasAuthenticated($request, $throttles);
        }

    //ログインが失敗なら、ログインの失敗回数を1増やします。
        if ($throttles) {
            $this->incrementLoginAttempts($request);
        }
        return redirect($this->loginPath())
            ->withInput($request->only($this->loginUsername(), 'remember'))
            ->withErrors([
                $this->loginUsername() => $this->getFailedLoginMessage(),
            ]);
    }
 
    ...
}

というようにログインの失敗回数を追跡しています。

さて、何回まで失敗とか失敗回数を超過したら何分ログインを許さないかはどこで指定されているのでしょう。今度はトレイトのThrottleLoginsを見てみましょう。

namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Cache\RateLimiter;
use Illuminate\Support\Facades\Lang;

trait ThrottlesLogins
{
    //ここです、失敗回数をチェックしているのは。
    protected function hasTooManyLoginAttempts(Request $request)
    {
        return app(RateLimiter::class)->tooManyAttempts(
            $request->input($this->loginUsername()).$request->ip(),
            $this->maxLoginAttempts(), $this->lockoutTime() / 60
        );
    }

    ...

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

    protected function lockoutTime()
    {
        return property_exists($this, 'lockoutTime') ? $this->lockoutTime : 60;
    }
}

最初のhasTooManyLoginAttemptsのメソッドでは、maxLoginAttemptslockoutTimeのメソッドがコールされていますね。

それぞれ、ここのクラスのインスタンスで、maxLoginAttemptslockoutTimeの変数が定義されているなら、その値を使いますが、定義なしでは、最高5回、ログインロックは60秒という設定です。

また、失敗回数は、ログイン+IPアドレスの値がキーとなっています。つまり、同じログインで同じIPからログインを試みて連続5回失敗したら、1分のログインロックとなるということです。

最後に、これらの失敗回数の追跡やロックアウトの保持のデータは、どこで管理しているのでしょう。

先のコードで以下の参照をみてください。

use Illuminate\Cache\RateLimiter;

Cacheとして管理しているようです。Laravelでは、Cacheはデフォルトでファイルで管理しています。しかし、ファイルではなくデータベースや高速なメムキャッシュでのデータの管理も可能です。

config/cache.phpにおいて設定するか、.envにおいて、

CACHE_DRIVER=database

と設定することも可能です。データベースと設定するなら、以下のようにmigrationを通して、

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCacheTable extends Migration
{
    public function up()
    {
                Schema::create('cache', function($table) {
                        $table->string('key')->unique();
                        $table->text('value');
                        $table->integer('expiration');
                });
    }

    public function down()
    {
        //
    }
}

としてcacheテーブルを作成してください。ここで以下のようにデータベースの中身を見ながらテストをすると、実際どのようなレコードが作成されるかわかります。

例えば、ログインに失敗すると、以下のようなレコードが作成されます。

mysql> select * from cache\G;
*************************** 1. row ***************************
       key: laraveltest@gmail.com66.87.77.84
     value: eyJpdiI6IlJQN1Awck9BVzlTWXdnMk4wb0ZOMnc9PSIsInZhb...
expiration: 1449946110
*************************** 2. row ***************************
       key: laraveltest@gmail.com66.87.77.84:lockout
     value: eyJpdiI6Im5pMmg0dm1iREl0TFIwNkkzZTV1V2c9PSIsInZhb...
expiration: 1449946259
2 rows in set (0.00 sec)

最初のレコードは、失敗回数を数えているレコードです。キー(key)として、Eメールアドレス+IPアドレスとなっています。キーの値(value)は暗号化されています。回数が設定を超えると、2番目のレコードが作成されます。キーの最後に:lockoutが追加されていることに注意してください。こちらのレコードの期限は、設定のブロック時間(デフォルトは60秒)を経過した日時となっています。期限が過ぎてアクセスしたときに、これらのレコードは削除されて再度、失敗回数を追跡してきます。

ユーザー認証(7)スロットルのユニットテスト

前回話したログインのスロットル機能。もちろん手動、つまりブラウザ、で確認はできます。しかし、手動ではいちいち面倒ですね。自動で動作を確認できたらもっと良いです。そこで登場するのがユニットテスト。難しくはないです。チェックしたいのは、

  1. パスワードが正しいときのログインが成功するかのテスト
  2. パスワードを間違えて、失敗回数がデフォルトの5回となったときに、スロットされるかのテスト
  3. その後デフォルトの60秒待って、パスワードが正しいときに成功するかのテスト

他にもいくつか考えられますが、最低限は以上としてテストを作成してみましょう。

注意:パスワードには理解しやすいように日本語としていますが、実際は英数字が一般的です。

class LoginTest extends TestCase
{
    public function testLoginSuccess()
    {
        $this->visit('/auth/login')
            ->type('test@gmail.com', 'email')
            ->type('正しいパスワード', 'password')
            ->press('保存')
            ->see('ホームページ');
    }

    public function testLoginThrottle()
    {
        for($i=1;$i <=5; $i++)
        {
            $this->visit('/auth/login')
                ->type('test@gmail.com', 'email')
                ->type('間違ったパスワード', 'password')
                ->press('ログイン')
                ->see('Eメールとパスワードにマッチするレコードがありません。');
        }

        $this->visit('/auth/login')
            ->type('test@gmail.com', 'email')
            ->type('間違ったパスワード', 'password')
            ->press('ログイン')
            ->see('ログインの失敗回数が設定を超えました。次回のログインまで60秒お待ちください。');

        sleep(10);

        $this->visit('/auth/login')
            ->type('test@gmail.com', 'email')
            ->type('正しいパスワード', 'password')
            ->press('ログイン')
            ->see('ログインの失敗回数が設定を超えました。');

        sleep(50);

        $this->visit('/auth/login')
            ->type('test@gmail.com', 'email')
            ->type('正しいパスワード', 'password')
            ->press('ログイン')
            ->see('ホームページ');
    }
}

最初のテスト、testLoginSuccessは、正しいパスワードを入力したときのテストで、その後「ホームページ」という文が入ったページへ行くとする仮定。

次のtestLoginThrottleは、スロットルされるまでの失敗回数の確認とスロットル解除の確認のテスト。まず間違ったパスワードを入れて5回失敗させます。画面には、「Eメールとパスワードにマッチするレコードがありません」のエラーメッセージが表示されます。その後、さらに失敗すると、今度はログインをスロットルしたメッセージが表示されます。ここから60秒経過しないと次のログインができません。確認のために10秒後に正しいパスワードでログインしてみましょう。そこでは、さらに「ログインの失敗回数が設定を超えました。」を含むエラーメッセージがでます。残りの秒数は誤差が出ると思うので、その部分の確認はしません。そして、50秒後(つまり、スロットル開始から60秒後)には、正しいパスワードでログインが成功となるはずです。

さて、ここで注意です。Laravelに含まれるphpunitのテストの設定ファイルは、以下の内容です。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="bootstrap/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">app/</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
    </php>
</phpunit>

前回話したように、ログイン失敗回数や現在スロットルされていることなどの情報は、デフォルトではファイルを使用して行っています。しかし、テストでは CACHE_DRIVERは配列設定となっています。私のテストでは、そのためか上のテストはスロットル期間終了後に成功とはなりませんでした。どうも残りの秒数のカウントが負となっていしまい、うまくいかない。

この設定をphpunit.xmlでarrayからfileあるいはdatabaseに変更とすると、うまく行きました。

ユーザー認証(8)ユーザーの有効・無効を考慮

まだまだ続くユーザ認証。今まではいわば基礎編みたいなもので、教科書通りの紹介。今回は、実践として、こういうときはどうする?みたいな応用編です。

まずはDBテーブルusersの構成を見るところから。

+----------------+------------------+------+-----+---------------------+----------------+
| Field          | Type             | Null | Key | Default             | Extra          |
+----------------+------------------+------+-----+---------------------+----------------+
| id             | int(10) unsigned | NO   | PRI | NULL                | auto_increment |
| active_flag    | char(1)          | NO   |     |                     |                |
| name           | varchar(255)     | NO   |     | NULL                |                |
| email          | varchar(255)     | NO   | UNI | NULL                |                |
| password       | varchar(60)      | NO   |     | NULL                |                |
| remember_token | varchar(100)     | YES  |     | NULL                |                |
| created_at     | timestamp        | NO   |     | 0000-00-00 00:00:00 |                |
| updated_at     | timestamp        | NO   |     | 0000-00-00 00:00:00 |                |
+----------------+------------------+------+-----+---------------------+----------------+

ごく簡単な構成です。しかし、実際にはユーザーはすでに退会したかとか、問題があるユーザーをブロックする必要がある、のような理由で、有効・無効のフラッグをつけるのが一般的です。

ということで、active_flagとしてusersのフィールドを追加してみましょう。このactive_flagは、char(1)として有効はY、無効はNの値とします。

migrationを作成して、以下のように編集して実行。

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddActiveFlagToUsers extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function ($table) {
            $table->char('active_flag', 1)->after('id');
        });
    }
...
}

さて、今度は認証のプログラムの変更です。ユーザーのログインとパスワードがマッチかつユーザーが有効のときだけに認証を成功としたいです。さて、どこを変更すればよいのでしょうか?

これは以下のAuthController.phpにトレイトで定義されているメソッドの追加となります。

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Http\Request;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins {
        getCredentials as getCredentialsTrait;
    }

    public function __construct()
    {
        $this->middleware('guest', ['except' => 'getLogout']);

        $this->maxLoginAttempts = 5;
        $this->lockoutTime = 60;
    }

    protected function getCredentials(Request $request)
    {
        $credentials = $this->getCredentialsTrait($request);

        $credentials['active_flag'] = 'Y';

        return $credentials;
    }
    ...

getCredentialsは、AuthenticateAndRegistsUsersトレイトの中で使用されているAuthenticatesUsersのトレイト。以下を参照してください。

ユーザー認証(3)ログイン・ログアウト

ここで同じ名前のメソッドを使用して、クレデンシャルにユーザーが有効、つまり active_flag => 'Y'という条件を追加したいです。
しかし、ここではまず、すでに定義されているgetCredentialsの関数をコールしてからの追加としたい。問題は、

これでは、再帰となるし、

$credentials = $this->getCredentials($request);

スタテックのメソッドでも、継承したクラスのメソッドでもないのでこうともできない。

$credentials = parent::getCredentials($request);

それゆえに、

    use AuthenticatesAndRegistersUsers, ThrottlesLogins {
        getCredentials as getCredentialsTrait;
    }

として、オリジナルのメソッド名を改名して、

$credentials = $this->getCredentialsTrait($request);

となったわけです。

ユーザー認証(9)Laravel 5.2 コンポーネント自動作成

Laravelの5.2が登場してきました。いくつか興味ある変更がありますが、ユーザ認証に関してはスタート地点が身近になりました。

以下のコマンドを実行するだけで、

php artisan make:auth

以下を自動的に作成してくれます。

  1.  画面とEメールのテンプレート
    • ログイン画面
    • ユーザー登録画面
    • パスワードリセット情報送信画面
    • パスワードリセットのEメール
    • パスワードリセット画面
    • ホーム画面(ログイン後)
  2.  ホームコントローラ(HomeController.php)
  3.  ルータの設定の変更 (routes.php)

データベースを用意して、.envの設定ファイルを編集すれば、以下のように。

Laravel 2015-12-27 13-58-55

Laravel 2015-12-27 13-56-14
画面は、Bootstrapを使用していて、ちょっと編集すればすぐに実践で使えそうです。

ユーザー認証(10)Laravel 5.2 マルチ認証

マルチ認証と言っても、複数のステップでユーザーを認証するわけでもなく、ちょっとピンと来ないですね。

例えばECシステムにおいて、ユーザー画面での会員ログインと、管理画面での管理者のログインがそれぞれ別に必要とします。どちらもログインはEメールとは限らないし、片方でログインしたらもう片方でも認証となるとも限りません。つまり、ログインするユーザーの種類や場所が複数必要となる状況が多々あります。それに対応する機能が、マルチ認証です。

Laravelの5.1までは、マルチ認証は対応していなく、以下のようなパッケージをインストールして使用していました。

Laravel4.2対応のLaravel Multi Auth

Laravel5.1対応のMultiAuth for Laravel 5.1

しかし、5.2からはLaravelの基本仕様となっています。さすが、Taylorくん!

今回はこの機能を見てみましょう。

まず、デフォルトでインストールされるconfig/auth.phpの中身の解析。

Laravel 5.1では、

return [

    /* デフォルトの認証ドライバー */

    'driver' => 'eloquent',

    /* 認証に使用されるモデル */

    'model' => App\User::class,

    /* 認証に使用されるDBテーブル。ここでは、dirverがdatabaseでないので関係ない */

    'table' => 'users',

    /* パスワードリセットの設定 */
    'password' => [
        'email'  => 'emails.password', // resources/views/emails/passwordをリンク送信メールのテンプレートとする
        'table'  => 'password_resets',  // パスワードリセットのトークンの情報を保存するDBテーブル
        'expire' => 60,                 // トークンは60分で期限切れ
    ],
];

これがLaravel5.2では、

return [
    /* 認証のデフォルト設定 */

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],                         

    /* 認証のガードを定義 */                                                   

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],

    /* 認証のプロバイダー */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

   /* パスワードリセットの設定 */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'email' => 'auth.emails.password',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

となりました。違いは、guardsprovidersの導入です。

ちょっとこれではわかりにくいので、先の例を使って、ECサイトを想像してもらって、ショッピングをするユーザ画面と、サイトを管理する管理者画面があり、どちらもログインで認証が必要と仮定しましょう。ユーザー画面では買い物かごをチェックアウトするには、会員のログインが必要とします。

となると必要な設定は以下にようになります。

return [
    /* 認証のデフォルト設定 */

    'defaults' => [
        'guard' => 'users',
        'passwords' => 'users',
    ],                         

    /* 認証のガードを定義 */                                                   

    'guards' => [
        'users' => [
            'driver' => 'session',
            'provider' => 'users_provider',
        ],

        'admin_users' => [
            'driver' => 'session',
            'provider' => 'admin_users_provider',
        ],
    ],

    /* 認証のプロバイダー */

    'providers' => [
        'users_provider' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

        'admin_users_provider' => [
            'driver' => 'eloquent',
            'model' => App\AdminUser::class,
        ],
    ],

   /* パスワードリセットの設定 */

    'passwords' => [
        'users' => [
            'provider' => 'users_provider',
            'email' => 'auth.emails.password',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

];

ガードには、会員ログインのためのusersと、管理者ログインのためのadmin_usersの2つを定義します。どちらもセッションを使って、ログイン後の画面をプロテクトします。また、それらのプロバイダーで定義されているように、会員のUsersと管理者のAdminUsersのエロクエントモデルが認証のための情報提供元となります。

guardsprovidersの概念を導入することにより、今までの1つだけの認証のメカニズムを複数としたわけです。

さて、次はこの設定の使用です。このファイル以外で使用するのは、ガード名だけですから簡単です。

ガードを指定する場所はプログラムの中でいくつかありますが、以下のようにapp/Http/routes.phpで使用されるのが一番明確と思います。

例えば、ユーザー画面では、

    Route::group(['middleware' => 'guest:users'], function() {
        Route::get('login', 'user\AuthController@getLogin');
        Route::post('login', 'user\AuthController@postLogin');
        Route::get('signup', 'user\SignupController@getSignup');
        Route::post('signup', 'user\SignupController@postSignup');
        Route::get('password/email', 'user\PasswordController@getEmail');
        Route::post('password/email', 'user\PasswordController@postEmail');
        Route::get('password/reset/{token}', 'user\PasswordController@getReset');
        Route::post('password/reset', 'user\PasswordController@postReset');
    });

    Route:: group(['prefix' => 'member', 'middleware' => 'auth:users'], function() {
        Route::get('index', 'user\MemberController@getIndex');
        Route::get('password', 'user\MemberController@getPassword');
        Route::post('password', 'user\MemberController@postPassword');
        Route::get('profile', 'user\MemberController@getProfile');
        Route::post('profile', 'user\MemberController@postProfile');
        Route::get('logout', 'user\AuthController@getLogout');
    });

以前、ユーザー認証(4)認証でページを保護で説明したように、

ミドルウェアとして、guestauthが使われます。しかし、前回と違って、guest:usersのようにガード名を指定することが必要です。指定がないなら、auth.phpのデフォルトのセクションで指定したガードが自動的に使われます。

ちなみに、管理者側では、

Route::group(['prefix' => 'admin', 'middleware' => 'guest:admin_users'], function()
{
    Route::get('login', 'admin\AuthController@getLogin');
    Route::post('login', 'admin\AuthController@postLogin');
});

Route:: group(['prefix' => 'admin', 'middleware' => 'auth:admin_users'], function() {

    Route::get('logout', 'admin\AuthController@getLogout');
    Route::get('index', 'admin\HomeController@getIndex')->name('admin.home');
..

こんな感じです。

AuthControllerは、ユーザ画面と管理画面では、前回紹介した自動作成使われるもののコピーを編集する必用あります。

ユーザ画面では、

...

class AuthController extends BaseController
{
    protected $guard = 'users';

    protected $redirectTo = 'user/member/index';   // ログイン後のリダイレクト先
    protected $redirectAfterLogout = 'user/login';   // ログアウト後のリダイレクト先

    protected $username = 'email';               // ログインとなるDBの項目名

    protected $maxLoginAttempts = 5;             // ログインスロットルとなるまで最高のログイン失敗回数
    protected $lockoutTime = 60;                 // ログインスロットルとなってからの待ち秒数

    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    public function showLoginForm()
    {
        return view('user.login');  //テンプレートの場所を変える
    }
..

管理画面では、

...

class AuthController extends BaseController
{
    protected $guard = 'admin_users';

    protected $redirectTo = 'admin/index';   // ログイン後のリダイレクト先
    protected $redirectAfterLogout = 'admin/login';   // ログアウト後のリダイレクト先

    protected $username = 'login';               // ログインとなるDBの項目名

    protected $maxLoginAttempts = 5;             // ログインスロットルとなるまで最高のログイン失敗回数
    protected $lockoutTime = 60;                 // ログインスロットルとなってからの待ち秒数

    use AuthenticatesUsers, ThrottlesLogins;

    public function showLoginForm()
    {
        $form = new \stdClass();

        $form->login = Form::text('login', '',
            ['size' => 20, 'maxlength' => 20, 'class' => 'en', 'autofocus' => 'autofocus']);

        $form->password = Form::password('password',
            ['size' => 40, 'maxlength' => 20, 'class' => 'en']);

        return view('admin.login')->with(compact('form')); //テンプレートの場所を変える
    }
..

となります。認証が2つとなると、テンプレートなどいろいろな指定が必要となることに注意してください。上の例では、会員のログインは、emailですが、管理者のログインは、emailでなくてもよい文字列という仮定です。

最後に、ログイン後にログインしたユーザーの情報がほしいときは、今まで、

Auth::user()

でしたが、マルチ認証となると、

Auth::guard('users')->user()
Auth::guard('admin_users')->user()

と明確にガード名を指定する必用があります。

Top