イントロダクション

Laravelは組み込み済みの認証サービスに加え、特定のリソースに対するユーザアクションを認可する簡単な手法も提供しています。認証と同様に、Laravelの認可のアプローチはシンプルで、主に2つの認可アクションの方法があります。ゲートとポリシーです。

ゲートとポリシーは、ルートとコントローラのようなものであると考えてください。ゲートはシンプルな、クロージャベースのアプローチを認可に対してとっています。一方のコントローラに似ているポリシーとは、特定のモデルやリソースに対するロジックをまとめたものです。最初にゲートを説明し、次にポリシーを確認しましょう。

アプリケーション構築時にゲートだけを使用するか、それともポリシーだけを使用するかを決める必要はありません。ほとんどのアプリケーションでゲートとポリシーは混在して使われますが、それで正しいのです。管理者のダッシュボードのように、モデルやリソースとは関連しないアクションに対し、ゲートは主に適用されます。それに対し、ポリシーは特定のモデルやリソースに対するアクションを認可したい場合に、使用する必要があります。

ゲート

ゲートの記述

ゲートは、特定のアクションを実行できる許可が、あるユーザにあるかを決めるクロージャのことです。通常は、App\Providers\AuthServiceProviderの中で、Gateファサードを使用し、定義します。ゲートは常に最初の引数にユーザインスタンスを受け取ります。関連するEloquentモデルのような、追加の引数をオプションとして受け取ることもできます。

/**
 * 全認証/認可サービスの登録
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
}

アクションの認可

ゲートを使用しアクションを認可するには、allowsdeniesメソッドを使ってください。両メソッドに現在認証中のユーザを渡す必要はないことに注目しましょう。Laravelが自動的にゲートクロージャにユーザを渡します。

if (Gate::allows('update-post', $post)) {
    // 現在のユーザはこのポストを更新できる
}

if (Gate::denies('update-post', $post)) {
    // 現在のユーザはこのポストを更新できない
}

特定のユーザがあるアクションを実行できる認可を持っているかを確認するには、GateファサードのforUserメソッドを使用します。

if (Gate::forUser($user)->allows('update-post', $post)) {
    // 渡されたユーザはこのポストを更新できる
}

if (Gate::forUser($user)->denies('update-post', $post)) {
    // ユーザはこのポストを更新できない
}

ポリシー作成

ポリシーの生成

ポリシーは特定のモデルやリソースに関する認可ロジックを系統立てるクラスです。たとえば、ブログアプリケーションの場合、Postモデルとそれに対応する、ポストを作成/更新するなどのユーザアクションを認可するPostPolicyを持つことになるでしょう。

make:policy Artisanコマンドを使用し、ポリシーを生成できます。生成したポリシーはapp/Policiesディレクトリに設置されます。このディレクトリがアプリケーションに存在していなくても、Laravelにより作成されます。

php artisan make:policy PostPolicy

make:policyコマンドは空のポリシークラスを生成します。基本的な「CRUD」ポリシーメソッドを生成するクラスへ含めたい場合は、make:policyコマンド実行時に--modelを指定してください。

php artisan make:policy PostPolicy --model=Post

Tip!! 全ポリシーはLaravelの サービスコンテナにより依存解決されるため、ポリシーのコンストラクタに必要な依存をタイプヒントすれば、自動的に注入されます。

ポリシーの登録

ポリシーができたら、登録する必要があります。インストールしたLaravelアプリケーションに含まれている、AuthServiceProviderにはEloquentモデルと対応するポリシーをマップするためのpoliciesプロパティを含んでいます。ポリシーの登録とは、指定したモデルに対するアクションの認可時に、どのポリシーを利用するかをLaravelへ指定することです。

<?php

namespace App\Providers;

use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションにマップ付されたポリシー
     *
     * @var array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
     * アプリケーションの全認証/認可サービスの登録
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //
    }
}

ポリシーの記述

ポリシーのメソッド

ポリシーが登録できたら、認可するアクションごとにメソッドを追加します。たとえば、指定したUserが指定Postインスタンスの更新をできるか決める、updataメソッドをPostPolicyに定義してみましょう。

updateメソッドはUserPostインスタンスを引数で受け取り、ユーザが指定Postの更新を行う認可を持っているかを示す、truefalseを返します。ですから、この例の場合、ユーザのidとポストのuser_idが一致するかを確認しましょう。

<?php

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    /**
     * ユーザにより指定されたポストが更新可能か決める
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return bool
     */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

必要に応じ、様々なアクションを認可するために、追加のメソッドをポリシーに定義してください。たとえば、色々なPostアクションを認可するために、viewdeleteメソッドを追加できます。ただし、ポリシーのメソッドには好きな名前を自由につけられることを覚えておいてください。

Tip!! ポリシーを--modelオプションを付け、Artisanコマンドにより生成した場合、viewcreateupdatedeleteアクションが含まれています。

モデルを持たないメソッド

ポリシーメソッドの中には、現在の認証ユーザのみを受け取り、認可するためのモデルを必要としないものもあります。この状況は、createアクションを認可する場合に、よく現れます。たとえば、ブログを作成する場合、どんなポストかにはかかわらず、そのユーザが作成可能かを認可したいでしょう。

createのように、モデルインスタンスを受け取らないポリシーメソッドを定義する場合は、モデルインスタンスを受け取る必要はありません。代わりに、その認証済みユーザが期待している人物かをメソッドで定義してください。

/**
 * 指定されたユーザがポストを作成できるかを決める
 *
 * @param  \App\User  $user
 * @return bool
 */
public function create(User $user)
{
    //
}

ポリシーフィルダー

