Backend Policy Authorization0:00
In the last day or so, I've been tinkering with some different approaches for policy authorization on the back end, but also the front end, and trying to find a way to remove duplication there. Here's an example. If I switch over to Sublime, this is a simple Post that I've whipped up, and you'll see I do have a policy that determines, well, do you have permission to delete the post and update the post? So we want to use this logic to determine whether or not you see these buttons. Okay, so if we're doing this entirely on the server side, it's really no big problem. For example, you can update a post if the person who created the post is equal to the
Okay, so if we're doing this entirely on the server side, it's really no big problem. For example, you can update a Post if the User who created the Post is equal to the User who is currently signed in. Now, if we visit that file, so what you're seeing here is this page, and I'm going to find the edit link. All right, well, if you can update the Post, then we will show this button. Okay, so now if we come back, give it a refresh, I no longer see that edit button because I am not currently signed in, so we do not have authorization. However, if I go to my routes/web.php file, let's just artificially sign somebody in. We'll say the person who created that Post has an ID of 1, so we'll just say log the
However, if I go to my routes/web.php file, let's just artificially sign somebody in. We'll say the person who created that Post has an ID of 1, so we'll just say log the person with an ID of 1 in, just real quick. Okay, so now if I give it a refresh, they are signed in, which means when we run this method, it returns true, and you now have authorization. And the exact same thing will be true for the delete link. So we could say, well, if you have permission to destroy the Post, then show the destroy button. And again, this would be, you know, a form in real life, or at least it would fire an AJAX request when you click it.
And again, this would be, you know, a form in real life, or at least it would fire an AJAX request when you click it. So if we come back, we'll say you can delete the Post. Maybe only an Administrator has permission. We're going to be very strict here. You can be a Contributor where you can update the Post, but you can't delete it. So we'll say if our Administrator has an ID of 1, maybe this is our simple system for an admin. ID of 1 is the admin, nobody else. So now if we give it a refresh, because we're signed in as that person, we do have permission.
ID of 1 is the admin, nobody else. So now if we give it a refresh, because we're signed in as that person, we do have permission. But if it were somebody else, we don't see the button. OK. So this is basic backend authorization 101. And by the way, you might want to say here, well, if the userId is 1, once again, that is our logic for an administrator. So you could do it like this, or you can add a before method, and then this will fire before we even hit these two authorization methods. So this would give you a single place to say, like, is the user an admin, or again, I'm
we even hit these two authorization methods. So this would give you a single place to say, like, is the User an admin, or again, I'm just doing it like this to be quick, then you instantly are authorized. So that would allow you to clean this up and then remove the duplicate checks, and we would still get the same thing. OK. So this is all well and good, but it gets a bit more tricky once you have to introduce frontend authorization. So for example, let's imagine that we decide, OK, this actually should be a view component. OK.
Authorization in Vue Components3:09
So for example, let's imagine that we decide, OK, this actually should be a view component. OK. Well now, we can't rely on backend authorization like this. The can directive doesn't work within a view component, of course. So again, let's just try it out and see what happens. I'm going to remove that, and you'll see in my resources/components directory, I've set up a new blog post file. So within the template, I will paste in what we had before, and then presumably as a prop, we will accept the post, and then we'll update accordingly. So maybe v-text or v-html would be post.title, and then this would be post.body.
we will accept the post, and then we'll update accordingly. So maybe vtext or vhtml would be post.title, and then this would be post.body. And then, yeah, we can't use the can directive, so I'll have to get rid of those temporarily until we think of something else. But this is what we might end up with. OK. So now, if we come back, we can say, all right, well, I want to show a blog post like so. All right. Let's come back to Chrome. I'm going to give it a refresh, and we still get the exact same thing.
Pass Can Props4:11
Let's come back to Chrome. I'm going to give it a refresh, and we still get the exact same thing. But now, how do we figure out the authorization when we are instead rendering this with JavaScript? And this is where everybody seems to do it a little bit differently. So what some people will do is pass through the permission as a prop, something like this. canUpdate. And yeah, they'll do something like, if the user can update the given post. But to be safe, you might want to export that or json_encode it. So you could do this right here, or you can use the helpful json directive. OK.
So you could do this right here, or you can use the helpful JSON directive. OK. So yeah, that would be one option. And then if you come back, you could say, accept that as a prop, and then conditionally render the edit button based upon whether or not you can update the Post. OK. So now, we're still calculating it on the server side. We're not duplicating anything. We simply pass it through. So if we come back, give it a refresh, open up Chrome DevTools, and you will see right
Embed Authorizations in JSON5:31
I do find, though, at some point, it gets a little clunky. It just sort of depends on the context. But if you're reaching for this over and over, it can get a little bit clunky, I think. Nonetheless, that would be option one. Here's option two, though. If I go to my routes file, let's just return the post, and it will be cast to JSON. So if I give this a refresh, here's what effectively is being sent to our view component. Now what if we could determine the authorizations on the back end once again, but then we pass it through as part of this JSON here. Let's see what that might look like.
it through as part of this JSON here. Let's see what that might look like. OK. Well, if I go to my Post model, now there are two ways that we could format this, and I'll show you both. So let's say you just want to show the authorizations as a prop. So you might do getAuthorizationsAttribute, or alternatively, if you want it to be called can or permissions, anything you want. OK. And to start, I'll just say something like this, update, these are the hard-coded permissions
OK. And to start, I'll just say something like this, update, these are the hard-coded permissions that you have. OK. Well, if I want this included as part of the JSON output that you see here, again, there's two options. One would be to leverage the appends property here. So this would be an array of custom accessors that you want included as part of the JSON casting. So now if I come back and give it a refresh, you'll see that's included there.
OK. So let's see. Here, I'll show you a couple ways again. We could use the Gate facade, and we could say Gate::allows for an update of the current Post. And then we'll just do the same thing but for destroy. OK. So now we're at least calculating it dynamically. So if I come back and give it a refresh, it looks like in this particular instance, we don't have permission to do either.
method where you accept the abilities you want to check. And then let's see if we can figure this out. We will collect those abilities. So we basically want to allow the user to say, give me the authorizations for update. And maybe those are the only ones I want to check. OK. So if we did that, we would collect the abilities, and then map over them. This might take a second. Let's see. map over the ability, and we want to return a check.
Let's see. Map over the ability, and we want to return a check. So Gate allows, and ability would be update or create or destroy, and then we would pass through the current instance. OK. So then, I'm going to show you that second option here. You can always override the toArray method. I think sometimes this can be a bit more clean, because you're not hunting around a model to figure out exactly what the JSON will look like. It's all being defined in a single point.
model to figure out exactly what the JSON will look like. It's all being defined in a single point. And if you don't like, like if you don't want to do this, where you say title is this title and body, and you're just repeating all of the properties, if you only want to append to it, you could say, well, OK, parent::toArray, and then we're going to tack on anything else that we need here. So for example, if I give that a refresh, you can see how that works. OK. So here, we could say, authorizations, is this authorizations? And yeah, here, you could specify what things you're checking for.
So here, we could say, authorizations, is this authorizations? And yeah, here, you could specify what things you're checking for. So let's come back. I think we'll have to tweak this, right? Let's see. Come back, refresh. Yeah, and that's because we're assuming the values. So if ability says update, we actually want that to be the key. So let's say array_flip to turn that into the key. So now update is the key, which means the index is now the value.
So let's say array_flip to turn that into the key. So now update is the key, which means the index is now the value. I think that's right. Come back, give it a refresh. There we go. So if we want to check only the things we're interested in, we could do so like that. All right. So of course, this is very quick and dirty and primitive, but you could then extract that to a trait to clean things up a bit. Now, the benefit to this approach is now you're no longer explicit with your authorizations.
that to a trait to clean things up a bit. Now, the benefit to this approach is now you're no longer explicit with your authorizations. So I can remove that entirely and get rid of it here. All right. And then finally, where we pass it through. I no longer have to deal with that, which is, again, a little bit clunky. OK. Now, we could change this to something like this. And then here, we would say only show this if post.authorizations.update.
And then here, we would say only show this if post.authorizations.update. OK. So let's remove that, come back, give it a refresh, and again, it's working. But let's change who signed in, refresh, and now we don't have permission to update the post, so we no longer see the button. So again, if you take a look here, blog post, those authorizations are now being passed here. And don't forget, they are specific to the user. That would be one issue. I think you pay a small penalty in terms of caching.
User Permissions and Mixins11:27
Option three, I was talking to Taylor Otwell about this, and here's what he roughly said. I think I'll be 75% of the way. I didn't see an example, but I think this is basically what he would do. So on your User model, or if you don't want to do User, it could even be on a pivot table to be more specific. Anywhere though where you have a link to a given User, you could store their, I was using that term authorizations, but he uses permissions, and we'll make that nullable. So here, he would store what permissions you have. And this would be a stringified version of things like, it could be updatePost or whatever convention you prefer.
And this would be a stringified version of things like, it could be update Post or whatever convention you prefer. Post update, post destroy. He used the example server create. You get the idea, right? So now, if we want to access that, if I go to my User model, scroll down here, we'll say permissions should be treated as a JSON type. So now, take a look at this. If I boot up php artisan tinker, and I'm going to create a new User, all right? So here, I could say, your permissions will be, you can update the post.
If I boot up php artisan tinker, and I'm going to create a new User, all right? So here, I could say, your permissions will be, you can update the Post. Okay, so now notice, if I save it, and we take a look at the User model, you'll see it's stringified. But if I try to access it, it will correctly be converted. So you get the idea. So now, we have a way to associate a set of permissions with a User record, or again, you could put it on a pivot table, if that would make more sense for you. Maybe on a subscriptions pivot table, and you want to see what permissions you are granted as a result of that subscription.
Maybe on a subscriptions pivot table, and you want to see what permissions you are granted as a result of that subscription. So next, my understanding would be, on every page request, the permissions would be loaded. So something like this. We could say, window.app will be a object, and here we will load the permissions. So this would be giving the authenticated users permissions. And now, if we come back, let's take a look at what we got here. Okay, so we're signed in as that user with an ID of 1, and in this example, they don't have any permissions. But if we sign in as the user with an ID of 3, let's go to Routes for Web, we'll update.
have any permissions. But if we sign in as the User with an ID of 3, let's go to Routes/web.php, we'll update this. All right, refresh. Now we can see the basic permissions that are afforded to this User. Okay, so next, what we could do is on our view component, if we want to check, we would do something like this, v-if, and then let's add a little helper method, like updatePost. If you can update the current post, or moderate posts, or delete form threads, you know, whatever you're checking there, then show the button. Here we would say, canAbility, and then we're just going to look into our app object.
you're checking there, then show the button. Here we would say canAbility, and then we're just going to look into our app object. So permissions.includes the ability, yeah, okay. So that's kind of nice, if we can update the Post. So let's come back, give it a refresh, and we now see that button. And I think that's the basic approach. And what we could even do is extend it. So yes, maybe we look into that user permissions column, but maybe also we can have additional checks, like if you are an administrator, you instantly gain access, or if you are the creator of the Post, then you instantly have access.
checks, like if you are an Administrator, you instantly gain access, or if you are the creator of the Post, then you instantly have access. But then beyond that, we'll just look into your roles and your permissions to see if you, no matter what the foreign key is, if you have permission in the database, then you get to see this button here. Anyways, I think that's basically, he might have something a little more fancy going on. This might be, I think he said this would be a mixin, so you would have something like this, import, authorizable, I'm kind of mixing terminology here. Something like this though. All right, and then we would have our mixins, this way you would keep from having to repeat
Something like this though. All right, and then we would have our mixins, this way you would keep from having to repeat yourself. And then for any view component that uses this mixin, you can immediately reference a can method. And again, that method is just going to look onto your app global object. So we would take all of that, and then resources/js/mixins/authorizable.js, paste that in. Okay, so now if I come back, yeah, I think this would be another way. We extract it to a mixin that we can pull in to any view component that needs it. Or if you really wanted to, you could make it a global mixin.
We extract it to a mixin that we can pull in to any view component that needs it. Or if you really wanted to, you could make it a global mixin. Yeah. So come back here, oh and by the way, here, I just saw, if you're not signed in, that returns null. So make sure you're not calling a permissions property on null. So make sure this is optional, or you only run this check if the User is currently signed in. So yeah, you could use any of these approaches, or even a mixture of them. So you could delegate to your policy, but then beyond that, look into the permissions column on the database for kind of a catch all.
