Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 67

Route Protection with Authentication

Recommended resources

Hello! Welcome back to the capstone module.

In our last lesson, you did an excellent job hardening our application by implementing crucial security measures against SQL Injection, Cross-Site Scripting (XSS), and Cross-Site Request Forgery (CSRF). Our individual actions are now much more secure.

However, if you look at our PostController, you'll notice a pattern: several methods begin with a check like if (!isset($_SESSION['user_id'])). This is a repetitive and error-prone way to handle authentication. Forget to add it to a new protected route, and you've created a security hole.

Today, we will address this by implementing a professional, scalable solution. This lesson covers the learning outcome: Protect application routes based on user authentication status. We'll use a powerful design pattern called "middleware" to centralize our authentication checks, cleaning up our controllers and making our application's access control robust and easy to manage.

1. The Core Logic of Route Protection

At its heart, protecting a route is a simple conditional check. Before allowing a user to see a page or perform an action, we ask: "Is this user authenticated?" Based on the answer, we either grant access or redirect them.

Route Protection Flowchart
This flowchart illustrates the fundamental logic. A request comes in for a route. The system checks for authentication. If the user is authenticated, access is granted. If not, they are redirected, typically to a login page.

The problem isn't the logic itself, but where and how we apply it. Sprinkling this check throughout our controllers violates the Don't Repeat Yourself (DRY) principle and makes the code harder to maintain.

The video below demonstrates this exact problem and introduces the need for a better approach.

PHP For Beginners, Ep 39 - Write Your First Middleware

In 'PHP For Beginners, Ep 39 - Write Your First Middleware', the instructor from Laracasts starts by showing the issue with manually checking for authentication in every controller.

Watch the first three minutes of the video (00:00 to 02:49). Pay attention to how he first identifies the problem (being able to visit the register page while logged in) and then implements a quick but flawed solution inside a controller, highlighting the problem of code duplication.


2. The Middleware Pattern

The professional solution to this problem is middleware. A middleware is a piece of code that filters HTTP requests entering your application. It sits between the initial request and the target controller, acting as a series of gates or checkpoints.

For our application, we need two main types of middleware:

  1. Authentication (auth) Middleware: Ensures that only logged-in users can access certain routes (e.g., creating a post, viewing a dashboard).
  2. Guest Middleware: Ensures that only non-logged-in users can access certain routes (e.g., the login and registration pages).

The following video provides an excellent, practical walkthrough of creating and using these two middleware types.

Session Based Authentication - Session Hijacking & Fixation - Build Expense Tracker App With PHP 8

The video 'Session Based Authentication' from the channel Program With Gio demonstrates a complete implementation of auth and guest middleware.

Please watch these two segments: Auth Middleware (01:14 - 03:58): Observe how an AuthMiddleware class is created. The core logic is simple: if the user is not in the session, create a redirect response. See how this middleware is then attached to a protected route. Guest Middleware (15:38 - 16:54): Watch how a GuestMiddleware is created with the opposite logic: if a user is logged in, they are redirected away from pages like login/register to the main dashboard.


3. Implementing Middleware in Our Project

Now, let's apply this pattern to our capstone project. We'll create the middleware classes and then hook them into our application's routing mechanism.

Step 1: Create the Middleware Classes

First, create a new directory: app/Middleware. Inside this directory, create two files.

app/Middleware/AuthMiddleware.php
This middleware will protect routes that require a logged-in user.

<?php

namespace App\Middleware;

class AuthMiddleware
{
    public function handle()
    {
        // If the user_id is not set in the session, they are not logged in.
        if (!isset($_SESSION['user_id'])) {
            // Redirect to the login page and stop script execution.
            header('Location: /login');
            exit();
        }
    }
}

app/Middleware/GuestMiddleware.php
This middleware will protect routes that are only for visitors.

<?php

namespace App\Middleware;

class GuestMiddleware
{
    public function handle()
    {
        // If the user_id IS set in the session, they are already logged in.
        if (isset($_SESSION['user_id'])) {
            // Redirect to the main posts page (or a dashboard) and stop execution.
            header('Location: /posts');
            exit();
        }
    }
}

Step 2: Integrate Middleware with the Router

Now we need to teach our router how to use these classes. This involves two parts: associating a middleware with a route, and then executing that middleware.

There are many ways to design this. A clean approach, often seen in frameworks, is to add a method to your Router class to "tag" a route with middleware, and then have your router's dispatch method execute it.

You can get inspiration for how clean this can look from the simple-php-router documentation. Notice how it allows applying middleware to single routes or entire groups.

simple-php-router/README.md at master - GitHub

The README for simple-php-router provides clear examples of a well-designed middleware system. We won't implement this exact library, but we will borrow the clean architectural concepts.

Skim the sections on 'Middleware' and 'Router groups'. Pay attention to the syntax SimpleRouter::group(['middleware' => ...]). This shows a common pattern for applying a middleware class to a set of routes, which is our ultimate goal.

For our simple application, we can implement a basic version of this. In your front controller (public/index.php) where you handle routing, you would modify the logic to call the appropriate middleware before calling the controller method.

A simplified integration could look like this:

  1. Modify your routing definitions to include middleware information.
  2. In your main request handling logic, check if a middleware is defined for the matched route. If so, instantiate and call its handle() method.

After this change, your controllers (PostController, AuthController) will be much cleaner, as you can remove all the if (!isset($_SESSION['user_id'])) checks from the store(), update(), destroy(), and other methods. The middleware now handles this responsibility.


4. Advanced Security: Preventing Session Attacks

Simply checking for a session variable is good, but we can do better. Your interest in CyberSecurity makes this the perfect time to introduce a crucial practice for securing user sessions.

PHP Session Flow Diagram
A session works by giving the user a unique Session ID, stored in a cookie. The server uses this ID to retrieve the user's specific session data on each request.

This mechanism can be vulnerable to attacks like Session Fixation and Session Hijacking.

  • Session Fixation: An attacker tricks a user into using a session ID known to the attacker. When the user logs in, the session ID doesn't change, and the attacker is now also logged in with the user's account.
  • Session Hijacking: An attacker steals a user's valid session ID after they have logged in (e.g., through an insecure network or an XSS vulnerability).

The primary defense against session fixation is to regenerate the session ID upon any change in privilege level, especially after a user logs in.

The video from Program With Gio explains this concept and its implementation perfectly.

Session Based Authentication - Session Hijacking & Fixation - Build Expense Tracker App With PHP 8

This is a critical segment on session security. It explains the 'why' behind regenerating session IDs and how to configure session cookies for maximum security.

Watch the segment from 08:45 to 15:38. Focus on: The explanation of Session Hijacking vs. Fixation and why a static session ID after login is a vulnerability (08:45). The solution: using the session_regenerate_id(true) function immediately after a successful login (11:27). The explanation of secure cookie options (HttpOnly, Secure, SameSite) and how to set them with session_set_cookie_params() (12:02).

Practical Security Upgrade

Based on the video, you should make two important upgrades to your application:

  1. In your AuthController's login method, immediately after you have verified the user's password and are about to set their $_SESSION['user_id'], add this line:

    session_regenerate_id(true);
    

    This invalidates the old session ID and issues a new one, breaking any potential session fixation attack.

  2. At the very start of your application (e.g., in a bootstrap file or at the top of public/index.php), before session_start(), configure the session cookie to be more secure.

    // Best to set these based on your environment (e.g., true in production)
    $is_production = false; // Set to true on your live server
    
    session_set_cookie_params([
        'lifetime' => 3600,
        'path' => '/',
        'domain' => '', // Set your domain in production
        'secure' => $is_production, // Send only over HTTPS
        'httponly' => true, // Prevent access from JavaScript
        'samesite' => 'Lax'
    ]);
    
    session_start();
    

This configuration makes it much harder for attackers to steal or manipulate session cookies.



Conclusion

Congratulations! You have significantly improved the structure and security of our application. By replacing scattered, repetitive checks with a centralized middleware system, the code is now cleaner, more maintainable, and less prone to human error.

Key Takeaways:

  • Middleware acts as a gatekeeper for your routes, centralizing logic like authentication.
  • Using AuthMiddleware and GuestMiddleware provides robust access control for different parts of your application.
  • Refactoring your controllers to remove manual authentication checks makes them cleaner and more focused on their core tasks.
  • session_regenerate_id() is a critical function to call after login to prevent session fixation attacks.
  • Configuring session cookies with HttpOnly, Secure, and SameSite flags provides an essential layer of defense against session hijacking and CSRF.

Preview of the Next Lesson:

We've now secured who can access our routes. In the next lesson, we'll continue our focus on security by taking another look at how data is presented to the user. We will cover how to render dynamic data in views while preventing XSS vulnerabilities, ensuring that even complex user-generated content is always displayed safely.