Laravel 6.x 認可

イントロダクション

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

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

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

ゲート

ゲートの記述

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

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

    Gate::define('edit-settings', function ($user) {
        return $user->isAdmin;
    });

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

コントローラのように、ゲートはClass@method形式のコールバック文字列を使い定義することも可能です。

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

    Gate::define('update-post', 'App\Policies\PostPolicy@update');
}

アクションの認可

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

if (Gate::allows('edit-settings')) {
    // 現在のユーザーは設定を変更できる
}

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)) {
    // ユーザーはこのポストを更新できない
}

anynoneメソッドを使い、複数のアクションの許可を一度に指定できます。

if (Gate::any(['update-post', 'delete-post'], $post)) {
    // ユーザーはポストの更新と削除ができる
}

if (Gate::none(['update-post', 'delete-post'], $post)) {
    // ユーザーはポストの更新と削除ができない
}

認証か例外を投げる

認証を試み、そのユーザーが指定したアクションの実行が許されていない場合は、自動的にIlluminate\Auth\Access\AuthorizationException例外を投げる方法を取るには、Gate::authorizeメソッドを使用します。AuthorizationExceptionは自動的に403 HTTPレスポンスへ変換されます。

Gate::authorize('update-post', $post);

// アクションが認証された…

追加コンテキストの指定

認可アビリティのゲートメソッド(allowsdeniescheckanynoneauthorizecancannot)と認可Bladeディレクティブ@can@cannot@canany)では2番めの引数として配列を受け取れます。これらの配列要素はゲートにパラメータとして渡されたもので、認可の可否を決定する際に、追加コンテキストとして利用できます。

Gate::define('create-post', function ($user, $category, $extraFlag) {
    return $category->group > 3 && $extraFlag === true;
});

if (Gate::check('create-post', [$category, $extraFlag])) {
    // このユーザーはポストを新規作成できる
}

ゲートレスポンス

ここまでは、単純な論理値を返すゲートのみを見てきました。しかしながら、エラーメッセージを含んだ、より詳細なレスポンスを返したい場合もあるでしょう。そのためには、ゲートからIlluminate\Auth\Access\Responseを返します。

use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function ($user) {
    return $user->isAdmin
                ? Response::allow()
                : Response::deny('You must be a super administrator.');
});

認可レスポンスをゲートから返す場合、Gate::allowsメソッドはシンプルに論理値を返します。ゲートから返される完全な認可レスポンスを取得したい場合は、Gate::inspectを使います。

$response = Gate::inspect('edit-settings', $post);

if ($response->allowed()) {
    // アクションは認可された…
} else {
    echo $response->message();
}

もちろん、アクションを許可できない場合、AuthorizationExceptionを投げるためにGate::authorizeメソッドを使うと、認可レスポンスが提供するエラーメッセージは、HTTPレスポンスへ伝わります。

Gate::authorize('edit-settings', $post);

// アクションは認可された…

ゲートチェックのインターセプト

特定のユーザーに全アビリティーへ許可を与えたい場合もあります。beforeメソッドは、他の全ての認可チェック前に実行される、コールバックを定義します。

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

beforeコールバックでNULL以外の結果を返すと、チェックの結果とみなされます。

afterメソッドで、すべての認可チャックの後で実行されるコールバックを定義することも可能です。

Gate::after(function ($user, $ability, $result, $arguments) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

beforeチェックと同様に、afterコールバックからNULLでない結果を返せば、その結果はチェック結果として取り扱われます。

ポリシー作成

ポリシーの生成

ポリシーは特定のモデルやリソースに関する認可ロジックを系統立てるクラスです。たとえば、ブログアプリケーションの場合、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\Policies\PostPolicy;
use App\Post;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

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

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

        //
    }
}

ポリシーの自動検出

モデルポリシーをいちいち登録する代わりに、モデルとポリシーの標準命名規則に従っているポリシーを自動的にLaravelは見つけます。具体的にはモデルが含まれているディレクトリの下に存在する、Policiesディレクトリ中のポリシーです。たとえば、モデルがappディレクトリ下にあれば、ポリシーはapp/Policiesディレクトリへ置く必要があります。さらに、ポリシーの名前は対応するモデルの名前へ、Policyサフィックスを付けたものにする必要があります。ですから、Userモデルに対応させるには、UserPolicyクラスと命名します。

独自のポリシー発見ロジックを利用したい場合、Gate::guessPolicyNamesUsingメソッドでカスタムコールバックを登録します。通常このメソッドは、AuthServiceProviderbootメソッドから呼び出すべきでしょう。

use Illuminate\Support\Facades\Gate;

Gate::guessPolicyNamesUsing(function ($modelClass) {
    // ポリシークラス名を返す
});

