Laravel has a great roles/permissions system out-of-the-box, based on Gates and Policies, and it’s usually used to access the whole menu item or some action, like create/delete. But what if you need to restrict a certain DB COLUMN from being edited? Like, for example, “published” checkbox for articles, that can be ticked only by Administrators, and not by Authors. Let’s see how to do it.

This is our example project. List of articles:

And then, a form to add/edit article:

So, what we need to do is to show this “Published” checkbox only to Administrator users.


Step 1. Creating a Gate in AuthServiceProvider

According to official Laravel documentation, we create a special Gate called articles_publish in file app/Providers/AuthServiceProvider.php:

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

    Gate::define('articles_publish', function($user) {
        return $user->roles->where('title', 'Admin')->count();
    });
}

Now, the logic of that function is totally individual and based on your database structure for roles and permissions. In my case, I have DB tables users / roles and pivot table role_user with relationship, like this:

class User extends Authenticatable
{
    // ...

    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}

And then two roles in the database.

Table users:

Table roles:

Table role_user:

So, one Admin and one Author.

In your case, it may be more simple, if user can have only one role, so you check users.role_id field:

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

    Gate::define('articles_publish', function($user) {
        return $user->role_id == 1; // for admin
    });
}

Or maybe you don’t have a separate roles table, and just use a “flag” field?

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

    Gate::define('articles_publish', function($user) {
        return $user->isAdmin == 1; 
    });
}

Even more simple, maybe you just want to check by email, without any roles?

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

    Gate::define('articles_publish', function($user) {
        return $user->email == 'admin@admin.com'; 
    });
}

So use whichever you prefer. Later in the article, I will show you how roles/permissions function is done in our QuickAdminPanel 2019 system, so you may want to use that.


Step 2. Restricting Field from Showing in the Form

Ok, so we have our Gate for permission, let’s use it. Let’s go to our Create form and add a check.

Let’s assume you have a Blade file for the form, in my case it’s resources/views/admin/articles/create.blade.php.

<form action="{{ route("admin.articles.store") }}" method="POST">
    @csrf
    <div class="form-group {{ $errors->has('title') ? 'has-error' : '' }}">
        <label for="title">{{ trans('global.article.fields.title') }}</label>
        <input type="text" id="title" name="title" class="form-control" value="{{ old('title', isset($article) ? $article->title : '') }}">
        @if($errors->has('title'))
            <p class="help-block">
                {{ $errors->first('title') }}
            </p>
        @endif
    </div>
    <div class="form-group {{ $errors->has('article_text') ? 'has-error' : '' }}">
        <label for="article_text">{{ trans('global.article.fields.article_text') }}</label>
        <textarea id="article_text" name="article_text" class="form-control ckeditor">{{ old('article_text', isset($article) ? $article->article_text : '') }}</textarea>
        @if($errors->has('article_text'))
            <p class="help-block">
                {{ $errors->first('article_text') }}
            </p>
        @endif
    </div>
    <div class="form-group {{ $errors->has('published') ? 'has-error' : '' }}">
        <label for="published">{{ trans('global.article.fields.published') }}</label>
        <input name="published" type="hidden" value="0">
        <input value="1" type="checkbox" id="published" name="published" {{ old('published', 0) == 1 ? 'checked' : '' }}>
        @if($errors->has('published'))
            <p class="help-block">
                {{ $errors->first('published') }}
            </p>
        @endif
    </div>
    <div>
        <input class="btn btn-danger" type="submit" value="{{ trans('global.save') }}">
    </div>
</form>

So, three fields. All we need to do is to add @can .. @endcan statement before and after the published field.

@can('articles_publish')
<div class="form-group {{ $errors->has('published') ? 'has-error' : '' }}">
    <label for="published">{{ trans('global.article.fields.published') }}</label>
    <input name="published" type="hidden" value="0">
    <input value="1" type="checkbox" id="published" name="published" {{ old('published', 0) == 1 ? 'checked' : '' }}>
    @if($errors->has('published'))
        <p class="help-block">
            {{ $errors->first('published') }}
        </p>
    @endif
</div>
@endcan

And that’s it for the form. Our Administrator will see that checkbox, and Authors won’t.
You should repeat that for your Edit form, too.


Step 3. Restricting the Field to Save

You would think that the previous step was the last, since authors don’t see that checkbox. But wait, it doesn’t step them from “hacking” the system and sending POST request manually via Postman or something like that. So we need to validate it on the back-end, too.

In my case, I have app/Http/Controllers/Admin/ArticlesController.php file, with this method:

public function store(StoreArticleRequest $request)
{
    abort_unless(\Gate::allows('article_create'), 403);

    Article::create($request->all());

    return redirect()->route('admin.articles.index');
}

So we’re restricting the creation of the article, with another permission, but nothing about that published field. So, one way of doing it is to remove its value from the $request->all() in case we don’t have a permission.

Here’s our new restricted version of the method:

public function store(StoreArticleRequest $request)
{
    abort_unless(\Gate::allows('article_create'), 403);

    $data = $request->all();
    if (!auth()->user()->can('articles_publish') && isset($data['published'])) {
        unset($data['published']);
    }
    Article::create($data);

    return redirect()->route('admin.articles.index');
}

So we’re using the same can() method, attaching it to current logged in user. Additionally, we check if published checkbox is actually checked, otherwise we don’t need to do anything.

And you should repeat the same logic in your update() method of your Controller.


So, now we’re done! You have a certain field restricted by a certain Gate, attached to specific roles you want.

If you want to restrict more fields, you can repeat the same steps again, or, maybe, create a separate Gate with more than one field logic.


Bonus: Roles/Permissions in QuickAdminPanel

Finally, I wanted to show you the Roles/Permissions system of our QuickAdminPanel 2019 – maybe you would want to use our generator, or manually adapt our method of saving the Gates via Middleware, instead of AuthServiceProvider.

Here’s a video.