Authorization
Authorization is off by default, so existing callers are unaffected. Opt in with comments.authorization (COMMENTS_AUTHORIZATION=true) and register a policy for the Comment model. The package ships a permissive starting point — every ability returns true — that you extend:
use Illuminate\Support\Facades\Gate;
use RoundlyConsulting\Comments\Models\Comment;
use RoundlyConsulting\Comments\Policies\CommentPolicy;
Gate::policy(Comment::class, CommentPolicy::class);Abilities
| Ability | Checked by | Argument |
|---|---|---|
create | Every write — builder, write(), writeComment() | The subject — for a reply, the root subject it joins |
update | Comments::update() | The Comment |
delete | Comments::delete(), deleteAll() | The Comment |
restore | Comments::restore() | The Comment |
moderate | Comments::approve() / hide(), approveAll() / hideAll() | The Comment |
lock / unlock | Comments::lock() / unlock(), lockThread() / unlockThread(), lockReplies() / unlockReplies() | The subject, or the Comment for a thread lock |
Checks run against the currently authenticated user ($user is null for a guest), and a denial throws UnauthorizedCommentActionException. A policy registered for Comment also covers a subclass configured in comments.model; with nothing registered, Laravel’s auto-discovery resolves the shipped, permissive CommentPolicy.
Your own policy
create receives the subject being commented on — for a reply, the root subject it joins — so you can decide per subject; lock and unlock receive what is being locked. A custom policy that lacks one of these methods denies that action, so extend the shipped CommentPolicy:
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Comments\Policies\CommentPolicy;
class ProjectCommentPolicy extends CommentPolicy
{
public function create(?Model $user, ?Model $commentable = null): bool
{
return $user !== null && $commentable instanceof Project && $commentable->hasMember($user);
}
public function lock(?Model $user, Model $lockable): bool
{
return $user?->isModerator() ?? false;
}
}namespace App\Policies;
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Comments\Models\Comment;
use RoundlyConsulting\Comments\Policies\CommentPolicy as BaseCommentPolicy;
class CommentPolicy extends BaseCommentPolicy
{
public function update(?Model $user, Comment $comment): bool
{
return $user !== null && $comment->actor?->is($user) === true;
}
public function delete(?Model $user, Comment $comment): bool
{
return $this->update($user, $comment);
}
}
// AppServiceProvider::boot()
Gate::policy(\RoundlyConsulting\Comments\Models\Comment::class, \App\Policies\CommentPolicy::class);Answering 403
The package ships no routes, so the exception is not an HTTP exception. Map it onto Laravel’s AuthorizationException to answer 403 from your own controllers:
use Illuminate\Auth\Access\AuthorizationException;
use RoundlyConsulting\Comments\Exceptions\UnauthorizedCommentActionException;
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->map(fn (UnauthorizedCommentActionException $e) => new AuthorizationException($e->getMessage()));
})Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.