blog bg

March 06, 2024

How To Implement Authorization with Laravel Policies

Share what you learn in this blog to prepare for your interview, create your forever-free profile now, and explore how to monetize your valuable knowledge.

How To Implement Authorization with Laravel Policies
 

Authorization is a vital aspect of any application, allowing you to control access to resources on your server. While implementing authorization can often be cumbersome and time-consuming, Laravel's policies offer a streamlined solution, making the process remarkably simple and efficient.

 

Policies serve as organized classes for managing authorization around specific model resources. For instance, consider a scenario where you have a Product model. With Laravel policies, authorizations for this resource can be neatly organized within a dedicated class.

 

Here's a step-by-step guide to implementing authorization using Laravel policies:

 

Setting Up a Laravel Application
First, ensure you have Laravel installed on your system. If not, you can install it using Composer:

composer create-project laravel/laravel example-policy-app

 

Once Laravel is installed, navigate to your project directory and start the development server:

cd example-policy-app
php artisan serve

 

Creating User and Product Models
Next, create the necessary models for users and products and their respective migration files. Run the following Artisan commands to generate the models:

php artisan make:model User --migration 
php artisan make:model Product --migration --policy

 

The --migration flag will not only create model files but also generate migration files. These migration files are essential for migrating our data to the database schema. Additionally, the --policy flag will create a policy file specifically for the product model, facilitating fine-grained access control.

 

User Model
In the generated User.php model file located in the app directory, you'll find the basic structure of the user model. Customize this file as needed, including defining any additional attributes or relationships.
 

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // Define fillable attributes
    protected $fillable = [
        'name', 'email', 'password',
    ];

    // Define any relationships if necessary
}

 

Product Model
Similarly, the Product.php model file will be created within the app directory. This file provides the structure for the product model, including fillable attributes and relationships.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    // Define fillable attributes
    protected $fillable = [
        'title', 'price', 'user_id',
    ];

    // Define any relationships if necessary
}

 

Migration Files

 

User Migration

The migration file for the user model will be created within the database/migrations directory. It will contain the schema blueprint for the users table, specifying the structure of the user-related data in the database.

 

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

 

Product Migration

Similarly, the migration file for the product model will be generated in the same directory. This file outlines the schema blueprint for the products table, defining the structure of product-related data in the database.

 

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProductsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->decimal('price', 8, 2);
            $table->foreignIdFor(\App\Models\User::class, 'user_id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
}

 

With these migration files in place, you can seamlessly migrate your data schema to the database using Laravel's migration tools by running.

php artisan migrate

 

Now that the policy class for the product model has been created, we will register it. Registering policies is how we can inform Laravel which policy to use when authorizing actions against a given model type.

 

Registering Policies

Register the ProductPolicy in the AuthServiceProvider class located at app/Providers/AuthServiceProvider.php. Add the following line to the $policies array:

 

protected $policies = [
    'App\Product' => 'App\Policies\ProductPolicy',
];

 

Defining Authorization Logic

In the ProductPolicy.php file, you can define authorization logic for various actions, such as viewing, creating, updating, or deleting products:

<?php

namespace App\Policies;

use App\Exceptions\ForbiddenException;
use App\Models\Product;
use App\Models\User;
use Illuminate\Auth\Access\Response;

class ProductPolicy
{
    public function allowed(User $user)
    {
        return $user->hasVerifiedEmail()
            ? Response::allow()
            : throw new ForbiddenException('Email Address Not Verified');
    }

    public function view(User $user, Product $product)
    {
        return $user->id === $product->user_id
            ? Response::allow()
            : throw new ForbiddenException($user->full_name . ' with id ' . $user->id . ' is not permitted to request this resource');
    }

    public function create(User $user)
    {
        return $user->hasVerifiedEmail()
            ? Response::allow()
            : Response::deny('Email Address not verified', 401);
    }

    public function update(User $user, Product $product)
    {
        return $user->id === $product->user_id
            ? Response::allow()
            : throw new ForbiddenException($user->full_name . ' with id ' . $user->id . ' is not permitted to update this resource');
    }

    public function delete(User $user, Product $product)
    {
        return $user->id === $product->user_id
            ? Response::allow()
            : throw new ForbiddenException($user->full_name . ' with id ' . $user->id . ' is not permitted to delete this resource');
    }

    public function restore(User $user, Product $product)
    {
        if (!$user->id === $product->user_id) {
            throw new ForbiddenException($user->full_name . ' with id ' . $user->id . ' is not permitted to restore this resource');
        }

        if (!$product->trashed()) {
            throw new ForbiddenException("The requested product has a $product->status status");
        }
        return Response::allow();
    }

    public function forceDelete(User $user, Product $product)
    {
        if (!$user->id === $product->user_id) {
            throw new ForbiddenException($user->full_name . ' with id ' . $user->id . ' is not permitted to parmenently delete this resource');
        }

        if (!$product->trashed()) {
            throw new ForbiddenException("Product must be soft-deleted (archived) before it can be permanently deleted.");
        }
        return Response::allow();
    }
}

 

In Laravel's policy class, each method must either return a boolean value or throw an exception. Laravel also offers Response instances, allowing for more detailed responses within our policy implementation. This enables us to provide users with clearer error messages when necessary.

 

Routing Setup and Policy Implementation

Now, let's integrate our product policies into the product route file for seamless authorization handling:

 

<?php

use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;

/**
 * @see \App\Policies\ProductPolicy for Authorization middleware
 * @see https://laravel.com/docs/10.x/routing#route-group-controllers
 */
Route::group([
    'as' => 'product.',
    'namespace' => "\App\Http\Controllers",
    'prefix' => 'products',
    'middleware' => [
        'auth:sanctum',
    ]
], function () {

	 // Route for creating a new product
    Route::post('/', [ProductController::class, 'store']);


 	// Route for fetching all products belonging to a user
    Route::get('/users', [ProductController::class, 'index']);
    
    // Route for viewing a specific product
    
Route::get('/{product}', [ProductController::class, 'show'])->middleware('can:view,product');
    
    
// Route for updating an existing product
    

Route::put('/{product}', [ProductController::class, 'update'])->middleware('can:update,product');
    
    

// Route for soft deleting a product

    


Route::delete('/{product}', [ProductController::class, 'delete'])->middleware('can:delete,product');
 
    // Route for restoring a soft-deleted product
    Route::get('/{product}/restore', [ProductController::class, 'restore'])->middleware('can:restore,product');
    
    

// Route for permanently deleting a soft-deleted product

    Route::delete('/{product}/force', [ProductController::class, 'forceDelete'])->middleware('can:forceDelete,product');
});

 

With these steps completed, you've successfully implemented authorization using Laravel policies. Now you can easily manage access to resources within your application.

 

Implementing authorization doesn't have to be daunting, thanks to Laravel's powerful features like policies. With a structured approach and clear guidelines, you can ensure secure and controlled access to your application's resources.

 

That's it! You're now equipped to efficiently manage authorization in your Laravel applications using policies.

275 views

Please Login to create a Question