読者です 読者をやめる 読者になる 読者になる

tanaka's Programming Memo

プログラミングについてのメモ。

Laravelの認証にSentinelを利用する

PHP Laravel Sentinel

Laravel5.3に対応させた記事を公開しました。 →
Laravel5.3でSentinelを利用する(1)LaravelとSentinel、Codeceptionのインストール - tanaka's Programming Memo








(一通りチェックしました。ユーザー登録、アクティベーション、パスワードの再設定、ログインをSentinelに置き換える手順です。 2016/7/28)
(「認証用のビューを作る」は、ローカルで動くようにする手順の前に必要だったので、位置を入れ替えました。2016/8/22)
(「コントローラーの作成」のregister()メソッドの引数が間違えていたので修正しました。2016/9/2)
(ログイン画面のinfoを変数に統一 2016/9/5)

はじめに

PHPフレームワークLaravelには簡単な認証や認可ができるauthパッケージが組み込まれてはいますが、ユーザー登録時にメールの確認などなしにいきなり登録できてしまったり、認可はできるにはできますが自前のロジックでロールを判断しないといけないなど、本格的に利用するには機能が不足しています。

Sentinelは上記のような機能を持つPHPフレームワークで、Laravel5に組み込むための機能を提供してくれています。Sentinelが提供するのはロジックだけで、ビューやユーザー確認用のメールなどは自前で実装する必要がありますが、それでも手間は相当減らせます。

上記を組み合わせた環境を構築するメモです。

前提環境

  • Laravel5.2
  • Sentinel2.0
  • mac OS X 10.11.5
  • PHP5.6.19
  • MySQL5.7.11
  • composerは事前にインストールできているものとする
  • Postfixを設定して、macからgmailなどを通してメール送信できる環境が構築できている

Laravelのインストール

  • macでターミナルを起動
  • プロジェクトフォルダーを作るフォルダーに移動
  • laravel -v を実行して、コマンドが見つからなかったらいかを実行して、グローバル環境にLaravelをインストールする
composer global require "laravel/installer"
  • 以下で、Laravelのプロジェクトを作成して、中に入る
laravel new lara-sentinel
cd lara-sentinel

データベースを作成する

今回のサンプルで操作するためのデータベースを作成します。

  • MySQL にログインする
mysql -u ユーザー名 -p
  • 上記でMySQLにログインできない場合は、MySQLサーバーを起動
sudo mysql.server start
  • データベースを作成する。以下は、lara_sentinelというデータベースを作成する例
create database lara_sentinel;
  • 操作用のユーザーを作成して、権限を与えておく。以下は、user_lara_sentというローカルホスト上のユーザーを作成する例。パスワードは任意
CREATE USER 'user_lara_sent'@'localhost' IDENTIFIED BY 'YourPassword';
  • 作成したローカルホストのuser_lara_sentユーザーに、lara_sentinelデータベースへのアクセス権限を与える
GRANT ALL PRIVILEGES ON lara_sentinel.* TO 'user_lara_sent'@'localhost';

Laravelの環境設定

  • Atomなどのエディターで、上記で作成したlara-sentinelのプロジェクトフォルダーを開く
  • .envを開いて、各種設定を行う
    • APP_URLを、 http://0.0.0.0:8080 に変更
    • DB_DATABASEを、作成したデータベース名に変更。ここでは lara_sentinel
    • DB_USERNAMEを、作成したユーザー名に変更。ここでは、 user_lara_sent
    • DB_PASSWORDを、設定したパスワードに変更。任意に設定したパスワードを書く
    • メール設定をいかに差し替える
MAIL_DRIVER=smtp
MAIL_HOST=localhost
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_NAME='送信元名'
MAIL_FROM_ADDRESS=from@mail.addr
MAIL_SENDMAIL='/usr/sbin/sendmail -bs'
MAIL_PRETEND=false

起動テスト

Laravelの起動を試します。

  • ターミナルで、プロジェクトフォルダーから以下を実行
php -S 0.0.0.0:8080 -t public

Sentinelをインストール

