Laravel 5.1 認可

イントロダクション

初めから用意されている認証サービスに付け加え、Laravelは認可ロジックを取りまとめ、リソースへのアクセスをコントロールする簡単な手段を提供します。認可ロジックを組織立てるのに役立つメソッドやヘルパはたくさん用意されていますので、このドキュメントで紹介します。

注意: 認可機能はLaravel 5.1.11で追加されました。この機能をアプリケーションに統合する前に、アップグレードガイドを参照してください。

アビリティの定義

あるユーザーが指定されたアクションを実行しても良いかをシンプルに判定するには、Illuminate\Auth\Access\Gateクラスを使い「アビリティ(ability)」を定義してください。Laravelに初めから用意されているAuthServiceProviderは、アプリケーションの全アビリティを定義するために便利な場所です。例として現在のUserPostモデルを受け取る、update-postアビリティを定義してみましょう。このアビリティの中で、ユーザーのidがポストのuser_idと一致するかを判定します。

<?php

namespace App\Providers;

use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 全認証/認可サービスの登録
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        $this->registerPolicies($gate);

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

渡ってきた$userNULLではないかの、チェックを行っていないことに注目です。Gateはユーザーが認証されていないか、forUserメソッドを使いユーザーが指定されていない場合は、全アビリティから自動的にfalseを返します。

クラスベースのアビリティ

認可のコールバックとして「クロージャー」を登録する方法に加え、クラス名とメソッドの文字列を引数に渡してもクラスメソッドを登録できます。必要であれば、クラスはサービスコンテナを利用し、依存解決されます。

$gate->define('update-post', 'Class@method');

認可チェックの停止

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

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

beforeコールバックがnullではない結果を返した場合、それをチェック結果として取り扱います。

afterメソッドを使い、全部の認可のチェックを行った後に実行するコールバックを定義できます。しかしafterのコールバックの中から認可チェックの結果を変更できません。

$gate->after(function ($user, $ability, $result, $arguments) {
    //
});

アビリティーの確認

Gateファサードによる確認

アビリティーを定義したら、さまざまな方法で「確認」できます。最初はGateファサードcheckallowdeniesメソッドを使う方法です。これらのメソッドはアビリティ名とコールバックに渡した引数を受け取ります。Gateが自動的に現在のユーザーをコールバック渡す引数の先頭に付け加えるため、こうしたメソッドに現在のユーザーを渡す必要はありません。ですから既に定義したupdate-postアビリティーを確認する場合、deniesメソッドにはPostインスタンスを渡す必要があるだけです。

<?php

namespace App\Http\Controllers;

use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    /**
     * 指定したポストの更新
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $post = Post::findOrFail($id);

        if (Gate::denies('update-post', $post)) {
            abort(403);
        }

        // ポストの更新処理…
    }
}

当然のことながら、allowsメソッドはdeniesメソッドをただひっくり返した働きをし、アクションが認可されていればtrueを返します。checkメソッドはallowsメソッドのエイリアスです。

特定のユーザーに対するアビリティーを確認

現在認証されているユーザーではなく、別のユーザーが指定したアビリティーを持っているかをGateファサードで確認したい場合は、forUserメソッドを使います。

if (Gate::forUser($user)->allows('update-post', $post)) {
    //
}

複数の引数の指定

もちろんアビリティのコールバックには複数の引数が指定できます。

Gate::define('delete-comment', function ($user, $post, $comment) {
    //
});

アビリティーで複数の引数が必要であれば、Gateファサードのメソッドに引数を配列として渡してください。

if (Gate::allows('delete-comment', [$post, $comment])) {
    //
}

Userモデルによる確認

他のやり方として、アビリティーをUserモデルインスタンスでも確認できます。LaravelのApp\Userモデルは、cancannotメソッドを提供しているAuthorizableトレイトをuseしています。これらのメソッドは、Gateファサードのallowdeniesメソッドと使い方が似ています。では、前の例と同様に、コードを次のように変更してみましょう。

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * 指定したポストの更新
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        $post = Post::findOrFail($id);

        if ($request->user()->cannot('update-post', $post)) {
            abort(403);
        }

        // ポストの更新処理…
    }
}

もちろん、canメソッドは単にcannotメソッドの真反対の働きです。

if ($request->user()->can('update-post', $post)) {
    // Update Post...
}

Bladeテンプレートでの確認

現在の認証ユーザーが指定したアビリティーを持っているかを簡単に確認するのに便利なように、Laravelは@can Bladeディレクティブを用意しています。例を見てください。

<a href="/post/{{ $post->id }}">ポスト表示</a>

@can('update-post', $post)
    <a href="/post/{{ $post->id }}/edit">ポスト編集</a>
@endcan

@canディレクティブは、@elseディレクティブと組み合わせても使えます。

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

フォームリクエストでの確認

Gateで定義したアビリティーをフォームリクエストauthorizeメソッドで活用する選択を取ることもできます。

/**
 * ユーザーがこのリクエストを作成できる認可があるかの判定
 *
 * @return bool
 */
public function authorize()
{
    $postId = $this->route('post');

    return Gate::allows('update', Post::findOrFail($postId));
}

ポリシー

ポリシーの作成

