A masterclass in architectural pragmatism that elegantly balances frontend performance with database-level integrity. It provides a definitive blueprint for building secure, multi-tenant systems without the usual technical debt.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
Everything you need to know about Permissions in 12 minsAdded:
Your app could land at the top of Hacker News or could land at the top of Hacker News. And this comes down to the permission system you choose to enforce who has access to what. Now, nine times out of 10, I reach for this one here.
But we need to cover a couple of other concepts to get there. So, let's start out with number one, which is Actually, let's see if you already know the name just based off the explanation. So, this is a pattern where we have one database with multiple tables, but each user only has access to their slice of data. As a more concrete example, we have this Vibe slopped Linear clone, which we're going to use to build out permissions for this video. If you don't know Linear, that's fine. This is basically just a to-do app where we have a bunch of tasks with different statuses. But currently, I can see not just my tasks, but everyone's tasks. So, the tables in the database might look something like this, where we have tasks and users. Maybe we also have some other tables like projects and comments. But currently, every user can see every other user's tasks, user info, projects, comments. But really, when this John user queries the database, they should only get back the John rows from each of these tables. Then, when the Sam user queries each of these tables, they just get back the Sam data.
And this is called mult Mhm. multitenancy, where we have multiple users or tenants using the same database. So, each of these tables could have a column that identifies that specific user who owns it or should have access. So, in this case, each task has a user ID, each project has a user ID, each comment has a user ID. And then we update our query to just select the tasks for the currently signed-in user.
And now, our application shows just John's to-do list items. And we can move this one over to done. But what's to stop the next dev who works on this just selecting all of the tasks? Or maybe they remember to do that for most of the tables, but forget one. We probably want to restrict access to that data from the database itself. And that's why multi-tenancy goes hand-in-hand with our next concept, row-level security or RLS.
So, currently, our tables are completely open. They'll allow anyone to send a query to read or even worse, write data.
But, if we enable row-level security on each of our tables, this behavior is reversed. It will automatically deny any requests to select, insert, update, or delete any of the data in any of these tables. So, if we want to allow any of those actions for any of our users, we need to write a policy to enable it. So, in this case, we want a policy for users to be able to read their tasks. So, if the user ID column of that specific task matches the user sending this query, then they're allowed to select that specific row. If the user ID column is different to the currently signed-in user, this row will continue being excluded from the result set. So, from the perspective of our application, nothing's changed. The currently signed-in user is only able to see the tasks that they own. But, the difference here is that with RLS, if one of our devs were to forget to add this where clause when selecting the tasks, it doesn't actually affect what the user sees. Our RLS policy is only allowing them access to tasks that they own.
Therefore, the same set of tasks gets returned for this specific user when they query all tasks. But, a quick performance thing I want to call out here is we shouldn't be relying on RLS to do that filtering for our queries because this expression needs to be evaluated for every single row the user is trying to select. And that could be expensive. So, anything we can do to reduce the number of rows we're trying to select, like adding that where clause to our select query, means this expression is only being evaluated for that subset of rows rather than the entire table. But, from a security perspective, forgetting to add that where clause won't expose data that the user shouldn't have access to, unless you've written a bad RLS policy, and then you're on your own. But, let's say I now want to share this task with my boss, so they know what I'm working on.
And I'm actually working on this one with one of the designers, and they want to add some mock-ups for me to review before they keep working on it. So, we're going to need a more advanced permission system than just this user owns this task. And for that, let's talk about role-based access control, or RBAC. This is where we have the data that we own, but maybe someone else has granted us read access to some of their tasks. And maybe we want to collaborate on others, so they've given us write access. For this, our schema gets a little more complex. We're going to create a new enum or type for role. So, we'll have roles for owner, viewer, and editor. We also want a permission enum with values for tasks.read, write, create, and delete. We then create a permissions table that maps one of those roles to a type of permission.
So, a viewer is only able to read tasks.
So, we'll give them the tasks.read permission, an editor has the tasks.read and tasks.write permission, and an owner can read and write tasks, but can also create or delete them. We then have a task roles table, where we say, "For this specific task, this specific user has this specific role." So, we remove the user_id column from each of our tables, and instead create a row for each of those users in the task roles table that maps that specific user to the task they were the owner of. And then, to stitch this all together, we have this rather intimidating database function that checks, "Is this specific user authorized to use this specific permission for this specific task?"
Really it's just doing two simple things in a kind of scary looking way. This bit looks up the role that the user has for that specific task, and this bit checks if that role maps to the permission being requested. So if John is asking to read the teach our back task, then we look up what role does John have on the teach our back task, and we see he is the owner of that task. So then we look up, does the owner role have the permission being requested, which is to read a task, so the tasks.read permission, and look at that, the owner role does have the tasks.read permission, so John is allowed to read the teach our back task. If Tyler asked to read the teach our back task, then he could because he has the role of viewer, and that maps to the tasks.read permission. But if he wanted to edit the task, this uses the tasks.write permission, which the viewer role doesn't have, so Tyler cannot edit the task, only read it. If Sam tried to read or write the teach our back task, well, he doesn't have a role for this task, so he can't read or write. Poor Sam. So since this function returns a simple true or false for whether the user requesting the task has the permission to do the thing, we can use this to greatly simplify our RLS policies and make them much more readable by enabling the select action with an expression that calls the authorized task function, passing in the ID of the user making this request, the tasks.read permission, and the ID for the task they're currently trying to select. Then the same for insert, but passing in the tasks.create permission, update with the tasks.write permission, and delete with the tasks.delete permission. So that may have seemed rather complicated, but really it was just setting up all the plumbing bits to make everything else simple. So let's speed run adding permissions for projects. So we have a projects table, tasks can now belong to a project, but they don't have to. We create that joining table that defines a user's specific role for a specific project. We add some values to our permission enum for projects to have read, write, create, and delete. And then we add some permissions to map those roles to project permissions, create an authorized project function that checks if the current user has the right permission for a specific project, and call that for each RLS policy we want to set up for select, insert, update, and delete. And since tasks can now belong to a project, we can create additional RLS policies for tasks that inherit the same permissions as its project. Cuz if you're the admin for a project, doesn't really make sense if you can't read the tasks that belong to that project. And the same for insert, update, and delete. And back over in our application, we can see any tasks where we are set as the owner. We can see tasks where we are the owner of the project they belong to. We can see other people's tasks when they've added us as an editor, or tasks where they've added us as a viewer. But we probably want our application to know about each of these roles that we have for each of these tasks, so that we could conditionally render an edit button if we have write permissions, maybe a trash can if we're allowed to delete it, and a read-only label if we only have read access to that task. Which brings us to our last concept, JWT claims. So back over in our application, we can use this auth.getClaims function to pull a list of the user's roles for projects and tasks out of their JWT, which we'll set up shortly. We can then do some JavaScript gymnastics to determine whether the user is a viewer, editor, or owner of any of the tasks, then use those properties to conditionally render our UI. But, to get these permissions into the JWT, we need to set up auth hooks. So, when the user is issued a new JWT, either by signing in or refreshing their session, we can call a function to add values or claims to the JWT. So, this function looks up all the project and task permissions for this specific user and adds them as claims on the JWT. Then, in our application, we get this blob of permissions when we call the get claims function, which we use to conditionally render our UI. Now, this auth hook only gets triggered when the user signs in or if their expired session is refreshed.
So, let's log our John user out and then back in, and now they can see both the edit and delete buttons. But, if we head over to the database and change their role from owner to viewer, even if we refresh, our John user can still see those buttons. And this demonstrates one of the gotchas that people often experience when they're using this kind of pattern, where the JWT claims are essentially cached by the application until the user's session is refreshed.
And this feels like a big problem because we just took away their admin access. So, why do they still have admin access? Well, they don't because our RLS policy uses the authorized task function, which checks the permission they currently have in the database rather than blindly trusting the cached value in the JWT. We actually do have access to that JWT from the authorized function, and it would be more performant to use this rather than doing a table lookup. But, then we're trusting that potentially stale value, meaning they would actually have permissions for a role that may have been revoked. So, while it's slightly less performant, it's definitely safer to do this extra table lookup. So, John may be seeing this slightly stale UI, but he can't actually delete this task.
And if we sign out and back in, the edit and delete buttons are gone. Yay! And now that we've got our permissions locked down, it's time to make our database more performant. But for that, you want to check out this video right here. Sam takes us through how Shopify's queries ground to a halt because they were using limit rather than a cursor.
But until next time, keep building cool stuff.
Related Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 viewsโข2026-05-28
How agent o11y differs from traditional o11y โ Phil Hetzel, Braintrust
aiDotEngineer
450 viewsโข2026-05-28
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation๐ฏโ
LearnwithSahera
1K viewsโข2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 viewsโข2026-05-29
Search Algorithms Explained in 60 Seconds! ๐ค๐จ
samarthtuliofficial
218 viewsโข2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 viewsโข2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 viewsโข2026-05-29
So What's Odin Lang Even Good For
TechOverTea
131 viewsโข2026-06-01