ここからは Sentinel の公式ページのインストール記事に従います。

  • プロジェクトフォルダー内から以下を実行してインストールする
composer require cartalyst/sentinel
  • config/app.php を開く
  • providers の配列を探して、その中に以下の行を加える
        /*
         * Sentinel Service Providers...
         */
        Cartalyst\Sentinel\Laravel\SentinelServiceProvider::class,
  • その下の aliases の配列に、以下を加える
        // Sentinel Aliases
        'Activation' => Cartalyst\Sentinel\Laravel\Facades\Activation::class,
        'Reminder'   => Cartalyst\Sentinel\Laravel\Facades\Reminder::class,
        'Sentinel'   => Cartalyst\Sentinel\Laravel\Facades\Sentinel::class,
  • 上書き保存する
  • ターミナルから以下を実行して、vendorフォルダーに必要なプロバイダーを出力する
php artisan vendor:publish --provider="Cartalyst\Sentinel\Laravel\SentinelServiceProvider"
  • 既存のユーザーテーブルは不要なので、マイグレーションで作成されないように以下のファイルを削除する
    • database/migrations/2014_10_12_000000_create_users_table.php
    • database/migrations/2014_10_12_100000_create_password_resets_table.php
  • 以下を実行して、データベースを生成する
php artisan migrate
  • ここでエラーが発生したら、.envを確認してデータベースの接続情報が合っているかや、MySQLサーバーが起動しているかを確認する

以上でSentinelの設置は完了です。 config/cartalyst.sentinel.php にSentinelの設定があるので、必要があれば変更します。

Webブラウザーhttp://0.0.0.0:8080 を再読み込みして、エラーが発生しなければここまで成功です。


認証用のビューを作る

手間を減らすために、認証関連のビューやコントローラーを既存のauth機能から取り込みます。すべて手製にするのであれば、この手順は不要です。

  • ターミナルからプロジェクトフォルダー内で以下を実行
php artisan make:auth

http://0.0.0.0:8080 を再読み込みすると、ビューが変更されて、ヘッダーに認証のためのボタンが並びます。Laravelのauth機能が組み込まれていますが、これをSentinelに差し替えていきます。


ローカルで実行できるように必要なものをダウンロードして組み込む

  • npmで必要なパッケージをsave-devで保存
npm install —-save-dev bootstrap font-awesome jquery@~2
  • publicフォルダー内に、vendorsフォルダーを作成
  • vendorsフォルダー内に、bootstrapフォルダーとjqueryフォルダーとfont-awesomeフォルダーを作成
  • node_modules/bootstrap/distフォルダー内のcssフォルダー、fontsフォルダー、jsフォルダーを、public/vendors/bootstrapフォルダーにコピー
  • node_modules/jquery/distフォルダー内のファイルを、public/vendors/jqueryフォルダーにコピー
  • node_modules/font-awesome/distフォルダー内の、cssフォルダーとfontsフォルダーを、public/vendors/font-awesomeフォルダーにコピー
  • resources/views/layouts/app.blade.php をエディターで開く
  • 以下の2行をコメントアウトして、ローカルのリンクに差し替える
{{--
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css" integrity="sha384-XdYbMnZ/QjLh6iI4ogqCTaIjrFk87ip+ekIjefZch0Y+PvJ8CDYtEs1ipDmPorQ+" crossorigin="anonymous">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:100,300,400,700">
--}}
<link rel="stylesheet" href="{{url('vendors/font-awesome/css/font-awesome.min.css')}}">
  • stylesは以下の通り
{{--
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
--}}
<link rel="stylesheet" href="{{url('vendors/bootstrap/css/bootstrap.min.css')}}">
<!-- JavaScripts -->
{{--
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js" integrity="sha384-I6F5OKECLVtK/BL+8iSLDEHowSAfUo76ZL9+kGAgTRdiByINKJaqTPH/QVNS1VDb" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
--}}
<script src="{{url('vendors/jquery/jquery.min.js')}}"></script>
<script src="{{url('vendors/bootstrap/js/bootstrap.min.js')}}"></script>

