Skip to content

Latest commit

 

History

History
413 lines (300 loc) · 17.7 KB

authorization.md

File metadata and controls

413 lines (300 loc) · 17.7 KB

Authorization

Introduction

In addition to providing authentication services out of the box, Laravel also provides a simple way to authorize user actions against a given resource. Like authentication, Laravel's approach to authorization is simple, and there are two primary ways of authorizing actions: gates and policies.

Think of gates and policies like routes and controllers. Gates provide a simple, Closure based approach to authorization while policies, like controllers, group their logic around a particular model or resource. We'll explore gates first and then examine policies.

You do not need to choose between exclusively using gates or exclusively using policies when building an application. Most applications will most likely contain a mixture of gates and policies, and that is perfectly fine! Gates are most applicable to actions which are not related to any model or resource, such as viewing an administrator dashboard. In contrast, policies should be used when you wish to authorize an action for a particular model or resource.

Gates

Writing Gates

Gates are Closures that determine if a user is authorized to perform a given action and are typically defined in the App\Providers\AuthServiceProvider class using the Gate facade. Gates always receive a user instance as their first argument, and may optionally receive additional arguments such as a relevant Eloquent model:

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

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

Gates may also be defined using a Class@method style callback string, like controllers:

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

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

Resource Gates

You may also define multiple Gate abilities at once using the resource method:

Gate::resource('posts', 'App\Policies\PostPolicy');

This is identical to manually defining the following Gate definitions:

Gate::define('posts.view', 'App\Policies\PostPolicy@view');
Gate::define('posts.create', 'App\Policies\PostPolicy@create');
Gate::define('posts.update', 'App\Policies\PostPolicy@update');
Gate::define('posts.delete', 'App\Policies\PostPolicy@delete');

By default, the view, create, update, and delete abilities will be defined. You may override the default abilities by passing an array as a third argument to the resource method. The keys of the array define the names of the abilities while the values define the method names. For example, the following code will only create two new Gate definitions - posts.image and posts.photo:

Gate::resource('posts', 'PostPolicy', [
    'image' => 'updateImage',
    'photo' => 'updatePhoto',
]);

Authorizing Actions

To authorize an action using gates, you should use the allows or denies methods. Note that you are not required to pass the currently authenticated user to these methods. Laravel will automatically take care of passing the user into the gate Closure:

if (Gate::allows('update-post', $post)) {
    // The current user can update the post...
}

if (Gate::denies('update-post', $post)) {
    // The current user can't update the post...
}

If you would like to determine if a particular user is authorized to perform an action, you may use the forUser method on the Gate facade:

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

if (Gate::forUser($user)->denies('update-post', $post)) {
    // The user can't update the post...
}

Intercepting Gate Checks

Sometimes, you may wish to grant all abilities to a specific user. You may use the before method to define a callback that is run before all other authorization checks:

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

If the before callback returns a non-null result that result will be considered the result of the check.

You may use the after method to define a callback to be executed after every authorization check. However, you may not modify the result of the authorization check from an after callback:

Gate::after(function ($user, $ability, $result, $arguments) {
    //
});

Creating Policies

Generating Policies

Policies are classes that organize authorization logic around a particular model or resource. For example, if your application is a blog, you may have a Post model and a corresponding PostPolicy to authorize user actions such as creating or updating posts.

You may generate a policy using the make:policy artisan command. The generated policy will be placed in the app/Policies directory. If this directory does not exist in your application, Laravel will create it for you:

php artisan make:policy PostPolicy

The make:policy command will generate an empty policy class. If you would like to generate a class with the basic "CRUD" policy methods already included in the class, you may specify a --model when executing the command:

php artisan make:policy PostPolicy --model=Post

{tip} All policies are resolved via the Laravel service container, allowing you to type-hint any needed dependencies in the policy's constructor to have them automatically injected.

Registering Policies

Once the policy exists, it needs to be registered. The AuthServiceProvider included with fresh Laravel applications contains a policies property which maps your Eloquent models to their corresponding policies. Registering a policy will instruct Laravel which policy to utilize when authorizing actions against a given model:

<?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
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
     * Register any application authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //
    }
}

Writing Policies

Policy Methods

Once the policy has been registered, you may add methods for each action it authorizes. For example, let's define an update method on our PostPolicy which determines if a given User can update a given Post instance.

The update method will receive a User and a Post instance as its arguments, and should return true or false indicating whether the user is authorized to update the given Post. So, for this example, let's verify that the user's id matches the user_id on the post:

