Skip to content

Latest commit

 

History

History
430 lines (310 loc) · 15.2 KB

authorization.md

File metadata and controls

430 lines (310 loc) · 15.2 KB

Authorization

Introduction

In addition to providing authentication services out of the box, Laravel also provides a simple way to organize authorization logic and control access to resources. There are a variety of methods and helpers to assist you in organizing your authorization logic, and we'll cover each of them in this document.

Defining Abilities

The simplest way to determine if a user may perform a given action is to define an "ability" using the Illuminate\Auth\Access\Gate class. The AuthServiceProvider which ships with Laravel serves as a convenient location to define all of the abilities for your application. For example, let's define an update-post ability which receives the current User and a Post model. Within our ability, we will determine if the user's id matches the post's 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
{
    /**
     * Register any application authentication / authorization services.
     *
     * @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;
        });
    }
}

Note that we did not check if the given $user is not NULL. The Gate will automatically return false for all abilities when there is not an authenticated user or a specific user has not been specified using the forUser method.

Class Based Abilities

In addition to registering Closures as authorization callbacks, you may register class methods by passing a string containing the class name and the method. When needed, the class will be resolved via the service container:

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

Intercepting Authorization Checks

Sometimes, you may wish to grant all abilities to a specific user. For this situation, 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) {
    //
});

Checking Abilities

Via The Gate Facade

Once an ability has been defined, we may "check" it in a variety of ways. First, we may use the check, allows, or denies methods on the Gate facade. All of these methods receive the name of the ability and the arguments that should be passed to the ability's callback. You do not need to pass the current user to these methods, since the Gate will automatically prepend the current user to the arguments passed to the callback. So, when checking the update-post ability we defined earlier, we only need to pass a Post instance to the denies method:

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * Update the given post.
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
    	$post = Post::findOrFail($id);

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

    	// Update Post...
    }
}

Of course, the allows method is simply the inverse of the denies method, and returns true if the action is authorized. The check method is an alias of the allows method.

Checking Abilities For Specific Users

If you would like to use the Gate facade to check if a user other than the currently authenticated user has a given ability, you may use the forUser method:

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

Passing Multiple Arguments

Of course, ability callbacks may receive multiple arguments:

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

If your ability needs multiple arguments, simply pass an array of arguments to the Gate methods:

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

Via The User Model

Alternatively, you may check abilities via the User model instance. By default, Laravel's App\User model uses an Authorizable trait which provides two methods: can and cannot. These methods may be used similarly to the allows and denies methods present on the Gate facade. So, using our previous example, we may modify our code like so:

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * Update the given post.
     *
     * @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);
    	}

    	// Update Post...
    }
}

Of course, the can method is simply the inverse of the cannot method:

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

Within Blade Templates

For convenience, Laravel provides the @can Blade directive to quickly check if the currently authenticated user has a given ability. For example:

<a href="/post/{{ $post->id }}">View Post</a>

@can('update-post', $post)
	<a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan

You may also combine the @can directive with @else directive:

@can('update-post', $post)
	<!-- The Current User Can Update The Post -->
@else
	<!-- The Current User Can't Update The Post -->
@endcan

Within Form Requests

You may also choose to utilize your Gate defined abilities from a form request's authorize method. For example:

/**
 * Determine if the user is authorized to make this request.
 *
 * @return bool
 */
public function authorize()
{
    $postId = $this->route('post');

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

Policies

Creating Policies

Since defining all of your authorization logic in the AuthServiceProvider could become cumbersome in large applications, Laravel allows you to split your authorization logic into "Policy" classes. Policies are plain PHP classes that group authorization logic based on the resource they authorize.

First, let's generate a policy to manage authorization for our Post model. You may generate a policy using the make:policy artisan command. The generated policy will be placed in the app/Policies directory:

php artisan make:policy PostPolicy

Registering Policies

Once the policy exists, we need to register it with the Gate class. The AuthServiceProvider contains a policies property which maps various entities to the policies that manage them. So, we will specify that the Post model's policy is the PostPolicy class:

<?php

namespace App\Providers;

use App\Post;
use App\Policies\PostPolicy;
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.
     *
     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
     * @return void
     */
    public function boot(GateContract $gate)
    {
        $this->registerPolicies($gate);
    }
}

Writing Policies

Once the policy has been generated and registered, we can add methods for each ability it authorizes. For example, let's define an update method on our PostPolicy, which will determine if the given User can "update" a 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 abilities it authorizes. For example, you might define show, destroy, or addComment methods to authorize various Post actions.

Note: All policies are resolved via the Laravel service container, meaning you may type-hint any needed dependencies in the policy's constructor and they will be automatically injected.

Intercepting All Checks

Sometimes, you may wish to grant all abilities to a specific user on a policy. For this situation, define a before method on the policy. This method will be run before all other authorization checks on the policy:

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

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

Checking Policies

Policy methods are called in exactly the same way as Closure based authorization callbacks. You may use the Gate facade, the User model, the @can Blade directive, or the policy helper.

Via The Gate Facade

The Gate will automatically determine which policy to use by examining the class of the arguments passed to its methods. So, if we pass a Post instance to the denies method, the Gate will utilize the corresponding PostPolicy to authorize actions:

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * Update the given post.
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
    	$post = Post::findOrFail($id);

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

    	// Update Post...
    }
}

Via The User Model

The User model's can and cannot methods will also automatically utilize policies when they are available for the given arguments. These methods provide a convenient way to authorize actions for any User instance retrieved by your application:

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

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

Within Blade Templates

Likewise, the @can Blade directive will utilize policies when they are available for the given arguments:

@can('update', $post)
	<!-- The Current User Can Update The Post -->
@endcan

Via The Policy Helper

The global policy helper function may be used to retrieve the Policy class for a given class instance. For example, we may pass a Post instance to the policy helper to get an instance of our corresponding PostPolicy class:

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

Controller Authorization

By default, the base App\Http\Controllers\Controller class included with Laravel uses the AuthorizesRequests trait. This trait provides the authorize method, which may be used to quickly authorize a given action and throw a AuthorizationException if the action is not authorized.

The authorize method shares the same signature as the various other authorization methods such as Gate::allows and $user->can(). So, let's use the authorize method to quickly authorize a request to update a Post:

<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    /**
     * Update the given post.
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
    	$post = Post::findOrFail($id);

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

    	// Update Post...
    }
}

If the action is authorized, the controller will continue executing normally; however, if the authorize method determines that the action is not authorized, a AuthorizationException will automatically be thrown which generates a HTTP response with a 403 Not Authorized status code. As you can see, the authorize method is a convenient, fast way to authorize an action or throw an exception with a single line of code.

The AuthorizesRequests trait also provides the authorizeForUser method to authorize an action on a user that is not the currently authenticated user:

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

Automatically Determining Policy Methods

Frequently, a policy's methods will correspond to the methods on a controller. For example, in the update method above, the controller method and the policy method share the same name: update.

For this reason, Laravel allows you to simply pass the instance arguments to the authorize method, and the ability being authorized will automatically be determined based on the name of the calling function. In this example, since authorize is called from the controller's update method, the update method will also be called on the PostPolicy:

/**
 * Update the given post.
 *
 * @param  int  $id
 * @return Response
 */
public function update($id)
{
	$post = Post::findOrFail($id);

	$this->authorize($post);

	// Update Post...
}