Lesson Description
The "Migrating UI to ABAC" Lesson is part of the full, Permission Systems that Scale course featured in this preview video. Here's what you'd learn in this lesson:
Kyle updates all remaining UI pages and components to use the new ABAC permission checks, fixes a few missed references to the old helper functions, then wraps up by highlighting the key advantages of the new system including declarative policies, full type safety, and a unified "can" function with no helper functions needed.
Transcript from the "Migrating UI to ABAC" Lesson
[00:00:00]
>> Kyle Cook: For us, let's go ahead and implement the rest of all of our permissions that we have inside of our application and inside of our permissions, I'm just going to scroll up to the top here. I'm specifically going to search for any time we've imported this file because this is the file we're getting rid of and trying to replace. So let's just do a quick search for that, and that represents all the pages where we need to update our permissions.
[00:00:19]
So the very first page that we're on here, this is going to be our project documents page. This is like the view of all of our documents. We'll go over and I just give this a refresh. Well, of course it's not going to work because we have a bunch of errors, but anyway, this is our project view page for all the documents, and we just need to check all of our permissions and use that permission thing.
[00:00:36]
So what we can do is we can say constant permissions is equal to await get user permissions, and we may not even need this user. I'll comment it out for now in case we end up still needing it, but now I can just come in here and say if they can access this data, so project update for our specific projects we've already queried that, then we want to render out that code and this is the new document button, so we'll just replace this with documents and create, and since we don't know what the document they're going to create looks like yet because they haven't typed any data into our form, we can leave this off, and this button will show up if they have permission to create any document at all, even if the document maybe has some restrictions on it, it'll still return true, because we just want to know, can they create some document at all.
[00:01:25]
So now with all that done, it looks like that user section was being used here because we have one more permission. This is again for a document create, so we'll say create documents and we'll get rid of that because again we don't know what the document looks like until they actually go ahead and create the document. Now we don't need the user or this function here, so we can completely get rid of that and again clean up our code slightly as we're going.
[00:01:47]
That's the whole point of this evolution is to slowly make our code a little bit easier to read and work with. Now the next file we're going to be moving to is another UI based file, and this file is specifically for our document details page. So when we go to an individual document page, this is the data being shown to us, and we need to do all the different checks inside of here. So if we scroll down, you can see we have a permission check for update.
[00:02:08]
So let's make sure we actually get our permissions. So that's going to be await get user permissions. We probably don't need our user anymore, so we'll delete that, and now we can use those permissions here. You want to pass it in the documents, and in this case we're doing an update, so we'll pass an update and then we'll pass it the document we're trying to update. If we scroll down, you can see we have delete, so I'll just copy all this code, paste it down to where we're doing our delete, and we'll replace update with delete.
[00:02:39]
As you can see, the pattern to using this works the same no matter how complex or how simple your permission check is. The one line of code you write to check that permission is the same everywhere, which is one of the huge benefits of this. Now it looks like we have just an import that needs to be removed. Let's get rid of that, and now we can move on to the next file we have, which looks like it's another UI based file.
[00:03:00]
This is our new document page. So we can do our permission check, getting our permissions like this, and then we can just check if our permissions allow us to create a document. There we go, and again, we don't actually know at this time what the document looks like. This is before they submit the document, they haven't filled out our form, so we leave that data for our document completely blank because we don't know what the document data will look like.
[00:03:26]
That's all the permissions inside this file, so let's move on to the next file. This one has our edit project page, and again, we're going to be getting the permissions from it, so I'll just copy this code because it's pretty much the same on every single file. We'll go into here, paste that down, and this one is a project and we're updating that project, and we specifically want to check, can they update this specific project, so we're going to pass the project along because we actually know what that project looks like.
[00:03:55]
We'll now move on to our next page because it looks like actually we have one more check inside here. This is for our delete, so we can say permissions can project delete and we'll pass in the individual project itself. That cleaned up all that. So now let's copy this over. Looks like we only got two files left. This is our new project page, so relatively straightforward. We'll just paste in our permission for that.
[00:04:24]
Make sure we get our import. There we go. This is create, and again, we don't know what the project looks like yet, so we leave that completely off just to see can they create any project at all. Finally, we can move on to our very last page that we need to update, and this one is going to involve a little bit more in the way of updates because this is our sidebar that we're updating. This is a UI component.
[00:04:43]
This is a little bit of a Next.js thing, but essentially we're using a client component, so all the code inside of here runs on the client instead of on the server. This is like React specific code. So we need to pass down whether or not they can access this because if we try to access our permission inside of here, we can't because this code needs to run on the server to get our user information.
[00:05:03]
So what we're going to do is we're going to take where we actually use our app sidebar, which is in our layout page here. So this is just the one place we use our sidebar component, and instead of passing down the user, we're just going to pass down a permission that essentially says can they do this thing or not. Now the thing we're checking for is can they create a project, so we can just say can create project and we can pass in a true or false variable for that.
[00:05:26]
So let's get our permission data, just like this, we have our permissions, and now I can just say permissions.can, I can pass it along what permission I want to check, which is a project create, and now I've passed that data in, and if we go into our app sidebar, we can actually use that. So here I can get that can create project that is just going to be a boolean, and now inside of here, instead of doing this permission check manually, I can just say can create project and we essentially remove the permission check entirely from this file.
[00:05:56]
So we've cleaned up this file just a little bit, and if we go back to where we originally passed this in, you can see that we're just passing that permission down to this particular file. The only reason I had to extract this out is because of Next.js specific stuff. This is a server component, which means it runs on the server, and this app sidebar is labeled as a client component, which means it runs on the client, so we don't have access to our database or anything else that we need to check all these permissions.
[00:06:20]
Now if we look, that handles all the different permission checks using our role-based access control. So let's go ahead and actually see if what we've done works properly. So let's go over to our application, we'll give it a quick refresh and we should hopefully see it either works or if we have an error, we'll be able to find that error. Can't resolve permissions slash document. So let's just make sure we search for that cause that looks like I probably just forgot to delete some permissions.
[00:06:42]
So I'll come in here just like that. This is a page here for editing our document, and it looks like we're using that helper function. So again, another reason why those helper functions aren't great is because now I have multiple ways to use permissions and I missed this one because it was using a different way than I expected. So here, we can replace this with our normal permission check, constant permissions is equal to await get user permission, and then we can just use that check, can, whoops.
[00:07:14]
There we go. Check the documents, check that they want to update because this is the edit page, and we'll pass in the document they're trying to update. There we go, that should clean up all the permissions we have. Let's just get rid of all of our imports, come back over here, and now you can see that it is actually showing up. Right now, it looks like there's a problem with getting our documents.
[00:07:30]
Oh, now it's just a caching thing probably, but now you can see we have all of our documents. These ones are showing up as blank temporarily. I think it was just caching stuff. Now they're showing up properly. If we try to create a new document, we'll just test this out. Looks like I have some weird caching stuff going on. I'm just going to restart my Next.js server to see if that's the issue. OK, after much debugging, I realized the whole reason we're getting all these weird issues is because these functions for adding permissions are actually asynchronous for the editor, author, and viewer.
[00:08:00]
So I just need to make sure that I await these so that it actually adds all the permissions we expect these users to have. So now if we just add all that, let's go back to what we were trying to do before we were trying to create a brand new document. When I click on this, we are actually brought properly to the page to create a new document. We can just create a document with random data inside of it, looks like it brought us to that page, we can edit that document and if we just change our data.
[00:08:21]
Let's make it all capital instead, you can see it is now capitalized, so we are having all of our permissions just like we expect. If we were to go to an admin user, we should see that they have much more permissions, they can pretty much do everything. They could take that document that we just created and delete it, and it looks like everything is working fine. So we have essentially a basic attribute-based access control system set up in our application, and let's talk a little bit about some of the pros and cons of this system versus what we had before.
[00:08:48]
One of the big pros is that we no longer have any of those helper functions and everything is unified in this one single can function. We mostly had that with role-based access control, but as we started to add more complexity, we had to implement those helper functions, and this essentially completely removes the need for that. Also, everything is incredibly declarative. If we go and look back at our code inside of here, and we just open one of these up, if you look at the actual code for what's going on, it's very clear to see, OK, I need to figure out what my user can do, I can read exactly what these conditions are, and it'll pretty much explain what's happening inside the code, so it's much more declarative than what we had before.
[00:09:24]
Also, we have full type safety. By doing all that complicated TypeScript work at the beginning, which definitely wasn't very fun, but by doing that, we allow ourselves to have type safety when we're using this system, and if we change something, we're going to get errors immediately where they happen. It's also much more composable, it's easy for us to add a brand new condition because instead of adding a new string and then a new helper function and then updating our code everywhere, if we want to change what it is that allows people to do things, we can just come in here, modify one condition, and now our code is going to propagate and work properly with all those different changes that happen.
[00:09:56]
We also were able to optimize our database. This comes from our caching, so depending on what language or framework you're using, you may have your own way of caching, but by caching this information on each request, we're making sure we're not overly fetching data when we don't really need to. This will make sure we fetch the data once and no more. Lastly, we also have all of our policies being correctly limited, so now when a user tries to create a document, it's checking that project ID before it creates the document, while before we weren't even doing that, and that's a really easy bug to creep into your system that you may not even realize it.
[00:10:29]
Now if you want to have the exact same code I do at this point, the branch we're going to be working on is 6-ABAC-Basic, because this is just the basic version of attribute-based access control. Live I will push up my current.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops