初めてのLaravel 5.6 : (30) Middleware

ログインが出来るようになったので、記事の作成や編集、削除はログインしていないと実行出来ないように制限をかけたいと思います。Laravel ではこれらのフィルタリングをミドルウェアの中で実行します。


Middleware

開発者が修正して良いミドルウェアは以下のディレクトリに格納されています。

app/Http/Middleware/
├── CheckForMaintenanceMode.php
├── EncryptCookies.php
├── RedirectIfAuthenticated.php
├── TrimStrings.php
├── TrustProxies.php
└── VerifyCsrfToken.php

その他、Laravel フレームワーク自身が提供するミドルウェアも vendor/laravel/framework 以下に多数あります。

RedirectIfAuthenticated.php を見てみます。

<?php
// app/Http/Middleware/RedirectIfAuthenticated.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  $guard
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            // ログインしていたら、リダイレクトする
            return redirect('/home');
        }

        return $next($request);
    }
}

ポイントは handle メソッドです。
ルートからコントローラのメソッドが呼び出される前に、handle メソッドが呼び出されます。
handle メソッドの中で、アプリケーションの次に進むかどうかを判断しています。
RedirectIfAuthenticated.php では、ログインしていたら、’/home’ にリダイレクトしています。
ログインしていない時は $next() コールバックを呼び出して処理を継続します。

ファイル名やこの処理の内容から、ログインしている時にアクセスできないようにする為のミドルウェアのようです。

むむむっ、ちょっとまずいですね。前回の修正の影響でバグっていることを発見してしまいました。
気づきましたか?後ほど修正します。


Middleware の登録

app/Html/Kernel.php の中でミドルウェアをシステムに登録します。

<?php
// app/Html/Kernel.php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\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,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];
}

グローバルミドルウェア

全ての HTTP リクエストに適用したいミドルウェアは $middleware 配列に登録します。

ルートミドルウェア

ルート毎に適用したいミドルウエアは $routeMiddleware 配列にキーと共に登録します。
ルートにミドルウェアを適用するには以下の様に設定します。

// routes/web.php

Route::get('admin/profile', function () {
    // ...
})->middleware('auth', '2つ目', ...);
// $routeMiddleware に登録したキーで指定します。

ミドルウェアグループ

1つのキーで複数のミドルウェアの適用をしたい時は $middlewareGroups 配列に登録します。


ルートミドルウェアの適用状況

ルートミドルウェアの適用状況は artisan route:list コマンドで確認できます。

artisan route:list

+--------+-----------+-------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method    | URI                     | Name             | Action                                                                 | Middleware   |
+--------+-----------+-------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD  | /                       | home             | App\Http\Controllers\ArticlesController@index                          | web          |
|        | GET|HEAD  | about                   | about            | App\Http\Controllers\PagesController@about                             | web          |
|        | GET|HEAD  | api/user                |                  | Closure                                                                | api,auth:api |
|        | POST      | articles                | articles.store   | App\Http\Controllers\ArticlesController@store                          | web          |
|        | GET|HEAD  | articles                | articles.index   | App\Http\Controllers\ArticlesController@index                          | web          |
|        | GET|HEAD  | articles/create         | articles.create  | App\Http\Controllers\ArticlesController@create                         | web          |
|        | DELETE    | articles/{article}      | articles.destroy | App\Http\Controllers\ArticlesController@destroy                        | web          |
|        | PUT|PATCH | articles/{article}      | articles.update  | App\Http\Controllers\ArticlesController@update                         | web          |
|        | GET|HEAD  | articles/{article}      | articles.show    | App\Http\Controllers\ArticlesController@show                           | web          |
|        | GET|HEAD  | articles/{article}/edit | articles.edit    | App\Http\Controllers\ArticlesController@edit                           | web          |
|        | GET|HEAD  | contact                 | contact          | App\Http\Controllers\PagesController@contact                           | web          |
|        | GET|HEAD  | dashboard               | dashboard        | App\Http\Controllers\DashboardController@index                         | web,auth     |
|        | POST      | login                   |                  | App\Http\Controllers\Auth\LoginController@login                        | web,guest    |
|        | GET|HEAD  | login                   | login            | App\Http\Controllers\Auth\LoginController@showLoginForm                | web,guest    |
|        | POST      | logout                  | logout           | App\Http\Controllers\Auth\LoginController@logout                       | web          |
|        | POST      | password/email          | password.email   | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail  | web,guest    |
|        | POST      | password/reset          |                  | App\Http\Controllers\Auth\ResetPasswordController@reset                | web,guest    |
|        | GET|HEAD  | password/reset          | password.request | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web,guest    |
|        | GET|HEAD  | password/reset/{token}  | password.reset   | App\Http\Controllers\Auth\ResetPasswordController@showResetForm        | web,guest    |
|        | POST      | register                |                  | App\Http\Controllers\Auth\RegisterController@register                  | web,guest    |
|        | GET|HEAD  | register                | register         | App\Http\Controllers\Auth\RegisterController@showRegistrationForm      | web,guest    |
+--------+-----------+-------------------------+------------------+------------------------------------------------------------------------+--------------+

1番右の Middleware 項目に各ルートに適用されているミドルウェアが表示されています。
articles 関連のルートには web が適用されています。
認証関連のルートには web と auth もしくは guest が適用されています。

実は routes/web.php で定義したルートには全て、web ミドルウェアが適用されます。なのでミドルウェアの適用など支持していない articles 関連のルートにも web が適用されているのです。

