Implementing RBAC in Laravel Tutorial

Posted on

This article was published on my personal blog

Role-Based Access Control, or RBAC, is the ability to add roles or restrict actions for users. It can be done in a general, high-level way, for example, to disallow some users from login into the admin panel. It can also be done more specifically, for example, allowing users to view a post but not edit it.

In this tutorial, you’ll learn how to implement RBAC in Laravel using Bouncer. Bouncer is a PHP package that lets you add roles and abilities to your Eloquent models.

You’ll build an editor that lets the user create private posts with the ability to allow other users to view and edit their posts. You can find the code for this tutorial in this GitHub repository.



Prerequisites

You need to download Composer to follow along in this tutorial.

In addition, this tutorial uses Laravel 8 with PHP 7.3. To run Laravel 8, you need your PHP version to be at least 7.3.

You can check your PHP version in the terminal:

php -v
Enter fullscreen mode

Exit fullscreen mode

NPM is also used in some parts of this tutorial, but it’s not important for implementing RBAC. If you want to follow along with everything, make sure you have NPM installed. NPM is installed by installing Node.js.



Project Setup

The first step is to set up the Laravel project. In your terminal, run the following command:

composer create-project laravel/laravel laravel-rbac-tutorial
Enter fullscreen mode

Exit fullscreen mode

Once that is done, switch to the directory of the project:

cd laravel-rbac-tutorial
Enter fullscreen mode

Exit fullscreen mode

Then, you need to add your database configuration on .env:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
Enter fullscreen mode

Exit fullscreen mode

Now migrate Laravel’s default migrations to your database:

php artisan migrate
Enter fullscreen mode

Exit fullscreen mode

This will add Laravel’s default tables as well as the users table.



Implement Authentication

To implement authentication easily, you can use Laravel UI. Install it with this command:

composer require laravel/ui
Enter fullscreen mode

Exit fullscreen mode

Then, run the following command to add the UI for authentication:

php artisan ui bootstrap --auth
Enter fullscreen mode

Exit fullscreen mode

This will add the directory app/Http/Controllers/Auth with the controllers needed to implement authentication. It will also add the necessary view files resources/views to add pages like login and register.

Then, compile the CSS and JavaScript assets added by the previous command:

npm install && npm run dev
Enter fullscreen mode

Exit fullscreen mode

This command might end in an error. If so, run the dev script again:

npm run dev
Enter fullscreen mode

Exit fullscreen mode

Finally, go to app/Providers/RouteServiceProvider.php and change the value for the constant HOME:

public const HOME = '/';
Enter fullscreen mode

Exit fullscreen mode

Now, run the server:

php artisan serve
Enter fullscreen mode

Exit fullscreen mode

Then, go to localhost:8000. You’ll see the login form.

Implementing RBAC in Laravel Tutorial

This is added by Laravel UI. Since you don’t have a user yet, click on Register in the navigation bar. You’ll see then a registration form.

Implementing RBAC in Laravel Tutorial

After you register as a user, you’ll be logged in.

Implementing RBAC in Laravel Tutorial



Add Posts

Now, you’ll add posts that the user will be able to create.

Start by creating a migration:

php artisan make:migration create_posts_table
Enter fullscreen mode

Exit fullscreen mode

This will create a new migration file in database/migrations with the file name’s suffix create_posts_table.

Open the migration file and replace its content with the following:

<?php

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->longText('content');
            $table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();
            $table->timestamps();
        });
    }

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

Enter fullscreen mode

Exit fullscreen mode

Now, migrate the changes to create the posts table:

php artisan migrate
Enter fullscreen mode

Exit fullscreen mode

Next, create the file app/Models/Post.php with the following content:

<?php

/**
 * Created by Reliese Model.
 */

namespace AppModels;

use CarbonCarbon;
use IlluminateDatabaseEloquentModel;

/**
 * Class Post
 * 
 * @property int $id
 * @property string $title
 * @property string $content
 * @property int $user_id
 * @property Carbon|null $created_at
 * @property Carbon|null $updated_at
 * 
 * @property User $user
 *
 * @package AppModels
 */
