Keeping your web application secure is a continuous effort. One basic yet effective technique is controlling access based on IP addresses. Whether you need to block malicious actors or restrict access to a specific set of users, managing IP addresses can be crucial.

In this article, we'll explore how to implement IP blocking and whitelisting in your PHP applications using Object-Oriented Programming (OOP). This approach provides a clean, reusable, and easy-to-understand way to manage access control.

The IPBlocker Class: Denying Access

Let's start by creating an IPBlocker class. This class will read a list of IPs from a text file and block any incoming requests from those IPs.

class IPBlocker {
    private $blockedIPs = [];
    private $blockedIPFile;

    public function __construct(string $blockedIPFile) {
        $this->blockedIPFile = $blockedIPFile;
        $this->loadBlockedIPs();
    }

    private function loadBlockedIPs(): void {
        if (!file_exists($this->blockedIPFile) || !is_readable($this->blockedIPFile)) {
            error_log("Error: Could not read blocked IP file: " . $this->blockedIPFile);
            return;
        }

        $ips = file($this->blockedIPFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if ($ips !== false) {
            $this->blockedIPs = array_map('trim', $ips);
        } else {
            error_log("Error: Failed to read lines from blocked IP file: " . $this->blockedIPFile);
        }
    }

    private function getClientIP(): ?string {
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        return filter_var($ip, FILTER_VALIDATE_IP);
    }

    private function isIPBlocked(string $ip): bool {
        return in_array($ip, $this->blockedIPs, true);
    }

    public function blockRequest(): void {
        $clientIP = $this->getClientIP();

        if ($clientIP && $this->isIPBlocked($clientIP)) {
            header('HTTP/1.1 403 Forbidden');
            echo "Your IP address has been blocked from accessing this website.";
            exit;
        }
    }
}

// Example usage:
$blockedIPFile = 'blocked_ips.txt';
$ipBlocker = new IPBlocker($blockedIPFile);
$ipBlocker->blockRequest();

// The rest of your application code goes here
?>

How it works:

  1. The IPBlocker class takes the path to a text file (blocked_ips.txt) in its constructor. This file should contain one IP address per line.
  2. The loadBlockedIPs() method reads the IPs from the file into the $blockedIPs array.
  3. The getClientIP() method attempts to retrieve the visitor's IP address, handling cases where the client might be behind a proxy.
  4. The isIPBlocked() method checks if the client's IP exists in the $blockedIPs array.
  5. The blockRequest() method orchestrates the process: it gets the client's IP and, if it's in the blocked list, sends a 403 Forbidden header and a message before terminating the script.

To use this:

  1. Create a text file named blocked_ips.txt in the same directory as your PHP script.
  2. Add the IP addresses you want to block to this file, one per line.
  3. Include and instantiate the IPBlocker class at the very top of your PHP files, calling the blockRequest() method.

The IPAllowlist Class: Granting Access

Now, let's create an IPAllowlist class to allow only specific IPs to access your website.

class IPAllowlist {
    private $allowedIPs = [];
    private $allowedIPFile;
    private $blockMessage = "Your IP address is not allowed to access this website.";
    private $blockStatusCode = 403;

    public function __construct(string $allowedIPFile) {
        $this->allowedIPFile = $allowedIPFile;
        $this->loadAllowedIPs();
    }

    public function setBlockMessage(string $message): void {
        $this->blockMessage = $message;
    }

    public function setBlockStatusCode(int $statusCode): void {
        $this->blockStatusCode = $statusCode;
    }

    private function loadAllowedIPs(): void {
        if (!file_exists($this->allowedIPFile) || !is_readable($this->allowedIPFile)) {
            error_log("Error: Could not read allowed IP file: " . $this->allowedIPFile);
            return;
        }

        $ips = file($this->allowedIPFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if ($ips !== false) {
            $this->allowedIPs = array_map('trim', $ips);
        } else {
            error_log("Error: Failed to read lines from allowed IP file: " . $this->allowedIPFile);
        }
    }

    private function getClientIP(): ?string {
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        return filter_var($ip, FILTER_VALIDATE_IP);
    }

    private function isIPAllowed(): bool {
        $clientIP = $this->getClientIP();
        return ($clientIP && in_array($clientIP, $this->allowedIPs, true));
    }

    public function checkRequest(): void {
        if (!$this->isIPAllowed()) {
            header("HTTP/1.1 " . $this->blockStatusCode . " Forbidden");
            echo $this->blockMessage;
            exit;
        }
    }
}

// Example usage:
$allowedIPFile = 'allowed_ips.txt';
$ipAllowlist = new IPAllowlist($allowedIPFile);
$ipAllowlist->checkRequest();

// The rest of your application code goes here
?>

How it works:

The IPAllowlist class functions similarly to the IPBlocker:

  1. It takes the path to an allowed_ips.txt file in its constructor.
  2. loadAllowedIPs() reads the allowed IPs from the file.
  3. getClientIP() retrieves the visitor's IP.
  4. isIPAllowed() checks if the client's IP is in the allowed list.
  5. checkRequest() verifies if the IP is allowed. If not, it sends a "403 Forbidden" response and stops execution.

To use this:

  1. Create a text file named allowed_ips.txt.
  2. Add the IP addresses you want to allow to this file, one per line.
  3. Include and instantiate the IPAllowlist class at the beginning of your PHP scripts, calling the checkRequest() method.

Combining Blocking and Allowing

You might need both blocking and allowing logic in your application. You can achieve this by instantiating both classes. However, you'll need to decide on the order of execution and how to handle cases where an IP might be in both lists (though this is generally not a recommended setup).

A more robust solution for complex scenarios might involve a single class that manages both blocked and allowed IPs, potentially with a priority setting (e.g., always block if in the blacklist, even if in the whitelist).

Further Enhancements

Here are some ideas for extending these classes:

  • Database Storage: Instead of text files, store the blocked and allowed IPs in a database for easier management and scalability.
  • IP Range Blocking/Allowing: Implement logic to handle IP address ranges using techniques like CIDR notation.
  • Logging: Log blocked access attempts for security auditing.
  • Configuration: Load file paths and messages from a configuration file.
  • Error Handling: Implement more sophisticated error handling, perhaps throwing exceptions instead of just logging errors.

Conclusion

These simple PHP OOP classes provide a foundation for implementing IP-based access control in your web applications. By reading IP lists from external files, you can easily manage who can and cannot access your site. Remember to always consider the specific security needs of your application and explore more advanced techniques as required.

By leveraging the power of OOP, we've created reusable components that can be easily integrated into your PHP projects to enhance their security posture. Happy coding!