全認可ロジックをAuthServiceProviderの中で定義するのは、大きなアプリケーションでは厄介ですから、Laravelでは認可ロジックを「ポリシー」クラスに分割できます。ポリシーは普通のPHPクラスで、認可するリソースに基づいてロジックをグループ分けするものです。

始めにPostモデルの認可を管理するポリシーを生成しましょう。make:policy Artisanコマンドでポリシーを生成します。生成したポリシーはapp/Policiesディレクトリーへ設置されます。

php artisan make:policy PostPolicy

ポリシーの登録

ポリシーができたら、Gateクラスで登録する必要があります。AuthServiceProviderには様々なエンティティを管理するポリシーとマップするためのpoliciesプロパティを含んでいます。では、PostモデルのポリシーであるPostPolicyクラスを指定しましょう。

<?php

namespace App\Providers;

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

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

    /**
     * アプリケーションの全認証/認可サービスを登録
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        $this->registerPolicies($gate);
    }
}

ポリシーの記述

ポリシーを生成し登録したら、認可する各アビリティーのためのメソッドを追加できます。指定したUserPostを「更新(update)」できるかを判定する、updateメソッドをPostPolicyに例として定義してみます。

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

認可する様々なアビリティーを必要なだけポリシーにメソッドとして追加定義してください。たとえばshowdestroyaddCommentメソッドなど、多様なPostアクションの認可が定義できるでしょう。

注目: 全ポリシーはLaravelのサービスコンテナにより依存解決されます。つまりポリシーのコンストラクターに必要な依存をタイプヒントで指定すれば、自動的に挿入されます。

全チェックの停止

場合により特定のユーザーにポリシーの全アビリティーを許可したい場合があります。この状況ではポリシーにbeforeメソッドを定義してください。このメソッドはポリシーにある他の全認可チェックより先に実行されます。

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

beforeメソッドがnullでない値を返した場合、それをチェックの結果として取り扱います。

ポリシーの確認

ポリシーメソッドはクロージャーベースの認可コールバックと全く同じ方法で呼び出します。GateファサードやUserモデル、@can Bladeディレクティブ、policyヘルパを使用できます。

Gateファサードによる確認

Gateはメソッドに渡された引数のクラスを調べ、どのポリシーを使用するか自動的に決定します。ですからPostインスタンスをdeniesメソッドに渡せば、Gateは認可するアクションに合ったPostPolicyを使用します。

<?php

namespace App\Http\Controllers;

use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    /**
     * 指定ポストの更新
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $post = Post::findOrFail($id);

        if (Gate::denies('update', $post)) {
            abort(403);
        }

        // ポストの更新処理…
    }
}

Userモデルによる確認

Userモデルのcancannotメソッドも引数に指定された引数に対する使用可能なポリシーがあれば、自動的に使用します。これらのメソッドはアプリケーションが取得したUserインスタンス全てに対するアクションを認可するために、便利な手法を提供しています。

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

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

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

同様に、@can Bladeディレクティブも与えられた引数に対して使用可能なポリシーを活用します。

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

policyヘルパによる確認

グローバルなpolicyヘルパ関数は、指定されたクラスインスタンスに対する「ポリシー」クラスを取得するために使用します。例えば、Postインスタンスをpolicyヘルパに渡し、対応するPostPolicyクラスのインスタンスを取得できます。

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

コントローラーの認可

Laravelに含まれるデフォルトのApp\Http\Controllers\Controller基本クラスでは、AuthorizesRequestsトレイトがuseされています。このトレイトはauthorizeメソッドを提供しており、簡単に指定アクションを認可するために使用でき、アクションが認可されていなければHttpExceptionを投げます。

authorizeメソッドはGate::allows$user->can()などの認可メソッドと同じ使用方法です。では、Postを更新するリクエストを手っ取り早く認可するために、authorizeメソッドを用いてみましょう。

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * 指定ポストの更新
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $post = Post::findOrFail($id);

        $this->authorize('update', $post);

        // ポストの更新処理…
    }
}

アクションが認可されていれば、コントローラーは通常通り続けて実行されます。しかしauthorizeメソッドがアクションを非認可と判断すると、HttpExceptionが自動的に投げられ、 403 Not AuthorizedステータスコードのHTTPレスポンスが生成されます。ご覧の通りにauthorizeメソッドはコード1行でアクションを認可し、例外を投げるための便利で手っ取り早い方法です。

AuthorizesRequestsトレイトは、現在認証中ではない別のユーザーのアクションを認可する、authorizeForUserメソッドも提供しています。

$this->authorizeForUser($user, 'update', $post);

ポリシーメソッドの自動決定

ポリシーのメソッドはコントローラーのメソッドと頻繁に対応します。たとえば前記のupdateメソッドのように、updateと言う名前がコントローラーメソッドとポリシーメソッドで共通です。

そのため、Laravelはauthorizeメソッドの引数にインスタンスだけを渡すことも許しています。認可するアビリティは呼び出しているメソッドの名前を元に自動的に決められます。この例では、コントローラーのupdateメソッドからauthorizeが呼びだされていますから、PostPolicy上でもupdateメソッドが呼びだされます。

/**
 * 指定ポストの更新
 *
 * @param  int  $id
 * @return Response
 */
public function update($id)
{
    $post = Post::findOrFail($id);

    $this->authorize($post);

    // ポストの更新…
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュを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)へ移動

その他

?

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