class Post extends Model
{
    protected $table = 'posts';

    protected $casts = [
        'user_id' => 'int'
    ];

    protected $fillable = [
        'title',
        'content',
        'user_id'
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
Enter fullscreen mode

Exit fullscreen mode



Add Post Form Page

You’ll now add the page the user will use to create a new post or edit an old one.

In your terminal, run:

php artisan make:controller PostController
Enter fullscreen mode

Exit fullscreen mode

This will create a new controller in app/Http/Controllers/PostController.php. Open it and add a constructor method:

/**
 * Create a new controller instance.
 *
 * @return void
 */
public function __construct()
{
    $this->middleware('auth');
}
Enter fullscreen mode

Exit fullscreen mode

This adds the auth middleware to all methods in this controller. This means that the user must be logged in before accessing any of the routes that point at this controller.

Next, add the postForm function that renders the post form view:

public function postForm ($id = null) {
    /** @var User $user */
    $user = Auth::user();

    $post = null;
    if ($id) {
        /** @var Post $post */
        $post = Post::query()->find($id);
        if (!$post || $post->user->id !== $user->id) {
            return response()->redirectTo('/');
        }
    }

    return view('post-form', ['post' => $post]);
}
Enter fullscreen mode

Exit fullscreen mode

Notice that this receives an optional id paramter, then retrieves the post based on that ID. It also validates that the post exists and belongs to the current logged-in user. This is because this method will handle the request for both creating a post and editing a post.

Then, create the view resources/views/post-form.blade.php with the following content:

@extends('layouts.app')

@push('head_scripts')
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/trix@1.3.1/dist/trix.css">
@endpush

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ $post ? __('Edit Post') :__('New Post') }}</div>

                <div class="card-body">
                    <form method="POST" action="#">
                        @csrf
                        @error('post')
                            <div class="alert alert-danger">{{ $message }}</div>
                        @enderror
                        <div class="form-group">
                            <label for="title">{{ __('Title') }}</label>
                            <input type="text" name="title" id="title" placeholder="Title" required 
                               value="{{ $post ? $post->title : old('title') }}" class="form-control @error('title') is-invalid @enderror" />
                            @error('title')
                                <span class="invalid-feedback">{{ $message }}</span>
                            @enderror
                        </div>
                        <div class="form-group">
                            <label for="content">{{ __('Content') }}</label>
                            @error('content')
                                <span class="invalid-feedback">{{ $message }}</span>
                            @enderror
                            <input id="content" type="hidden" name="content" value="{{ $post ? $post->content : old('content') }}">
                            <trix-editor input="content"></trix-editor>
                        </div>
                        <div class="form-group">
                            <button type="submit" class="btn btn-primary">{{ __('Submit') }}</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/trix@1.3.1/dist/trix.js"></script>
@endsection
Enter fullscreen mode

Exit fullscreen mode

This shows a form with 2 inputs. A title text input, and a text editor for the content. The text editor is Trix, an easy-to-use open-source editor.

You still need to add the link to the page. So, in resources/views/layouts/app.blade.php add the following menu item in the ul under the comment <!-- Left Side Of Navbar -->:

<li class="nav-item">
    <a class="nav-link" href="{{ route('post.form') }}">{{ __('New Post') }}</a>
</li>
Enter fullscreen mode

Exit fullscreen mode

Finally, add the route to the page in routes/web.php:

Route::get('/post/{id?}', [PostController::class, 'postForm'])->name('post.form');
Enter fullscreen mode

Exit fullscreen mode

If you go to the website now, you’ll notice a new link in the navbar that says “New Post”. If you click on it, you’ll see the form you created for the posts.

Implementing RBAC in Laravel Tutorial



Save Posts

Before you implement the save functionality for posts, it’s time to use Bouncer to implement RBAC.

In your terminal, run the following to install Bouncer:

composer require silber/bouncer v1.0.0-rc.10
Enter fullscreen mode

Exit fullscreen mode

Then, in app/Models/User.php make the following changes:

use SilberBouncerDatabaseHasRolesAndAbilities;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable, HasRolesAndAbilities;

Leave a Reply

Your email address will not be published.