表示するメッセージを準備する

メッセージをハードコーディングするのを避けるために、言語ファイルを作成しておきます。

  • Finderなどでプロジェクトフォルダーを開く
  • resources/langフォルダーを開く
  • enフォルダーをコピーして、そのまま貼り付けて複製する
  • 「en のコピー」から、「ja」にフォルダー名を変更する
  • 両方のフォルダーに[sentinel.php]というファイルを作成しておく
  • config/app.php をエディターで開く
  • 'locale'を'en'から'ja'に変更する

以上で完了です。以降、sentinel.phpに追加した配列を、PHPのコードからは trans('sentinel.要素名')、bladeからは@lang('sentinel.要素名')で参照できます。


ユーザー登録

まずはユーザー登録をSentinelのものに入れ替えます。

Sentinelのユーザー登録についてはこちら

ここでは次のことをします。

  • ユーザー名と、メールアドレスと、パスワードと、確認用のパスワードを受け付ける
  • ユーザー名は1項目のみにして、first_nameを利用
  • ユーザー登録を完了するには、メールによる確認作業を必要にする
  • ユーザー名、メールアドレス、パスワードは必須
  • パスワードと確認用パスワードは一致していなければならない

ビューの調整

必要な項目は揃っているので変更しなくても構いません。表記を変更したり、表示を日本語にしたい、独自の欄を設けたい場合などの手順です。

  • resources/views/auth/register.blade.php をエディターで開く
  • 変更したい箇所を修正する
  • 上書き保存

コントローラーの作成

Sentinelを操作するコントローラーを作成します。app/Http/Controllers/Sentinelフォルダーの中に作成する例です。

  • ターミナルから以下を実行
php artisan make:controller Sentinel/SentinelController
  • ユーザー登録は、registerメソッドで行うことにする。app/Http/Controllers/Sentinel/SentinelController.phpをエディターで開いて、以下のようにする
<?php

namespace App\Http\Controllers\Sentinel;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;
use Sentinel;
use Mail;
use Activation;

class SentinelController extends Controller
{
    /**
     * ユーザー登録
     */
     protected function register(Request $request)
     {
         // バリデーション
         $this->validate($request, [
            // nameは必須で、255文字まで
            'name' => 'required|max:255',
            // emailは必須で、emailの形式で、255文字までで、usersテーブル内でユニーク
            'email' => 'required|email|max:255|unique:users',
            // passwordは必須で、6文字以上255文字以下で、確認欄と一致する必要がある
            'password' => 'required|between:6,255|confirmed',
        ]);

        // 情報に問題がなければ、ユーザー登録
        $credentials = [
            'first_name' => $request['name'],
            'email' => $request['email'],
            'password' => $request['password'],
        ];
        $user = Sentinel::register($credentials);

        // アクティベーションを作成する
        $activation = Activation::create($user);
        // メールで送信する
        $this->sendActivationCode($user, $activation->code);

        // メールを確認して、承認してからログインすることを表示するページへ
        return redirect('login')->with('info', trans('sentinel.after_register'));
    }

    /**
     * 指定のユーザーに、指定のコードをメールで送信する
     * @param Cartalyst\Sentinel\Users\UserInterface $user ユーザー
     * @param string アクティベーションコード
     */
    private function sendActivationCode($user, $code) {
        Mail::send('sentinel.emails.activation', [
            'user' => $user,
            'code' => $code,
        ], function($m) use ($user) {
            $m->from(config('app.activation_from'), config('app.appname'));
            $m->to($user->email, $user->name)->subject(trans('sentinel.activate_title'));
        });
    }

}
  • 上書き保存

ルートの設定

コントローラーができたので、registerで呼び出したら登録するようにルートを設定します。ついでに、loginのルートも設定しておきます。

  • app/Http/routes.php をエディターで開く
  • Route::auth(); は、Laravelのauthは利用しないので削除
  • POSTメソッドのregisterで登録を発動させるので、Route::auth()があった場所に、以下を追加
Route::get('login', function() {return view('auth.login', [
    'info' => session('info'),
    'myerror' => session('myerror')
]);});
Route::post('login', 'Sentinel\SentinelController@login');

Route::get('register', function() {return view('auth.register');});
Route::post('register', 'Sentinel\SentinelController@register');

登録後のメッセージを表示できるように、loginビューに表示領域を追加します。

  • resources/views/auth/login.blade.php をエディターで開く
  • <div class="panel-body"> という行の下に、以下を追加
                    @if(isset($info))
                    <div class="alert alert-info">
                        {{ $info }}
                    </div>
                    @endif
                    @if(isset($myerror))
                    <div class="alert alert-danger">
                        {{ $myerror }}
                    </div>
                    @endif

以上で、viewからはinfoとmyerror、redirectする場合はsessionでinfoかmyerrorに情報を渡すことで、ログイン画面にメッセージを表示します。

メッセージの追加

エラーなどのメッセージを用意します。

  • resources/lang/ja/sentinel.php を開いて、以下を追加する
<?php
return [
    'activate_title' => 'ユーザー登録を完了してください',
    'after_register' => 'ご登録いただいたメールアドレスに、登録を完了するためのリンクを書いたメールを送信しました。メールを開いて、リンクを押して、ユーザー登録を完了させたら、以下からログインしてください。',
];
  • 上記のファイルを、enフォルダーにもコピーする

以上でルートとビューは完成です。あとは、メールを送信するために必要なデータを設定します。


メールの設定

ユーザーを有効にするアクティベーションにメールを使います。メールの送信はLaravelのMailクラスを使うと簡単にできます。

メール本文には、Bladeのビューを使います。Mail::send()メソッドの1つ目の引数は、メール本文のビューの名前。2つ目の引数は、ビューに渡す変数の連想配列、3つ目はメッセージのインスタンスを受け取るコールバック関数で、返信先や件名などをカスタマイズできます。

必要な設定を作成

  • config/app.php を開く
  • return []の中に、以下のようにアプリ名と送信元メールアドレスを追加
    'appname' => 'Laravel-Sentinelテスト',
    'activation_from' => env('ACTIVATION_FROM', 'from@email.com'),
  • ACTIVATION_FROMを環境ごとに変更する場合は、.env ファイルに定義を追加する

ビューを作成する

  • resources/views/sentinel/emails/activation.blade.php を作成
  • 以下のような本文を作成
{{ $user->first_name }}様<br>
<br>
[{{ config('app.appname') }}]へのユーザー登録を仮受付いたしました。<br>
以下のリンクをクリックして、登録を確定させてください。<br>
<br>
<a href="{{ $link = url('activate', [base64_encode($user->email), $code])}}">{{ $link }}</a><br>
<br>
--------<br>
[{{config('app.appname')}}]システムメール<br>
*本メールは登録専用のものです。返信には使えません。<br>
<br>

以上で、ユーザー登録の申請から、アクティベーション用のメールを送信するところまでできました。実行すると、登録した宛先にアクティベーション用のメールが送信されます。

HTMLメールを使いたくない場合は

送信時に、配列でtextを指示することで、プレーンテキストのメールにできます。

        Mail::send(['text' => 'sentinel.emails.activation'], [
            'user' => $user,
            'code' => $code,
        ], function($m) use ($user) {
            $m->from(config('app.activation_from'), config('app.appname'));
            $m->to($user->email, $user->name)->subject(trans('sentinel.activate_title'));
        });

なお、この場合は当然ですがHTMLタグは使えませんので、ビューからタグは削除してください。

アクティベーション処理

メールで送信した認証コードを使って、アクティベーションを行う処理を作成します。

アクティベーション用のルートを作成

メールからは、 activate/{メールアドレス}/{アクティベーションコード} という形式でアクティベーション用のアクセスがあります。これを受け取って、処理するためのルートを追加します。

  • app/Http/routes.php をエディターで開く
  • registerの登録の下に、以下を追加
Route::get('activate/{email}/{code}', 'Sentinel\SentinelController@activate');

アクティベーションの実行

ルートを定義したので、それを処理する activate メソッドを SentinelController.php に追加します。

ルート内のパラメーター({email}と{code})は、Requestに入れられて渡されますので、$request->emailや$request->codeのようにアクセスすることができます。

  • app/Http/Controller/Sentinel/SentinelController.php をエディターで開く
  • 以下の activate() メソッドをクラスに追加する
    /**
     * アクティベーション
     */
    protected function activate(Request $request) {
        // ユーザーを取得する
        $user = Sentinel::findByCredentials(['email' => base64_decode($request->email)]);
        if (is_null($user)) {
            return redirect('login')->with(['myerror' => trans('sentinel.invalid_activation_params')]);
        }

        // アクティベーション済みだった場合、そのまま戻る
        if (Activation::completed($user)) {
            return redirect('login');
        }

        // アクティベーションを実行する
        if (!Activation::complete($user, $request->code)) {
            return redirect('login')->with(['myerror' => trans('sentinel.invalid_activation_params')]);
        }

        return redirect('login')->with(['info' => trans('sentinel.activation_done')]);
    }

必要なメッセージの追加

アクティベーション関連のメッセージを追加します。

  • resources/lang/enフォルダーと、resources/lang/jaフォルダー内のsentinel.phpに、以下を追加
    'invalid_activation_params' => 'アクティベーションの情報が一致しませんでした。',
    'activation_done' => 'ユーザー登録を完了しました。ログインして、サービスをご利用ください。',


以上で、アクティベーション処理は完成です。メールに届いたアクティベーションのリンクをクリックすると、アクティベーションが完了して、ログインできるようになります。また、 http://0.0.0.0:8080/activate/bademail/badcode などでアクセスすると、アクティベーションのパラメーターが不正である旨、表示されます。


ログイン処理の実装

アクティベーションが完了したら、ログインができるようになります。ログイン処理を、Sentinelに変更します。ルートは前の手順ですでに作成済みなので、postに対するコントローラーの作成を行います。

必要なメッセージの追加

ログインに関連するメッセージを追加します。

  • resources/lang/ja/sentinel.php をエディターで開く
  • 以下のメッセージを追加
    'not_activation' => 'ユーザー登録が完了していません。登録したメールアドレスに、登録確認用のメールを送信してあります。メールを開いて、リンクをクリックしてください。メールを紛失した場合は、下のリンクからメールを再送できます。',
    'login_failed' => 'ログインに失敗しました。正しいメールアドレスとパスワードでログインしてください。',
    'login_throttling' => 'ログイン失敗が規定回数を越えました。一定時間、IPを凍結します。',

メニューをログイン・ログアウトに合わせて変更

ログイン実装に先立って、ヘッダーメニューがログインしているかどうかに応じて切り替わるようにしておきます。切り替え処理は app.blade.php 内で行っているので修正します。

  • resources/views/layouts/app.blade.php をエディターで開く
  • @if (Auth::guest()) という行を探して、以下に書き換える
                    @if (Sentinel::guest())
  • {{ Auth::user()->name }} という箇所を探して、以下に書き換える
{{ Sentinel::getUser()->first_name }}

以上で完了です。ログインしたらメニューが切り替わるのが確認できるようになりました。

ログイン処理の実装

ログイン処理を実装します。アクティベーションされていない時や、ログイン失敗時の処理もまとめて作成します。アクティベーションされていない時には、アクティベーションコードの再送信をする指示をビューに投げていますが、この辺りの実装は後ほど行います。

  • app/Http/Controllers/Sentinel/SentinelController.php をエディターで開く
  • アクティベーション時と、ログイン失敗による凍結を確認するために、以下のuseを冒頭に追加する