Note: AuthServiceProvider中で明確にマップされたポリシーは、自動検出される可能性のあるポリシーよりも優先的に扱われます。

ポリシーの記述

ポリシーのメソッド

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

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

<?php

namespace App\Policies;

use App\Post;
use App\User;

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コマンドにより生成した場合、viewcreateupdatedeleterestoreforceDeleteアクションが含まれています。

ポリシーレスポンス

これまで、シンプルな論理値を返すポリシーメソッドだけを見てきました。しかし、エラーメッセージを含むより詳細なレスポンスを返したいこともあります。それには、ポリシーメソッドからIlluminate\Auth\Access\Responseを返してください。

use Illuminate\Auth\Access\Response;

/**
 * このユーザーにより、指定ポストが更新できるか判定
 *
 * @param  \App\User  $user
 * @param  \App\Post  $post
 * @return bool
 */
public function update(User $user, Post $post)
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::deny('You do not own this post.');
}

ポリシーから認可レスポンスを返す場合、Gate::allowsメソッドはシンプルな論理値を返します。しかし、ゲートから完全な認可レスポンスを取得するには、Gate::inspectメソッドを使用します。

$response = Gate::inspect('update', $post);

if ($response->allowed()) {
    // アクションは認可された…
} else {
    echo $response->message();
}

もちろん、アクションを許可できない場合、AuthorizationExceptionを投げるためにGate::authorizeメソッドを使うと、認可レスポンスが提供するエラーメッセージは、HTTPレスポンスへ伝わります。

Gate::authorize('update', $post);

// アクションは認可された…

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

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

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

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

ゲストユーザー

HTTPリクエストが認証済みユーザーにより開始されたものでなければ、全てのゲートとポリシーは自動的にデフォルトとしてfalseを返します。しかし、「オプショナル」なタイプヒントを宣言するか、ユーザーの引数宣言にnullデフォルトバリューを指定することで、ゲートやポリシーに対する認可チェックをパスさせることができます。

<?php

namespace App\Policies;

use App\Post;
use App\User;

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;
    }
}

ポリシーフィルター

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

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

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

Note: クラスがチェックするアビリティと一致する名前のメソッドを含んでいない場合、ポリシークラスのbeforeメソッドは呼び出されません。

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

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\Http\Controllers\Controller;
use App\Post;
use Illuminate\Http\Request;

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

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

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

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

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

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

リソースコントローラの認可

リソースコントローラを活用している場合、コントローラのコンストラクタの中で、authorizeResourceメソッドを使用できます。このメソッドはリソースコントローラのメソッドへ適切なcanミドルウェア定義を付加します。

authorizeResourceメソッドは最初の引数にモデルのクラス名を受け取ります。モデルのIDを含むルート/リクエストパラメータ名を第2引数に受け取ります。

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }
}

次のコントローラメソッドが、対応するポリシーメソッドにマップされます。

コントローラメソッド ポリシーメソッド
index viewAny
show view
create create
store create
edit update
update update
destroy delete

Tip!! 指定するモデルのポリシークラスを手っ取り早く生成するには、--modelオプションを付けmake:policyコマンドを実行します。php artisan make:policy --model=Post

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

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

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

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

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

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

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

指定するリスト中の認可アビリティをユーザーが持っているかを判定することもできます。@cananyディレクティブを使用します。

@canany(['update', 'view', 'delete'], $post)
    // 現在のユーザーはポストとの更新、閲覧、削除ができる
@elsecanany(['create'], \App\Post::class)
    // 現在のユーザーはポストを作成できる
@endcanany

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

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

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

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

追加コンテキストの指定

認可アクションにポリシーを使う場合、数多くの認可関数やヘルパで第2引数に配列を渡せます。配列の第1要素はどのポリシーを呼び出すべきか決定するために使われます。残りの配列要素は、ポリシーメソッドへのパラメータとして渡されたもので、認可の可否を決定する際に追加のコンテキストとして利用できます。例として、次のような追加の$categoryパラメータを持つ、PostPolicyメソッド定義を考えてみましょう。

/**
 * このユーザーが指定されたポストを更新できるか判断する
 *
 * @param  \App\User  $user
 * @param  \App\Post  $post
 * @param  int  $category
 * @return bool
 */
public function update(User $user, Post $post, int $category)
{
    return $user->id === $post->user_id &&
           $category > 3;
}

認証済みのユーザーが指定ポストを更新できるかの判断を試みる時、次のようにこのポリシーメソッドを呼び出せます。

/**
 * 指定ポストの更新
 *
 * @param  Request  $request
 * @param  Post  $post
 * @return Response
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function update(Request $request, Post $post)
{
    $this->authorize('update', [$post, $request->input('category')]);

    // 現在のユーザーは、このブログボスとを更新できる…
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる