|

How To Protect Specific Routes With Middleware

How To Protect Specific Routes With Middleware

We have completed setting up a user registration system which allows new users to register and log in, as well as for existing users to log in and log out. This means we have two types of users now of our site. A user is either a guest, or is a registered user. It makes sense then that guest users should have access to certain areas of the site, and be limited to other sections of the site. The same goes for a registered user. He or she should have access to some areas of the site, but not others. This is the perfect scenario for implementing Middleware in Laravel, and that is exactly what we will set up in this tutorial.


Guests should not be able to submit games

We now have a user registration system in place on our games application. It makes sense then, that now we should only allow registered users to be able to submit new games to the site. In fact since we set up the association between Users in the system to specific Games and Reviews, our store() logic is going to expect that we will provide a user_id when we try to submit a game. If this is not provided, the attempt to insert a new Game will cause an exception to be thrown such as “SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘user_id’ cannot be null”. As we can see, guests can visit the /games/create route with ease as it stands now. Even if we provide no link to that route, there is nothing stopping a user from simply typing it into their web browser. Notice we even see the Log In link, indicating our guest status.
guests should not be able to see submit form

We don’t provide a link to /games/create for guests, but they could simply type it into the browser, like we see above. We can use middleware to prevent them from being able to do this. Since we want to protect something that has to do with a Games resource, we open up the GamesController and apply our middleware like so:


app/Http/Controllers/GamesController.php


Route [login] not defined.

If we now try to visit the /games/create route as a guest user, we see a problem.
route login is not defined


How to name a route

You’ll be happy to know, it is easy to fix Route not defined errors. It turns out that the auth middleware is redirecting non authenticated users to a named route. So far, we do not have any named routes in our application. Let us see how easy it is to define our login route as the named route, login.


routes/web.php

Once our route is named as ‘login’, if we then try to visit ‘games/create’ as a guest by typing it into the browser, we will now in fact be redirected successfully to our login page.
non authenticated users redirected to login page


Setting Up Middleware in the Constructor

An auth middleware is now in place on our Games Controller by way of setting it up in a constructor function. What this means is that every single method in this controller is now protected by the auth middleware. This means guest users can not even visit /games to list all games or even visit a specific game such as visiting /games/3. They will be redirected right to the login page if they try to do something like this. Do we really want that behavior though? Maybe you do for your application, and if this is what you want, all you need to do is add that $this->middleware(‘auth’); line to the constructor function, and your resources will be locked down tighter than a banjo string.


Limiting methods affected by Middleware

In our case, we don’t really want to lock down every single method in our Games Controller. We’d prefer to let guest users still be able to make use of the website in some capacity. Let’s let users at least visit the games page and look at specific games. To limit which methods get middleware applied to them, we can use the except() method like so:


app/Http/Controllers/GamesController.php

This should allow us to view games and look at a specific game, but if we try to type in the route to submit a new game as a guest, we’ll get redirected right back to the login page. Let’s see this in action right here.
guests can view games


Guests should not be able to add game reviews

Let’s see if the same problem will creep up on us if we try to add a game review as a guest. Will we also get that spooky error message of “SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘user_id’ cannot be null”? I’m betting we will.
SQLSTATE23000 Integrity constraint violation 1048 Column user_id cannot be null

Can we fix this? Of course we can! All we need to do is to add the same type of auth middleware to the Reviews Controller like we did with the Games Controller. We’ll also make use of that except() method and pass an array of two strings, index and show, to indicate that we want to protect all methods from guest users except for the index() and show() methods. We can let guest users look at the list of reviews or look at a specific review, no problem.


app/Http/Controllers/ReviewsController.php

With our update to the Reviews Controller and the addition of middleware in the constructor function, as a guest we should now be able to list all reviews and also look at a specific view, but if we try to visit the route to add a review – our new middleware should say “oh no, you head over to the login page before you try adding a review”. Let’s see.
auth middleware in action


Authenticated users should not see a login form

Protecting routes from guest users was pretty easy as we saw. What about for logged in users? Are there any routes that perhaps a logged in user should not really be visiting? How about the /register route? If a user is already logged into the system, they should not be able to type the /register route into their browser and see a register form. The same goes for the /login route. A logged in user should not be able to visit the /login route and see a login form. As it stands now, that is exactly what they can do. We will log in as Mario, and see if this is the case.
logged in user still sees login form

Whereas for our Games and Reviews Controllers we made use of the $this->middleware(‘auth’) middleware, when dealing with login and registration routes, we will make use of the $this->middleware(‘guest’) middleware. The auth version is basically stating, a user can only access this resource if he or she is authenticated. Quite the opposite is the case for the guest middleware. It states that a user can only access particular resources if they are a guest. Sounds great! Let’s go ahead and apply that guest middleware to our Sessions Controller so that if a logged in user tries to visit the login form, they will be redirected.

Once again, let’s remember that if we apply a middleware to a Controller in the constructor, it is going to affect every single method in that controller. Most times you are going to want to make use of either the except() method or the only() method to limit which controller methods should be affected by middleware. In this case here, we know that a guest should be able to both view a login form, and establish a new login session via the create() and store() methods. It wouldn’t make sense for a guest however to access the destroy() method since a guest can not log out, if they are not logged in. So we remove destroy() from getting the middlware treatment by passing that string to the except() method. Ok, with this in place let’s see what happens.

We get an error of: NotFoundHttpException in RouteCollection.php line 179:
laravel guest middleware redirect home


How to change guest middleware redirect

We see that we got an error relating to a route not found or some such thing. When we were dealing with the auth example of middleware, we corrected this by setting up a named route. In this case of using the guest middleware, we can take a different approach. The guest middleware makes user of the RedirectIfAuthenticated class. That class is set up to redirect the user to /home via return redirect(‘/home’); We can consider the /games route more of our home in this application. So all we need to do is update that RedirectIfAuthenticated class like we see below, and all should work.


app/Http/Middleware/RedirectIfAuthenticated.php

With that small change, if we try to type in the url of our login form at /login as an authenticated user, we get redirected right to the /games page. This is the exact behavior we were looking for.
guest middleware custom redirect


Authenticated users should not see a Registration form

The last thing we need to cover is applying some middleware to our RegistrationController. The RegistrationController does two things. It offers a form to create a new user via the create() method, and it stores that new user in the database via the store() method. Should a logged in user be able to visit the registration form and try to log in a new user? Of course not, that doesn’t really make sense. We can fix that however by applying the guest middleware. First let’s confirm that it is not working as we like just yet.
logged in user can still see registration form

It looks like that is the case. We can now add our guest middleware to the RegistrationController to fix this.


app/Http/Controllers/RegistrationController.php

If we simply refresh that /register route, we should be redirected right back to our /games page.
redirect for logged in user register form


How To Protect Specific Routes With Middleware Summary

That should just about cover it for using middleware in Laravel to protect certain routes of your application based on their authentication status. No longer can a guest try to submit a new game or review, and then have the application throw an error because there is no user_id. In addition, we took care of logged in users being redirected if they accidentally or purposefully try to type in the /login or /register routes. We can apply middleware to the methods in our controllers by setting it up in the constructor function. We also saw how we can limit which methods the middleware is applied to with the except() or only() methods as needed. Great work!