ミドルウェアの適用はルートの設定(routes/web.php)以外に、コントローラでも行えます。認証関連のルートにはコントローラでミドルウェアが適用されています。
LoginController.php を見てみます。

<?php
// app/Http/Controllers/Auth/LoginController.php

...

class LoginController extends Controller
{
    ...

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

コンストラクタでこのコントローラに guest ミドルウェアの適用がされています。
ただし、logout メソッドは除くという支持もされています。


ログイン状態によるアクセス制御

前置きが長くなりましたが、ここまでである程度ミドルウェアが理解できたかと思います。ここから実装に戻ります。

シナリオ

やりたいことは…

  • 記事の登録と編集はログインしている時だけ行えるようにする。

つまり…

  • ログインしていない時に、記事の登録画面、編集画面にアクセスされたらログイン画面にリダイレクトする。
  • ログインしていない時には、記事の登録ボタン、編集ボタンを表示しない。

このような実装が必要になります。
今まで見てきた中から、auth ミドルウェアが使えそうです。

Controller

コントローラにミドルウェアの設定を追加します。

<?php
// app/Http/Controllers/ArticlesController.php

...

class ArticlesController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')
            ->except(['index', 'show']);
    }

    ...
}

artisan コマンドでルートを確認します。

php artisan route:list

+--------+-----------+-------------------------+------------------+------------------------------------------------------------------------+--------------+
| Domain | Method    | URI                     | Name             | Action                                                                 | Middleware   |
+--------+-----------+-------------------------+------------------+------------------------------------------------------------------------+--------------+
|        | POST      | articles                | articles.store   | App\Http\Controllers\ArticlesController@store                          | web,auth     |
|        | GET|HEAD  | articles                | articles.index   | App\Http\Controllers\ArticlesController@index                          | web          |
|        | GET|HEAD  | articles/create         | articles.create  | App\Http\Controllers\ArticlesController@create                         | web,auth     |
|        | DELETE    | articles/{article}      | articles.destroy | App\Http\Controllers\ArticlesController@destroy                        | web,auth     |
|        | PUT|PATCH | articles/{article}      | articles.update  | App\Http\Controllers\ArticlesController@update                         | web,auth     |
|        | GET|HEAD  | articles/{article}      | articles.show    | App\Http\Controllers\ArticlesController@show                           | web          |
|        | GET|HEAD  | articles/{article}/edit | articles.edit    | App\Http\Controllers\ArticlesController@edit                           | web,auth     |
+--------+-----------+-------------------------+------------------+------------------------------------------------------------------------+--------------+

※ 上記の表は article に絞って表示しています。

index と show 以外のルートには auth ミドルウェアが適用されました。

ログアウトした状態で以下の URL にアクセスしてみてください。ログイン画面にリダイレクトされるはずです。

※ 上記の {id} の部分は実在する ID に置き換えてください。

View

View の方もログイン状態に応じてボタンの表示制御を行うよう修正します。

{{-- resources/views/articles/index.blade.php --}}
@extends('layout')

@section('content')
        <h1>
            Articles
            @auth
            {{-- ログインしている時だけ表示 --}}
                <a href="{{ route('articles.create') }}" class="btn btn-primary float-right">新規作成</a>
            @endauth
        </h1>

        <hr/>

    @foreach($articles as $article)
        ...
    @endforeach
@endsection
{{-- resources/views/articles/show.blade.php --}}
@extends('layout')

@section('content')
    <h1>{{ $article->title }}</h1>

    <hr/>

    <article>
        <div class="body">{{ $article->body }}</div>
    </article>

    <br>

    <div>
        {{-- ログインしている時だけ表示 --}}
        @auth
            <a>id]) }}"
                class="btn btn-primary"
            >
                編集
            </a>

            {!! delete_form(['articles', $article->id]) !!}
        @endauth

        <a href="{{ route('articles.index') }}" class="btn btn-secondary float-right">
            一覧へ戻る
        </a>
    </div>
@endsection

ログアウトした状態で記事一覧画面、記事表示画面を表示してみてください。ボタンが表示されないはずです。


guest ミドルウェアのデバッグ

guest ミドルウェアが適用されているルートでは、ログインしている時にアクセスするとダッシュボードにリダイレクトされて欲しいのですが、現状ではどうでしょう?

ログインしている状態で http://localhost:8000/login にアクセスすると…

Sorry, the page you are looking for could not be found.

ページが見つからないと表示されました。

上の方で RedirectIfAuthenticated.php を見ている時に、バグを発見したと言ったのはこの事です。

RedirectIfAuthenticated.php を修正します。

<?php
// app/Http/Middleware/RedirectIfAuthenticated.php

...

class RedirectIfAuthenticated
{
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            // リダイレクト先を修正
            // return redirect('/home');
            return redirect(route('dashboard'));
        }

        return $next($request);
    }
}

もう一度、ログインしている状態で http://localhost:8000/login にアクセスすると…
今度はダッシュボードが表示されるはずです。


Middleware の新規作成

新規に Middleware を作成するには artisan コマンドを使用します。

php artisan make:middleware MyMiddleware

app/Http/Middleware/MyMiddleware.php が作成されます。

<?php
namespace App\Http\Middleware;

use Closure;

class MyMiddleware {

    public function handle($request, Closure $next)
    {
        // ここで何かやります。

        return $next($request);
    }

}

まとめ

以下のことが出来るようになりました。

  • ミドルウェアの新規作成
  • ミドルウェアのシステムへの登録
  • ミドルウェアを使ってのアクセス制御

コメントを残す