use Cartalyst\Sentinel\Checkpoints\NotActivatedException;
use Cartalyst\Sentinel\Checkpoints\ThrottlingException;
  • ログインが成功した時に表示する先のパスを、クラス内に定義する。以下は、ログインしたらルートパスを表示するもの。適宜、変更すること
    /**
     * ログイン後に表示するパス
     *
     * @var string
     */
    protected $redirectTo = '/';
  • 以下のloginメソッドをSentinelControllerクラスに追加
    /**
     * ログイン
     */
    protected function login(Request $request) {
        // バリデーション
        $this->validate($request, [
            'email' => 'required|email|max:255',
            'password' => 'required|between:6,255',
            'remember' => 'boolean',
        ]);

        // 認証処理
        try {
            $this->userInterface = Sentinel::authenticate([
                'email' => $request['email'],
                'password' => $request['password']
            ], $request['remember']);
        } catch (NotActivatedException $notactivated) {
            return view('auth.login', [
                'myerror' => trans('sentinel.not_activation'),
                'resend_code' => $request['email'],
            ]);
        } catch (ThrottlingException $throttling) {
            return view('auth.login', ['myerror' => trans('sentinel.login_throttling')."[あと".$throttling->getDelay()."秒]"]);
        }

        if (!$this->userInterface) {
            // エラー
            return view('auth.login', ['myerror' => trans('sentinel.login_failed')]);
        }

        return redirect($this->redirectTo);
    }

以上で、ログイン、未アクティベーション、IP凍結に対応しました。あとは、未アクティベーション時に、再度アクティベーションコードをメールする処理を追加します。

アクティベーションコードの再送信

ユーザーが誤ってアクティベーションコードを削除した場合を想定して、アクティベーションコードを再送信する機能を追加します。約束事は以下の通りです。

以下、実装手順です。

  • app/Http/routes.php をエディターで開く
  • Route::get('register', ・・・);の上に、以下のコードを追加する
Route::get('register/{email}', 'Sentinel\SentinelController@resendActivationCode');
  • app/Http/Controller/Sentinel/SentinelController.php をエディターで開く
  • 以下の関数を、クラスに追加する
    /**
     * 指定のメールアドレスのアクティベーションコードを再送する
     */
    protected function resendActivationCode(Request $request) {
        // 古いアクティベーションコードを削除
        Activation::removeExpired();

        // ユーザーを確認
        $user = Sentinel::findByCredentials(['email' => base64_decode($request->email)]);
        if (is_null($user)) {
            return redirect('login')->with(['myerror' => trans('sentinel.invalid_activation_params')]);
        }

        // すでにアクティベート済みの時は、何もせずにログインへ
        if (Activation::completed($user)) {
            return redirect('login')->with(['info' => trans('sentinel.activation_done')]);
        }

        // アクティベーションの状況を確認
        $exists = Activation::exists($user);
        if (!$exists) {
            // 存在しない場合は、再生成して、そのコードを送信する
            $activation = Activation::create($user);
        }
        else {
            // 現在のコードを
            $activation = $exists;
        }

        // メールで送信する
        $this->sendActivationCode($user, $activation->code);
        // メールを確認して、承認してからログインすることを表示するページへ
        return redirect('login')->with('info', trans('sentinel.after_register'));
    }

ビューの調整

アクティベーションが済んでいない時に、アクティベーションコードを再送信するためのリンクを表示するようにします。アクティベーションの再送信は、

  • resources/views/auth/login.blade.php をエディターで開く
  • パスワード欄の下に、以下のコードを追加
                        @if(isset($resend_code))
                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <a class="btn btn-link" href="{{ url('register', base64_encode($resend_code))}}">
                                    ユーザー登録を完了させるメールを再送
                                </a>
                            </div>
                        </div>
                        @endif

以上で完了です。ユーザー登録をした後、アクティベーションをメールで行う前に、ログインを試してみてください。アクティベートがみ完了である旨、メッセージが表示されて、アクティベートコードの再送信のリンクが表示されます。リンクをクリックすると、アクティベートコードが再送信されます。その時点で、アクティベートコードが古くなっていたら、新しいアクティベートコードを生成して、そのコードをメールします。


パスワードリセットをSentinelに置き換える

login画面のパスワードリセットに対応します。

ルートの作成