特定のユーザには指定したポリシーの全アクションを許可したい場合があります。そのためには、beforeメソッドをポリシーへ定義してください。beforeメソッドはポリシーの他のメソッドの前に実行されるため、意図するポリシーメソッドが実際に呼び出される前に、アクションを許可する機会を提供します。この機能は主に、アプリケーションの管理者に全てのアクションを実行する権限を与えるために使用されます。

public function before($user, $ability)
{
    if ($user->isSuperAdmin()) {
        return true;
    }
}

ユーザに対して全認可を禁止したい場合は、beforeメソッドからfalseを返します。nullを返した場合、その認可の可否はポリシーメソッドにより決まります。

ポリシーを使ったアクションの認可

Userモデルによる確認

Laravelアプリケーションに含まれるUserモデルは、アクションを認可するための便利な2つのメソッドを持っています。cancantです。canメソッドは認可したいアクションと関連するモデルを引数に取ります。例として、ユーザが指定したPostを更新を認可するかを決めてみましょう。

if ($user->can('update', $post)) {
    //
}

指定するモデルのポリシーが登録済みであれば適切なポリシーのcanメソッドが自動的に呼びだされ、論理型の結果が返されます。そのモデルに対するポリシーが登録されていない場合、canメソッドは指定したアクション名に合致する、ゲートベースのクロージャーを呼びだそうとします。

モデルを必要としないアクション

createのようなアクションは、モデルインスタンスを必要としないことを思い出してください。そうした場合は、canメソッドにはクラス名を渡してください。クラス名はアクションを認可するときにどのポリシーを使用すべきかを決めるために使われます。

use App\Post;

if ($user->can('create', Post::class)) {
    // 関連するポリシーの"create"メソッドが実行される
}

ミドルウェアによる認可

送信されたリクエストがルートやコントローラへ到達する前に、アクションを認可できるミドルウェアをLaravelは持っています。デフォルトでApp\Http\Kernelクラスの中でcanキーにIlluminate\Auth\Middleware\Authorizeミドルウェアが割り付けられています。あるユーザがブログポストを認可するために、canミドルウェアを使う例をご覧ください。

use App\Post;

Route::put('/post/{post}', function (Post $post) {
    // 現在のユーザはこのポストを更新できる
})->middleware('can:update,post');

この例では、canミドルウェアへ2つの引数を渡しています。最初の引数は認可したいアクションの名前です。2つ目はポリシーメソッドに渡したいルートパラメータです。この場合、暗黙のモデル結合を使用しているため、Postモデルがポリシーメソッドへ渡されます。ユーザに指定したアクションを実行する認可がない場合、ミドルウェアにより403ステータスコードのHTTPレスポンスが生成されます。

モデルを必要としないアクション

この場合も、createのようなアクションではモデルインスタンスを必要としていません。こうした場合、ミドルウェアへクラス名を渡してください。クラス名はアクションを認可するときに、どのポリシーを使用するかに使われます。

Route::post('/post', function () {
    // 現在のユーザはポストを更新できる
})->middleware('can:create,App\Post');

コントローラヘルパによる認可

Userモデルが提供している便利なメソッドに付け加え、App\Http\Controllers\Controllerベースクラスを拡張しているコントローラに対し、Laravelはauthorizeメソッドを提供しています。canメソッドと同様に、このメソッドは認可対象のアクション名と関連するモデルを引数に取ります。アクションが認可されない場合、authorizeメソッドはIlluminate\Auth\Access\AuthorizationException例外を投げ、これはデフォルトでLaravelの例外ハンドラにより、403ステータスコードのHTTPレスポンスへ変換されます。

<?php

namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    /**
     * 指定したポストの更新
     *
     * @param  Request  $request
     * @param  Post  $post
     * @return Response
     */
    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);

        // 現在のユーザはブログポストの更新が可能
    }
}

モデルを必要としないアクション

既に説明してきたように、createのように、モデルインスタンスを必要としないアクションがあります。この場合、クラス名をauthorizeメソッドへ渡してください。クラス名はアクションの認可時に、どのポリシーを使用するのかを決めるために使われます。

/**
 * 新しいブログポストの生成
 *
 * @param  Request  $request
 * @return Response
 */
public function create(Request $request)
{
    $this->authorize('create', Post::class);

    // 現在のユーザはブログポストを生成できる
}

Bladeテンプレートによる認可

Bladeテンプレートを書くとき、指定したアクションを実行できる認可があるユーザの場合のみ、ページの一部分を表示したい場合があります。たとえば、実際にポストを更新できるユーザの場合のみ、ブログポストの更新フォームを表示したい場合です。この場合、@can@cannot系ディレクティブを使います。

@can('update', $post)
    <!-- 現在のユーザはポストを更新できる -->
@elsecan('create', $post)
    <!-- 現在のユーザはポストを作成できる -->
@endcan

@cannot('update', $post)
    <!-- 現在のユーザはポストを更新できない -->
@elsecannot('create', $post)
    <!-- 現在のユーザはポストを作成できない -->
@endcannot

これらのディレクティブは@if@unless文を使う記述に対する、便利な短縮形です。上記の@can@cannot文に対応するコードは以下のようになります。

@if (Auth::user()->can('update', $post))
    <!-- 現在のユーザはポストを更新できる -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- 現在のユーザはポストを更新できない -->
@endunless

モデルを必要としないアクション

他の認可メソッドと同様に、アクションがモデルインスタンスを必要としない場合、@can@cannotディレクティブへ、クラス名を渡すことができます。

@can('create', Post::class)
    <!-- 現在のユーザはポストを更新できる -->
@endcan

@cannot('create', Post::class)
    <!-- 現在のユーザはポストを更新できない -->
@endcannot