Laravel Roles/Permissions: Complex Multi-Clinic Project
Fe0VSwpZpaU — Published on YouTube channel Laravel Daily on October 8, 2024, 5:00 AM
Watch VideoSummary
This summary is generated by AI and may contain inaccuracies.
- Speaker A released a course on roles and permissions recently. In this video, Speaker A will try to summarize the seven lessons from the course into main things you need to know. - The project is based on Laravel Breeze and the demos are done with the help of the demo package. Then we discuss the database structure and the setup of the database. - In Laravel, they check the check for the team for the clinic in the policy, but in this project they are relying on global scopes instead. - Speaker A emphasizes automated tests in this video because unauthorized access to the records is one of the most crucial and most typical security issues in projects in general. There are quite a lot of tests in the course and in the demo project.
Video Description
An overview of the demo-project in our course on Roles and Permissions.
Full course: https://laraveldaily.com/course/roles-permissions?mtm_campaign=youtube-roles-permissions-main
- - - - -
Support the channel by checking out my products:
- My Laravel courses: https://laraveldaily.com/courses
- Filament examples: https://filamentexamples.com
- Livewire Kit Components: https://livewirekit.com
- - - - -
Other places to follow:
- My weekly Laravel newsletter: https://us11.campaign-archive.com/home/?u=a459401212599a54203d036ee&id=91c1337873
- My personal Twitter: https://twitter.com/povilaskorop
Transcription
This video transcription is generated by AI and may contain inaccuracies.
Hello guys, have you ever had to deal with complex roles and permission system like this one? So we received a comment a month ago and decided to create a demo project which turned into a course. So we released a course on roles and permissions recently and I will link that in the description below. And one of the sections of the course is about one project based on this screenshot. But it's a typical complex scenario of multiple roles in multiple teams where each role has a different feature set. So if you want it more visual, little down below I have Excel sheet, this one. So this table describes who can do what. It's a task management system, for example for a clinic, but it may be for any team and then there are users and teams. So in this video I will try to summarize those seven lessons from the course into main things you need to know, which becomes kind of a tradition on this channel where release a course. And then I summarize the main things on YouTube here for free. But if you want the full course with the detail and step by step and the access to the repository, then it's inside the course which will be linked in the description below. So we will dive into gates, policies, spotsy permission package, global scopes and other things. But first, the demonstration of the project itself is based on Laravel Breeze visually. And here I am logged in as a master admin, which is just for managing the teams, in this case clinic. Let me zoom that in so you can just add new clinic as master admin. Choose the user from the owners because owner may have multiple clinics or create a new user. So that's the sole purpose of that role. Master Admin. Now if I log in as a clinic owner, I can first switch between the clinics, between the teams and then manage the users of my clinic. And also clinic owner doesn't have the access to the tasks. And then if we log in with a lower level user, for example staff or doctor, then we can manage the tasks, assign them to patients and stuff like that. So the full crud of the tasks is available to staff or doctor, except for delete. From what I remember, doctor cannot delete the task, so it's more granular. Not just menu items visible or not, it's permissions inside of those menu items, also different for different roles. Now let's dive into the code. First let's discuss the database structure. In this project we're using spotty laravel permission package which results in the database schema like this. So we have roles, global roles for all the teams. Then role has permissions and permissions are in a separate database table. And then we also have default users of Laravel with a lot of different emails and fake data. But also those users have roles which is in the table. Model has roles which uses polymorphic relationships. And the important part here is just not model has roles but model has roles within the team. So this is what makes that project complex. There may be different roles within different teams. The roles and permissions are seated in this role and permission seeder. So we have for each of the permissions and create them in the database. And then for each of the roles we assign the permissions to those roles. Those permissions and role enum are enum classes from app enums, permission and row. Why those enums? If we reference the permission name multiple times in the same project, for example list team with uncheck by permission with gate authorize or something like that, then we use this string quite a few times and while writing that code elsewhere, we don't really even remember, is it list team? Is it index team? Is it underscore? Is it dash? Is it one word? How is it spelled? So quite a big possibility of typos and bugs as a consequence. Instead we have kind of the one stop shop, the ultimate source of truth of names of permissions. And then everywhere else we reference permissions with permission cases or specific name of permission like this instead of strings. And yeah, then we have a big switch case here. In case of this role we add these permissions and so on. So we have role sync permission at the end of this private method which is called in the main seeder. So this results in the seating of rows and permissions. Then for the users we have a user seeder which looks like this. We see Master Admin, clinic owner and other users with factories. And those factories have states. So staff is if we go to user factory here we have master admin which creates the team in itself automatically, then clinic owner also with the team. And then for each user we update the current team, set the permissions with a spidey package and assign the role. And then for all the other users, non admin, non clinic owners, then we do first or create for the team checking if that team already exists with this name that was set as a private property in the factory itself. So yeah, this is the setup of the database. An interesting part, an interesting kind of notice about relationships between users and teams. So user may have multiple teams and you would be tempted to create a pivot table which is team user. A typical belongs to many relationship but the thing is that we can reuse the same table of spotsy laravel permission package which is this one model has role which comes from the config and do belongs to menu with team model not through the pivot table but through spaty specific table with model id as a pivot key. So this table I showed you before that already. This is our actually pivot table. And now I will show you how we use the permissions in one of the controllers. There are three controllers, three big features in this project which is teams, users and tasks. And one of those will be shown here. So we have task controller to manage tasks. This is how it looks on the website. So add task, fake filler, chrome extension, save task, then you can edit and delete. So nothing really fancy here, but what is maybe fancy is about permissions. This is exactly the topic of this video. So in each of the method of the controller we have gate authorize at the beginning. So we check if the current user has the ability of view any and then create and then create here as well. Update for the edit and update methods and then delete for destroy method. Those names of the abilities are not random, they are powered by typical policy names. So separately we have a policy called task policy for the model of the task with these methods view any, create, update and delete. And this so happens that we use spicy Laravel permission under the hood. So all we need to do to return who has permission is check the permission for specific permission. Again enum here instead of string. And that function comes from spicy permission package. So to reiterate in the controller we call gate authorize which is the method name of the policy. And inside we use spicy permission package. Now this is only one of the options. This is kind of a bigger setup for potentially more complex projects. But you can avoid using policies that's not necessary. We just decided in this course to show that more complex structure for the cases. That policy check may be more complex than just this, but you also may do something like this gate authorize and instead of view any directly do permission list task. And this is another benefit of using enums autocomplete in your IDE. Something like this is also possible and maybe more beneficial. But we deliberately went for a bit more complex structure with policies to showcase the Laravel capabilities for more robust projects. Now let's talk about multi tenancy in the controller and in the policy you don't see any check for the team for the clinic. So potentially someone could land on the task from another clinic. So we should probably check it somewhere and we do, but not in here. And again, there are two options, at least two options how to do that. In Laravel, one of the options is to do that in the policy. So for example, we can update the task only our own team task. So you can do user has permission to and task team id for example equals our own auth user current team id, something like this. But in this project we are relying on global scopes instead. In eloquent, even before the permission, while doing the route model binding in this task, for example, in the edit task before gate authorize, the task is resolved like this. So task model has a booted method with global scope here team tasks. In fact it's two in one global scopes. So if we have a logged in user, which means we're not in the terminal or artisan command or queue or something like that, then we filter all the records of the tasks by current id of the team of the user and also on top. That's why I called it two in one. If we have the role of patient, then we show only the tasks for the patient. So that's why we don't need to check that in the policy. It's checked in the background in the global scope of eloquent for all the eloquent queries in all the project, not necessarily only in that controller. Which means that if I have two tasks, I created a second temporary task, fake task with another team id here. So I'm logged in with team id two now and I have only my task. So in the list I don't see that task of ABC. And if I click edit, I can edit that task, but if I go to id to edit, look what happens. I have 404 not found, as if that task didn't exist in the database at all. And this may be a difference for you in some cases. What do you want to return in case of unauthorized access to another record? In case of global scope, this where condition forces to record not to be found and return 404 even before the gate or policy check. But we could also comment that out and then in the permission we also don't check it. And if we refresh we have unauthorized access, which is a security issue. So if we don't check that in the global scope, then we need to do something like this in the update method. So now for refresh we receive 403 different status code, different error message. You may customize both of them, but basically this means record is in the database but you don't have access to it. It's your personal preference which to use? The final thing I want to emphasize in this video is automated tests. While creating this course, we have made a lot of changes in roles, permissions, and experimenting with a lot of things. So to ensure that we didn't break anything accidentally, even for ourselves, it was very helpful to have this for example, task test. We're using pest here, but PHP unit should be roughly similar. For example, checking that create task page is accessible to those users. And this is where factory with different states also paid off massively. So we have quite a lot of tests in this course and in this demo project, roughly about 60 from what I remember. So if we run PHP artisan test in the terminal, I will do it like this. So we have roughly 2 seconds, 70 tests. So a lot of cases, or sometimes one case is with different users or different roles with past data sets, as you saw already just now. But basically what I want to emphasize is for complex roles and permissions logic, it's a must to have automated test, because unauthorized access to your records is one of the most crucial and most typical security issues in projects in general. So the cost of the bug on this level may be pretty high. I remember Matt Stouffer once said on the stage that if you have limited time for automated tests, first you need to test those features, that if they fail you would lose your job. So this is one of the examples. So yeah, this is the project that I wanted to show you with roles and permissions. Of course, it's just one option, one way of dealing with that. You may check different permissions in different ways, you may or may not use policies, you may or may not use the package of spotsy permission, or you may use another package like bouncer. So as usual in Laravel, there are many ways to do the same thing. So we can discuss in the comments what would you do differently. But if you like this approach, or you want to get deeper analyzing that code, I remind you that this comes from the course which we released recently. Roles and permissions in Laravel eleven, lesson six to lesson twelve are for that specific project with repository included at the end of the lesson. That's it for this time and see you guys in other videos.