パスワードリセット画面の呼び出しと、パスワードリセットを実行するPOSTルートを定義します。

  • app/Http/routes/php をエディターで開く
  • registerルートの下辺りに、以下を追加
Route::get('password/reset/{email}/{code}/{password}', 'Sentinel\SentinelController@resetPassword');
Route::get('password/reset', function() {return view('auth.passwords.reset', ['token'=>'']);});
Route::post('password/reset', 'Sentinel\SentinelController@sendResetPassword');

リマインダーに必要なメッセージを追加

リマインダーに関連する文言をメッセージに追加しましょう。

  • resources/lang/ja/sentinel.php をエディターで開く
  • 以下のメッセージを配列に追加する
    'password_reset_sent' => 'パスワードを再設定するためのメールを送信しました。届いたメールに記載のリンクをクリックするとパスワードの再設定が完了しますので、新しいパスワードでログインしてください。',
    'reminder_title' => 'パスワードをリセットできます。',
    'password_reset_done' => 'パスワードをリセットしました。',
    'password_reset_failed' => 'リセットコードが古くなっていました。もう一度、リセットし直してください。',

メールのビューを作成

パスワードをリセットするためのメールの本文を作成します。

  • resources/views/sentinel/mails/reminder.blade.php を作成して、以下のコードを書く
{{ $user->first_name }}様<br>
<br>
[{{ config('app.appname') }}]のパスワードを再設定するには、以下のリンクをクリックしてください。<br>
<br>
<a href="{{ $link = url('password/reset', [base64_encode($user->email), $code, base64_encode($password)])}}">{{ $link }}</a><br>
<br>
--------<br>
[{{config('app.appname')}}]システムメール<br>
*本メールは登録専用のものです。返信には使えません。<br>
<br>

パスワードをリセットするメソッドの作成

  • app/Http/Controllers/Sentinel/SentinelController.php をエディターで開く
  • SentinelのReminderを使えるように、冒頭にuseを追加
use Reminder;
  • クラス内に、以下のコードを追加
    /**
     * パスワードを再設定するための処理
     * ユーザーが有効で、パスワードが条件に合致していたら、SentinelのReminderを使って処理する
     */
    protected function sendResetPassword(Request $request) {
        // 古いリマインダーコードを削除
        Reminder::removeExpired();

        // チェック
        $this->validate($request, [
           // emailは必須で、emailの形式で、255文字まで
           // メールアドレスの有無は、不正を避けるためにチェックしない
           'email' => 'required|email|max:255',
           // passwordは必須で、6文字以上255文字以下で、確認欄と一致する必要がある
           'password' => 'required|between:6,255|confirmed',
       ]);

       // ユーザーを検索
       $user = Sentinel::findByCredentials(['email'=>$request->email]);
       if (is_null($user)) {
           // ユーザーがいなければ成功したような感じにしてログイン画面へ
           return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
       }

       // リマインダーが作成済みなら、それを再送信する
       $code = "";
       $exists = Reminder::exists($user);
       if ($exists) {
           // すでに設定されているので、リマインダーコードを設定
           $code = $exists->code;
       }
       else {
           // 新規にリマインダーを作成して、コードを返す
           $reminder = Reminder::create($user);
           $code = $reminder->code;
       }

       // メールを送信
       Mail::send('sentinel.emails.reminder', [
           'user' => $user,
           'code' => $code,
           'password' => $request->password,
       ], function($m) use ($user) {
           $m->from(config('app.activation_from'), config('app.appname'));
           $m->to($user->email, $user->name)->subject(trans('sentinel.reminder_title'));
       });

       // 成功したら、login画面へ移動
       return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
    }

バリデーションは以下に対して行います。

  • 空欄
  • パスワードが6文字以上、255文字以下じゃない
  • パスワードと確認が一致しない
  • メールの有無を確認するとメールアドレスを探索される可能性があるのでチェックしない

以上で、パスワードをリセットするメールの送信まで出来ました。 http://0.0.0.0:8080/login にアクセスして、[Forgot Your Password?]をクリックして、Reset Passwordの画面に移動します。この画面はそのまま利用できます。emailと新しく設定するパスワードを入力してボタンを押すと、メールが送信されます。

