در حال بارگذاری ...

Problem: Guest-only pages0:00

Alright, welcome back, everybody. So yeah, in the last episode, we added support for basic registration. So yeah, if I want to log in Sally at example.com, sure enough, we are signed in, and that has been recorded in the session. So if I right-click and go to Storage, Cookies, sure enough, here is our PHP session cookie. Alright, very cool, but we do have one problem. Think about it. What would happen if I try to visit that registration page again, even though I'm already signed in? Alright, let's see what happens. /register, and yeah, I see the form to register for a new account. So what exactly would happen here if we try to register another person? Yeah, this just gets very strange very quickly. If I have a look at TablePlus, and then if I open the Users table, yeah, this is weird. So we registered an account for Sally, and then while Sally was signed

it works in both directions. Okay, so let's think about this. What are some options for solving this problem? Now, of course, we could tackle this page by page if you want. So for example, if I go into registration/create, yeah, here, we want to say, well, if you're already signed in, there's no need for you to access this page again. So let's disallow it. Yeah, so you might say something like, well, see if we have a User in the session, but assume we don't. And yeah, if we do have a User, then let's get out of here. So let's redirect you to somewhere. So let's send you back to the homepage, and then kill the script or exit, or die, whatever you want. And yeah, that would work. So if I come back, and I try to manually access that register page, notice, it instantly redirects me back to the homepage. And that's what we want. But we once again have that issue of duplication.

Route-level access control2:23

try to manually access that register page, notice, it instantly redirects me back to the homepage. And that's what we want. But we once again have that issue of duplication. So if there are other controller actions that are restricted to guests only, at the moment, I would copy and paste this into every single controller, where it is applicable, which I definitely don't want to do. Okay, so here's another option. What if we tackle it at the route level? Let's see what that might look like. Let's go into my routes file. And yeah, why don't we say, yeah, here we go, register. So what if there was a way to declare when I register this route, that it should be restricted to only guests. And I said only, so why don't we just add a method like only, and then we'll give it some kind of keyword like only, only guests, only guests can access this particular route. Meanwhile, if we came up here,

don't we just add a method like only, and then we'll give it some kind of keyword like only, onlyGuests, onlyGuests can access this particular route. Meanwhile, if we came up here, maybe something like this, where you access your notes, we could say, well, onlyAuthenticatedUsers can have permission or authorization to access this specific route. Yeah, that feels good. Let's see if we can make it work. And we'll start with this one only. All right, well, it sounds like if we want to call an only method on our router, we need to return to the router and make some tweaks. All right, let's open up CoreRouter.php. And yeah, let's see, here's our class. Maybe right down here, we're going to have a method called only, and this accepts some kind of key. And then let's dd the key just to confirm that we are calling this method, because I think at the moment, none of this is going to work. All right, let's come back, try to visit

Enable chained route methods4:01

And then let's dd the key just to confirm that we are calling this method, because I think at the moment, none of this is going to work. All right, let's come back, try to visit register. And yeah, sure enough, we get a fatal error, call to a member function only on null. All right, so what's going on here? We tried to call an only method, we declared it on the router, but it's not working. All right, let's go back to our routes and have a look. So it sounds like we're calling a get method. And then we call an only method on whatever is returned from the get method. All right, well, let's have a look at the get method. The get method doesn't return anything at all. So that is why we are getting the error, because null is returned. Okay, so let's do this. We're calling an add method. The add method adds a new route binding. And then down here at the bottom, why don't we have it return the current instance. And that way, we can continue chaining.

We're calling an add method. The add method adds a new route binding. And then down here at the bottom, why don't we have it return the current instance. And that way, we can continue chaining off of this router object. Okay, so now for all of these verbs, like get, post, put, patch, delete, let's have it return whatever is returned from the add method, which again, is the router object. And if we do that, we should be able to continue chaining off of that router. All right, so if I come back to Firefox and refresh, there we go. Now we're getting our authorization key. Well, really, it's more like a middleware. A middleware is a little higher level than where we currently are. But for now, think of it sort of like a bridge that will take us from an initial request to the core of your application. And that bridge has the ability to really do anything at once. But for now, that bridge is going to authorize the user. So again, are you a guest? Are you signed

Store middleware on routes6:31

So if I switch back, it's sort of like I need to say, okay, well, go grab the most recently appended route here, and then we need to associate this middleware with it. Okay, so let's do this. Why don't we say by default, every route has a middleware, but it's set to null. There is no middleware by default for this application. However, if we call only, we will change that. So what we could do and what we basically want to do is grab the routes array, figure out whatever the last item is within it, and then update its middleware key to be equal to whatever the key that I passed in is. Okay, so now how do we grab the last item? There's a bunch of ways. In recent versions of PHP, we could say, what is it, array_key_last. This will give me the key for the last item within the routes array. There's really a bunch of ways to do it. There might even be a newer way in PHP 8 to make that a little easier, but I think this will work for

