How to Use Policies in Laravel

Laravel policies can streamline the creation of website security measures. Security is an important aspect of most websites, especially those that contain sensitive information. This starts with securing the majority of the content behind a login page. We can also add in 2-factor Authentication, Recaptchas, and other features to make the login more secure.

However, there are further considerations. For example, if our site contains information about Companies, with one or more Users belonging to a Company, we don’t want a User to see information about a Company that they do not belong to. We also don’t want them to delete the Company record, or their own User record, since that can cause issues with our website. If we also have a table for Orders, and Orders are associated with a Company, then a User should only be able to view or edit Orders that are associated with their Company.

Setting up Security

Let’s assume we have 2 basic roles – Administrator and Client. We might want to set up our security like this:

AdministratorView/Edit/Delete all Users, Create Users
View/Edit/Delete Companies, Create Companies
View/Edit/Delete all Orders, Create Orders
ClientView all Users associated with their Company, Edit their User information
View/Edit their Company record
View/Edit Orders for their Company, Create Orders
Security Roles

There a number of ways we can accomplish this. We could put the logic in the controllers for the Users, Companies and Orders. For example, we might have this method in the Order controller:

    public function index() {
        switch (Auth::user()->role) {
            case 'Client':
                $orders = Order::where('company_id', '=', Auth::user()->company_id)->get();
                break;
            case 'Administrator':
                $orders = Order::all();
            default:
                $orders = null;
                break;
        }
        return view('Orders.Index',['orders'=>$orders]);
    }

This works fine, but this won’t be the only place we need to get user records in the website. So we would have to modify every query for Users in the system to check the logged in user’s role, and add that extra where clause. That isn’t terribly efficient, and it would be easy to miss a query, especially for a new developer on the project who doesn’t know the ins and outs of the system. In addition, we will most likely have multiple tables that require this type of security. Learn about best pratices here.

Creating Laravel Policies

Instead of this, we can use policies. Policies are classes that organize authorization logic around a particular model or resource. Using our example, we would create policies for Users and Companies. Polices are generated using the make:policy Artisan command. This will create an empty policy class in the app/Policies directory. If you want to create a policy class with example policy methods related to viewing, creating, updating, and deleting the resource, you can use the --model option.

To generate the policies for the models in our example, we would use the following commands:

php artisan make:policy UserPolicy --model=User
php artisan make:policy CompanyPolicy --model=Company
php artisan make:policy OrderPolicy --model=Order

Naming Laravel Policies

In general, we recommend naming your policy based on the model name – e.g. UserPolicy. By doing so, Laravel will automatically associate the policy with the correct model. If we want to name your policy something else, we can modify App\Providers\AuthServiceProvider with a mapping of the policies to the correct model.

<?php

namespace App\Providers;

use App\Models\Order;
use App\Policies\OrderPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Order::class => OrderPolicy::class,
    ];

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

        //
    }
}

Adding Security Rules to the Policy

Now that the policy has been created, we can set up the security for our models. We’ll set up the Order Policy as an example. Referencing our example above, Administrators can view/edit/delete all Order records and create Orders, while Clients can view/edit orders for their Company, and create Orders.

Looking at the example code for the controller created above, Client users can view and edit any order records where the value in the company_id column matches the the value in their company_id column. For each method in the OrderPolicy, two parameters are passed. The $user parameter is the model for the logged in User, while the $order parameter is the model for the Order.

An example of the OrderPolicy based on the rules we defined would like like this:

<?php

namespace App\Policies;

use App\Models\Order;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class OrderPolicy
{
    use HandlesAuthorization;

    /**
     * Determine whether the user can view the model.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Order  $order
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function view(User $user, Order $order)
    {
        switch ($user->role) {
            case 'Administrator':
                return true;
            case 'Client':
                return $order->company_id === $user->company_id;
            default:
                return false;
        }
    }

    /**
     * Determine whether the user can create models.
     *
     * @param  \App\Models\User  $user
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function create(User $user)
    {
        return true;
    }

    /**
     * Determine whether the user can update the model.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Order  $order
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function update(User $user, Order $order)
    {
        switch ($user->role) {
            case 'Administrator':
                return true;
            case 'Client':
                return $order->company_id === $user->company_id;
            default:
                return false;
        }
    }

    /**
     * Determine whether the user can delete the model.
     *
     * @param  \App\Models\User  $user
     * @param  \App\Models\Order  $order
     * @return \Illuminate\Auth\Access\Response|bool
     */
    public function delete(User $user, Order $order)
    {
        switch ($user->role) {
            case 'Administrator':
                return true;
            default:
                return false;
        }
    }
}

Any time a query is run against the Order table, the appropriate method will be checked in the OrderPolicy. If it returns true, then the query succeeds (or in the case of a view, the order record is included in the result). If it returns false, the query will fail (or the order will not be included in the view).

This is a pretty simple example, but it gives us a good idea of what is possible with Laravel policies. For more documentation and examples, you can reference Laravel’s documentation. You can also find out more about our Laravel projects and abilities here.

This technique is the result of our explorations. It may not be suitable for your needs. Each technique has its advantages and disadvantages and we invite you to do your own experiments and draw your own conclusions based on your situation.