リマインダーの実行

メールで送られたリンクをクリックした時の処理を実装して完成させます。

  • app/Http/Controllers/Sentinel/SentinelController.php をエディターで開く
  • 以下のresetPasswordメソッドをクラスに追加する
    /**
     * パスワードのリセットを実行する
     */
    protected function resetPassword(Request $request) {
        // データ長を調整しておく
        $email = substr(base64_decode($request->email), 0, 255);
        $code = substr($request->code, 0, 64);
        $passwd = substr(base64_decode($request->password), 0,255);

        $user = Sentinel::findByCredentials(['email' => $email]);
        if (is_null($user)) {
            // 不正なアクセスだが、正常に終わったようなメッセージを返す
            return redirect('login')->with('info', trans('sentinel.password_reset_done'));
        }

        // リマインダーを完了させる
        if (Reminder::complete($user, $code, $passwd)) {
            // 成功
            return redirect('login')->with('info', trans('sentinel.password_reset_done'));
        }

        // 失敗
        return redirect('login')->with('info', trans('sentinel.password_reset_failed'));
    }

ルートはすでに設定しているので、以上で完成です。メールに届いたリンクをクリックして、パスワードが変更されることを確認してください。


ログアウト

ログアウトに対応させます。

ルートの追加

ログアウトのルートを設定します。

  • app/Http/routes.php をエディターで開く
  • 以下のルートを適当な場所に追加する
Route::get('logout', 'Sentinel\SentinelController@logout');

コントローラーを追加する

  • app/Http/Sentinel/SentinelController.php
  • logoutメソッドをクラスに追加する
    /**
     * ログアウト処理
     */
    protected function logout(Request $request) {
        Sentinel::logout();

        return redirect($this->redirectTo);
    }

以上でログアウトの実装完了です。


Sentinel用の認可ミドルウェアを提供

ログインしているのに、Homeを表示しようとするとログイン画面に移行してしまいます。これは、ログインしているかどうかのチェックがまだSentinelに対応していないためです。Laravel Authの認証ミドルウェアを、Sentinelのものに書き換えてログインに対応します。

Authのguard(毎回、認証を要求するアカウント)は今回は利用の予定がなかったので、シンプルにログインしているかのチェックのみにします。

  • app/Http/Middleware/Authenticate.php をエディターで開く
  • Sentinelにアクセスできるように以下のuse文を追加
use Sentinel;
  • handleメソッドを以下のように書き換える
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Sentinel::guest()) {
            if ($request->ajax() || $request->wantsJson()) {
                // ajaxでのアクセスや、JSONの場合はテキストのみ
                return response('Unauthorized.', 401);
            } else {
                // ログインしていないのでログイン画面へ
                return redirect()->guest('login');
            }
        }

        // 認証しているので指定のルートへ
        return $next($request);
    }

以上で対応できました。ログインすると、Homeにアクセスできるようになります。

コントローラーの__construct()で $this->middleware('auth'); と書いておくと、そのコントローラーの操作は、Sentinelでログインしていないとログイン画面にリダイレクトされるようになります。

ついでに、RedirectIfAuthenticated ミドルウェアもSentinelに対応させておきましょう。

  • app/Http/Middleware/RedirectIfAuthenticated.php をエディターで開く
  • Sentinelにアクセスできるように以下のuse文を追加
use Sentinel;
  • handleメソッドを以下のように修正する
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Sentinel::check()) {
            return redirect('/');
        }

        return $next($request);
    }

上記は、 app/Http/Kernel.php の$routeMiddleware 配列に guest というミドルウェア名で定義されています。

      • -


以上で、Laravelの認証の機能を全て置き換えることができました。ユーザー登録にメールによる確認が必要になるのはメリットが大きいです。

編集可能なロールやパーミッションを使った認可機能がつけば、さらにSentinelに変更した効果が高まります。こちらに記事をまとめました。