for the last item within the routes array. There's really a bunch of ways to do it. There might even be a newer way in PHP 8 to make that a little easier, but I think this will work for now. Okay, so I think that should do the trick. Why don't we finish up by dying and dumping the full routes array to confirm that this works. So if I come back and give this a refresh, where's register? There we go. So here's our endpoint, and notice that it now has the guest middleware associated with it. So if I go back to routes and we come up here where I said, well, let's say only auth, only authenticated users, come back, refresh, and there we go. So /notes now has an auth middleware key associated with it. Okay, perfect. So now the next step, let's get rid of that and return this so that we could potentially chain further. The next step is, well, when we figure out what the corresponding route is, we need to apply that middleware.

Enforce guest/auth middleware8:21

let's get rid of that and return this so that we could potentially chain further. The next step is, well, when we figure out what the corresponding route is, we need to apply that middleware. So let's do this. When we try to match a route, we check, well, does the URI and does the method match up? If so, then right here, let's apply the middleware. Okay, and I'm going to do this in long form, and then we will condense it. So yeah, we might say something like, well, if the route middleware equals guest, then let's think about it. What do we need to do in this case? Well, we need to say, well, let's confirm that you are a guest. Or we could say, well, if session user, and again, we'll just default that to false, but if there is a user, then you are not allowed to be here. So once again, we would redirect you. So notice I'm taking that exact logic we wrote before, and I'm just throwing it in here, at least for now. We'll clean this up.

now you can see the reason for things like this. Okay, let's bring it back and now implement what's effectively the opposite. So to access the notes endpoint, you have to be signed in. Okay, so let's do this. Let's open up, I don't yet have a logout method, we will tackle that, I think in the next episode, when we build a login form. For now, I'm just going to manually delete the cookie, which will remove that reference. Okay, so now if I visit the notes endpoint, yeah, even though I'm not signed in, I'm able to access a members only section of the website. Okay, so let's fix that. We will come back to our router. And yeah, again, this is a little sloppy, I'm going to clean it up at the end of the video. But I could duplicate this and say, well, if the route middleware is off, then we basically want to do the opposite. So our check in this case would be, well, if there is not a User in the

But I could duplicate this and say, well, if the route middleware is off, then we basically want to do the opposite. So our check in this case would be, well, if there is not a User in the session, and again, we will assume that's false. In that case, we will once again redirect you, we should probably update the status code as well. But for now, it's okay. So let's come back and give it a refresh. And there we go, we no longer can access the notes endpoint because we aren't signed in. But if we do, well, let's register some brand new person. All right, now we try to access it as a member. And it works exactly what we would expect here. Okay, but now as you can imagine, this gets pretty sloppy pretty quickly. So instead, what if the handlers for each of these middleware could be stored within their own file? All right, let's give that a shot. Why don't we to start, where could we put this? Let's just put it in core for now. Let's add a

Extract middleware into classes11:47

middleware could be stored within their own file? All right, let's give that a shot. Why don't we to start, where could we put this? Let's just put it in core for now. Let's add a directory called middleware. And one of them will be a class called Auth. This is our Auth middleware. All right. And what's our namespace here? It's Core Middleware. All right. Let's duplicate it. And then we'll have another one for Guest. Or again, you can name these whatever you want. If you want it to be GuestsOnly, that can be your class. Remember, there's no rules here, name it whatever you want. Okay. So how should we do this? Each of these will have a handle method. So notice how I'm updating each one. That sort of means they're conforming to a similar contract. And that contract states that each of them provides a handle method that can be called to determine and evaluate whether the request can further continue to the core of your application.

And that contract states that each of them provides a handle method that can be called to determine and evaluate whether the request can further continue to the core of your application. Okay, cool. So let's do this. Let's open up a split, return to my router. And in my auth check, I'm going to grab that, cut it, and move it in here. So I'm just moving, I'm extracting this logic into a dedicated file like so. All right. Next, let's go into guest. And I'll do the exact same thing right here. Okay. So now, incrementally, what I could do is say, well, if we have a guest middleware, then let's instantiate that Guest class and then call a handle method on it. Yep. And then we'll do the exact same thing here, new AuthMiddleware, and then call handle. So if I come back and we try this again, yes, I'm signed in, I can access the notes endpoint, but I cannot access the register endpoint. All of that is working just the same. Okay. But next, again, as you can imagine, if we later add

Map keys to middleware13:40

again, yes, I'm signed in, I can access the notes endpoint, but I cannot access the register endpoint. All of that is working just the same. Okay. But next, again, as you can imagine, if we later add some middleware, I don't know, to access this route, you have to confirm your email address or something like that. So maybe we'll say emailConfirmed or something like that. And that would hit a confirmedEmail middleware. Again, just notice how every time I add a new route, I have to return to the router, do a check. It's just not necessary, I guess is what I'm saying. So instead, what if we could simplify this a little bit? Hmm. It sounds like, if you think about it, we're associating a key with a corresponding middleware class. So the guest key points to the Guest class. The auth key points to the Auth class. So why don't we set up some kind of lookup table? Here's what I'll do. I'm going to add sort of like a parent class called Middleware. And what I'll do here

points to the Auth class. So why don't we set up some kind of lookup table? Here's what I'll do. I'm going to add sort of like a parent class called Middleware. And what I'll do here is create like a map. So I'll call it map even. And we'll say guest points to guest. And auth points to auth. All right. Let's see how that would work now. We'll go back to our router. And now let's comment all of this and replace it with our Middleware class. And do note, whenever I type that in PhpStorm, it's going to import it at the top, just so you don't get confused. But yeah, I could say middlewareMap, and I will pass through the routeMiddleware. All right. So are we on the same page? What's happening here? We are referencing this constant. We are passing through whatever the key was. So if it's guest, that's going to return a path to this corresponding class. If this is instead, you know, if this evaluates to auth,

We are passing through whatever the key was. So if it's guest, that's going to return a path to this corresponding class. If this is instead, you know, if this evaluates to auth, then it's going to return this class path. Okay. So now think about it. I have our middleware here, which is now guest or auth at the moment. Let's instantiate it, or we could even resolve it out of the container that we built if we expand that just a little bit. And that would be cool. But for now, we will instantiate it and then call a handle method. Yeah. So this is a more dynamic automated way to handle what we were manually writing over and over again. Okay. So let's check it. Come back, give it a refresh. And oh, we're getting too cocky here. Undefined array key on line 61. Oh, you know what I think that is? It's probably because most of the time this is null. So maybe we should instead say, well, if we have a middleware registered for this

array key on line 61. Oh, you know what I think that is? It's probably because most of the time this is null. So maybe we should instead say, well, if we have a middleware registered for this route, only on that condition should we do this? And I can get rid of that commented code. Yeah. Maybe that'll work. Let's come back, give it a refresh. And it does. Okay. So once again, I'm signed in, which means I can no longer access this register endpoint. That works as well. And what's even better is now down the line, once I add more middleware, it's a lot easier. I don't have to return to that router class, which is always a good thing. I can instead write new code. Maybe we call it confirmed. That will be something like EmailConfirmed class. And yeah, I add new code and I can instantly start referencing that within my routes file. So for example, I don't have any appropriate place here. Maybe if there's

Add middleware resolver17:14

confirmed class. And yeah, I add new code and I can instantly start referencing that within my routes file. So for example, I don't have any appropriate place here. Maybe if there's like a forum and access the forum, you have to have confirmed your email address or something like that. That would be one way to go. Okay. Great. So really, this wasn't that much effort. And it's pretty flexible when you think about it. So now let's just do one more thing. If I come back to my router, I'd like to do just a little bit of cleanup. Um, this is fine. But what if we had some kind of method like resolve right on the middleware class? So that would accept the key. And I'm just sort of tucking away some of this logic. Move it here. And I could say static, which is just late static binding. It's the current instance. Look in the map constant, which can be public. It's probably fine. And then

Move it here. And I could say static, which is just late static binding. It's the current instance. Look in the map constant, which can be public. It's probably fine. And then look for this key. That will give us a class path. And then we call handle on it. Okay. But I still have a couple alarm bells that we need to address. But we'll tackle it in just a minute. So let's say middleware resolve route middleware. Cool. That now hits this method. And the first check will be, well, if we don't have a key at all, then there's nothing to do here. So maybe we can just return early. Next, we find the corresponding middleware class. And we call a handle method on it. Okay. So a couple more issues. But let's make sure this is still working. Incremental refactors. Yeah. I'm signed in. I try to access register. And it disallows it. Great. But, yeah, think about it. What about now if I were to

Handle unknown middleware keys18:59

make sure this is still working. Incremental refactors. Yeah. I'm signed in. I try to access /register. And it disallows it. Great. But, yeah, think about it. What about now if I were to reference, let's just update this to be foobar, some kind of middleware key that we don't know anything about? Let's see what would happen. Access /register. And we get a fatal error, as you would expect, of course. Undefined array key foobar. And then also, class name must be a valid object or a string. So what's happening here is middleware is equal to let's just have a look. null. So we're trying to call handle on null, which we can't do. So let's do this. Let's say if we don't have a corresponding middleware, then that's exceptional behavior. And we don't know what you're trying to do here. So I will throw an exception that says no matching middleware found for key. And then we will add the key here. And then let's add a period. And then let's just

what you're trying to do here. So I will throw an Exception that says no matching middleware found for key. And then we will add the key here. And then let's add a period. And then let's just merge all of this into string interpolation. And maybe even wrap the key within quotes. And then let's see. What am I missing? A brace. Reformat. And, yeah, I think that'll do the trick. So now, if I switch back to Firefox and refresh, we still have that warning. I will deal with that. But, yeah, now notice the error is no matching middleware found for key foobar. And this is what we would want. You're trying to reference a middleware key that our application knows nothing about. So it is appropriate to throw an exception, as we've done here. Okay. Finally, let's just update line 18. And, yeah, right here, we're trying to reference, again, a key that doesn't exist off of this array. So why don't we say if there's no corresponding key in that array, then we can

? If we don't, then let's abort. Otherwise, instantiate it, and then call the corresponding logic. Yeah. So just a little bit of work there. But trust me, things like this will be incredibly useful for all of the applications you build.

MiddlewareRedirect Guests

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