Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

reneweiser's avatar

Approach to create a permissions system that allows a user to only edit resources they own or are part of

tl;dr

Is there a better approach to implement a role/permission system that works based on individual resources, without using a third party package?

Requirements I'm trying to implement

  1. The application uses Laravel
  2. In the application you can create articles
  3. You need the permission to create articles
  4. You can invite other users of the application to edit the text of the article
  5. Invited users can not change the title or the published state of the article, only the user that created the article can do that
  6. It is possible to transfer ownership, each article only has one owner
  7. Users can be invited to any number of articles
  8. Users can own any number of articles
  9. Admins have full control over articles, even if they are not owners

I know of spatie/laravel-permission. But I'm trying to solve as much as I can without third party packages.

Here are the roles and their permissions based on the requirements:

  • Guest
  • User
    • Can create articles
  • Editor
    • Can edit
  • Owner
    • Can edit
    • Can publish
    • Can change title
    • Can delete
  • Admin
    • all of the above regardles of ownership

Approach 1

Tables:

  • users
  • articles
  • article_user (pivot)
    • article_id
    • user_id
    • role_id
  • roles
  • role_user (pivot)
    • user_id
    • role_id
  • permissions
  • permission_role (pivot)

The table "article_user" has an extra column role_id. The role_id allows me to map the permissions associated with the role to any given action I want to allow/deny (e.g. "update-title", "publish-article", "edit-text").

This works for articles only. But now I store the relation between roles and users in two different tables and for each new model I would have to create a new pivot which doesn't seem to be a good solution.

Approach 2

Tables:

  • users
  • articles
  • roles
  • role_user (pivot)
    • role_id
    • user_id
  • permissions
  • permission_role (pivot)
    • permission_id
    • role_id
    • model (e.g. "App\Article")
    • model_id

The permissions table contains the columns model and model_id. It maps any given permission to the model/resource it applies to. model stores the string representation of the model (e.g. "App\Article") and model_id is the ID of the specific resource.

With this I can map permissions to any model/resource I want without the need to create new pivots for each new model that may be introduced later. But now I can't guarantee data integrity when resources get deleted because I lost the foreign key constraint. I could keep track of deletions myself (which I would like to avoid). model_id no longer refers to any specific table, but instead the column model tells me which type of resource I should query.

Approach 3

As suggested by @smnhunt in this thread, which is basically Approach 1 but it also has the drawback of not having data integrity for free since deleting a user would leave an orphan record in the json field.

0 likes
4 replies
notomato's avatar

Policy classes are the way to go and are really simple/flexible. The logic lives in the policy class method and you get the model and user as arguments so you can check whatever you like - just return boolean. You don't even need roles table, as long as you store who is invited, the owner id etc you just check it all in the policy method.

reneweiser's avatar

When it comes to authorise actions, you should consider using Laravel Policies

@amaury Yes, I am using Gates and Policies in both approaches but I want to find an appropriate way to organize data that fits my requirements first.

There are Laracasts lessons about Gates and Policies

@amaury Unless I missed something, those tutorials explain how you would set up a system that checks if a single user owns/created an article. But if I want Article-User to be a Many-to-Many relationship with individual permissions attached, I think I need a different approach.

You don't even need roles table, as long as you store who is invited, the owner id etc you just check it all in the policy method.

@notomato Are you thinking of something like a json field on the articles table that lists users and their permissions? That would be the third approach I found. Granted, you could easily use that inside a policy, but you can't use a foreign key constraint to ensure data integrity. I think I should let the database take care of deleting orphaned records.

smnhunt's avatar

I've only just spotted this thread. There would be the option of including the JSON containing information about the roles on the pivot table record rather than on the user/article. This way information about permissions would be freely available (e.g. within a policy) but the database would also delete the pivot record when the user/article was deleted.

Please or to participate in this conversation.