<?php

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return bool
     */
    public function update(User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

You may continue to define additional methods on the policy as needed for the various actions it authorizes. For example, you might define view or delete methods to authorize various Post actions, but remember you are free to give your policy methods any name you like.

{tip} If you used the --model option when generating your policy via the Artisan console, it will already contain methods for the view, create, update, delete, restore, and forceDelete actions.

Methods Without Models

Some policy methods only receive the currently authenticated user and not an instance of the model they authorize. This situation is most common when authorizing create actions. For example, if you are creating a blog, you may wish to check if a user is authorized to create any posts at all.

When defining policy methods that will not receive a model instance, such as a create method, it will not receive a model instance. Instead, you should define the method as only expecting the authenticated user:

/**
 * Determine if the given user can create posts.
 *
 * @param  \App\User  $user
 * @return bool
 */
public function create(User $user)
{
    //
}

Guest Users

By default, all gates and policies automatically return false if the incoming HTTP request was not initiated by an authenticated user. However, you may allow these authorization checks to pass through to your gates and policies by declaring an "optional" type-hint or supplying a null default value for the user argument definition:

<?php

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     *
     * @param  \App\User  $user
     * @param  \App\Post  $post
     * @return bool
     */
    public function update(?User $user, Post $post)
    {
        return $user->id === $post->user_id;
    }
}

Policy Filters

For certain users, you may wish to authorize all actions within a given policy. To accomplish this, define a before method on the policy. The before method will be executed before any other methods on the policy, giving you an opportunity to authorize the action before the intended policy method is actually called. This feature is most commonly used for authorizing application administrators to perform any action:

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

If you would like to deny all authorizations for a user you should return false from the before method. If null is returned, the authorization will fall through to the policy method.

{note} The before method of a policy class will not be called if the class doesn't contain a method with a name matching the name of the ability being checked.

Authorizing Actions Using Policies

Via The User Model

The User model that is included with your Laravel application includes two helpful methods for authorizing actions: can and cant. The can method receives the action you wish to authorize and the relevant model. For example, let's determine if a user is authorized to update a given Post model:

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

If a policy is registered for the given model, the can method will automatically call the appropriate policy and return the boolean result. If no policy is registered for the model, the can method will attempt to call the Closure based Gate matching the given action name.

Actions That Don't Require Models

Remember, some actions like create may not require a model instance. In these situations, you may pass a class name to the can method. The class name will be used to determine which policy to use when authorizing the action:

use App\Post;

if ($user->can('create', Post::class)) {
    // Executes the "create" method on the relevant policy...
}

Via Middleware

Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the Illuminate\Auth\Middleware\Authorize middleware is assigned the can key in your App\Http\Kernel class. Let's explore an example of using the can middleware to authorize that a user can update a blog post:

use App\Post;

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->middleware('can:update,post');

In this example, we're passing the can middleware two arguments. The first is the name of the action we wish to authorize and the second is the route parameter we wish to pass to the policy method. In this case, since we are using implicit model binding, a Post model will be passed to the policy method. If the user is not authorized to perform the given action, a HTTP response with a 403 status code will be generated by the middleware.

Actions That Don't Require Models

Again, some actions like create may not require a model instance. In these situations, you may pass a class name to the middleware. The class name will be used to determine which policy to use when authorizing the action:

Route::post('/post', function () {
    // The current user may create posts...
})->middleware('can:create,App\Post');

Via Controller Helpers

In addition to helpful methods provided to the User model, Laravel provides a helpful authorize method to any of your controllers which extend the App\Http\Controllers\Controller base class. Like the can method, this method accepts the name of the action you wish to authorize and the relevant model. If the action is not authorized, the authorize method will throw an Illuminate\Auth\Access\AuthorizationException, which the default Laravel exception handler will convert to an HTTP response with a 403 status code:

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * Update the given blog post.
     *
     * @param  Request  $request
     * @param  Post  $post
     * @return Response
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);

        // The current user can update the blog post...
    }
}

Actions That Don't Require Models

As previously discussed, some actions like create may not require a model instance. In these situations, you may pass a class name to the authorize method. The class name will be used to determine which policy to use when authorizing the action:

/**
 * Create a new blog post.
 *
 * @param  Request  $request
 * @return Response
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function create(Request $request)
{
    $this->authorize('create', Post::class);

    // The current user can create blog posts...
}

Via Blade Templates

When writing Blade templates, you may wish to display a portion of the page only if the user is authorized to perform a given action. For example, you may wish to show an update form for a blog post only if the user can actually update the post. In this situation, you may use the @can and @cannot family of directives:

@can('update', $post)
    <!-- The Current User Can Update The Post -->
@elsecan('create', App\Post::class)
    <!-- The Current User Can Create New Post -->
@endcan

@cannot('update', $post)
    <!-- The Current User Can't Update The Post -->
@elsecannot('create', App\Post::class)
    <!-- The Current User Can't Create New Post -->
@endcannot

These directives are convenient shortcuts for writing @if and @unless statements. The @can and @cannot statements above respectively translate to the following statements:

@if (Auth::user()->can('update', $post))
    <!-- The Current User Can Update The Post -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- The Current User Can't Update The Post -->
@endunless

Actions That Don't Require Models

Like most of the other authorization methods, you may pass a class name to the @can and @cannot directives if the action does not require a model instance:

@can('create', App\Post::class)
    <!-- The Current User Can Create Posts -->
@endcan

@cannot('create', App\Post::class)
    <!-- The Current User Can't Create Posts -->
@endcannot