Building a Multi-Role CMS with Custom Policies in Laravel 11

Hello, laravel web developers! In this tutorial, we’ll build a Multi-Role CMS in Laravel 11 using custom policies to manage user roles such as Admin, Editor, and Author. Policies allow us to control user access dynamically and give specific permissions based on roles.

Once the policy class has been registered, you may add methods for each action it authorizes. If you used the --model option when generating your policy via the Artisan console, it will already contain methods for the viewAnyviewcreateupdatedeleterestore, and forceDelete actions.

Laravel 11 Building a Multi-Role CMS with Custom Policies

Laravel 11 Building a Multi-Role CMS with Custom Policies

 

Prerequisites:

  • Basic knowledge of Laravel.
  • A fresh Laravel 11 project set up.
  • Laravel's authentication system (can be scaffolded using php artisan make:auth or Jetstream)

 

Step 1: Install the Laravel 11 application

We'll install the laravel 11 application using the following command in this step.

composer create-project laravel/laravel laravel-11-example

 

Step 2: Set Up Roles and Permissions in the Database

We’ll start by defining user roles and assigning them permissions. For simplicity, we'll store user roles directly in the users table.

Add Role Field to Users Table

php artisan make:migration add_role_to_users_table --table=users

Migration:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('role')->default('author'); // Default role set as 'author'
    });
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('role');
    });
}

Now, run the migration:

php artisan migrate

 

Step 3: Define User Roles

We’ll define roles in the User model as constants.

app/Models/User.php

class User extends Authenticatable
{
    const ROLE_ADMIN = 'admin';
    const ROLE_EDITOR = 'editor';
    const ROLE_AUTHOR = 'author';

    // Check role methods for convenience
    public function isAdmin()
    {
        return $this->role === self::ROLE_ADMIN;
    }

    public function isEditor()
    {
        return $this->role === self::ROLE_EDITOR;
    }

    public function isAuthor()
    {
        return $this->role === self::ROLE_AUTHOR;
    }
}

 

Step 4: Define Custom Policies

Next, we'll use Laravel Policies to authorize user actions based on their roles. Create a Policy for the Post Model.

php artisan make:policy PostPolicy --model=Post

Edit the generated policy to include methods that define access based on user roles.

app/Policies/PostPolicy.php

class PostPolicy
{
    // Only Admins and Editors can view all posts
    public function viewAny(User $user)
    {
        return $user->isAdmin() || $user->isEditor();
    }

    // Only Admins, Editors, and Authors can view individual posts
    public function view(User $user, Post $post)
    {
        return $user->isAdmin() || $user->isEditor() || $user->isAuthor();
    }

    // Only Admins and Editors can create new posts
    public function create(User $user)
    {
        return $user->isAdmin() || $user->isEditor();
    }

    // Only Admins and Editors can update posts
    public function update(User $user, Post $post)
    {
        return $user->isAdmin() || $user->isEditor();
    }

    // Only Admins can delete posts
    public function delete(User $user, Post $post)
    {
        return $user->isAdmin();
    }
}

 

Step 5: Register the Policy

In AuthServiceProvider, register the policy to link it with the Post model.

app/Providers/AuthServiceProvider.php

use App\Models\Post;
use App\Policies\PostPolicy;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    public function boot()
    {
        $this->registerPolicies();
    }
}

 

Step 6: Applying Policies in Controllers

With the policies defined, let's use them in the PostController.

app/Http/Controllers/PostController.php

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    // Show all posts (Admin/Editor only)
    public function index()
    {
        $this->authorize('viewAny', Post::class);
        $posts = Post::all();
        return view('posts.index', compact('posts'));
    }

    // Show single post (All roles)
    public function show(Post $post)
    {
        $this->authorize('view', $post);
        return view('posts.show', compact('post'));
    }

    // Create a new post (Admin/Editor only)
    public function create()
    {
        $this->authorize('create', Post::class);
        return view('posts.create');
    }

    // Store the post
    public function store(Request $request)
    {
        $this->authorize('create', Post::class);

        // Validation and saving logic
    }

    // Edit a post (Admin/Editor only)
    public function edit(Post $post)
    {
        $this->authorize('update', $post);
        return view('posts.edit', compact('post'));
    }

    // Update the post
    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post);
        
        // Validation and updating logic
    }

    // Delete a post (Admin only)
    public function destroy(Post $post)
    {
        $this->authorize('delete', $post);
        
        // Delete logic
    }
}

 

Step 7: Blade Views Authorization

In your Blade views, use the @can directive to control the visibility of actions based on roles.

posts/index.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Building a Multi-Role CMS with Custom Policies in Laravel 11 - Techsolutionstuff</title>
</head>
<body>
	<h2>Laravel 11 Building a Multi-Role CMS with Custom Policies - Techsolutionstuff</h2>
	@foreach ($posts as $post)
		<div class="post">
			<h3>{{ $post->title }}</h3>
			<p>{{ $post->body }}</p>

			<!-- Show Edit button if the user can update the post -->
			@can('update', $post)
				<a href="{{ route('posts.edit', $post) }}">Edit</a>
			@endcan

			<!-- Show Delete button if the user can delete the post -->
			@can('delete', $post)
				<form action="{{ route('posts.destroy', $post) }}" method="POST">
					@csrf
					@method('DELETE')
					<button type="submit">Delete</button>
				</form>
			@endcan
		</div>
	@endforeach  
</body>
</html>

 

Step 8: Middleware for Role-Based Routing

If you want to restrict certain routes based on roles, you can create a custom middleware. Create Middleware using the following command.

php artisan make:middleware CheckRole

app/Http/Middleware/CheckRole.php 

public function handle($request, Closure $next, $role)
{
    if (!auth()->check() || auth()->user()->role !== $role) {
        abort(403, 'Unauthorized action.');
    }

    return $next($request);
}

Register Middleware in the app.php file:

bootstrap/app.php

<?php
   
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
   
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
            'role' => \App\Http\Middleware\CheckRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

Use Middleware in Routes. Now, you can restrict routes based on roles like this:

routes/web.php

Route::group(['middleware' => ['role:admin']], function () {
    Route::resource('posts', PostController::class);
});

 

Step 9: Run the Laravel 11 Application

Now, run the laravel 11 application using the following command.

php artisan serve

 


You might also like:

techsolutionstuff

Techsolutionstuff | The Complete Guide

I'm a software engineer and the founder of techsolutionstuff.com. Hailing from India, I craft articles, tutorials, tricks, and tips to aid developers. Explore Laravel, PHP, MySQL, jQuery, Bootstrap, Node.js, Vue.js, and AngularJS in our tech stack.

RECOMMENDED POSTS

FEATURE POSTS