Nomisoft
Menu

Blocking IP's in Symfony

05th August 2016

What do you do if you need to block certain IP addresses from accessing your entire application? Your initial thoughts would probably be looking at blocking IP's at your OS level using your firewall or at the web server level by using rules in your .htaccess file. However what if the IP addresses are not fixed, they might be constantly changing depending on some other business logic or IP addresses stored in a database.

The example below shows how we can block IP addresses using Symfony by intercepting requests using kernel event listeners. To keep things simple in this example we're just using an array to hold the list of IP addresses, in reality you'd probably be using Doctrine to fetch the list of IP addresses from a table or calling code somewhere else in your application to return the IP addresses to you.

The Code


#src/AppBundle/EventListener/KernelListener.php

namespace AppBundle\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

class KernelListener
{

    public function onKernelRequest(GetResponseEvent $event)
    {
        if ($event->getRequestType() !== \Symfony\Component\HttpKernel\HttpKernel::MASTER_REQUEST) {
            return;
        }

           $bannedIps = array('172.16.0.1', '172.16.0.2', '172.16.0.3');

        if (in_array($event->getRequest()->getClientIp(), $bannedIps)) {
            $event->setResponse(new Response('Your IP is banned', 403));
        }

    }
}

When a request event occurs our onKernelRequest() function will be run.

A single page on your application could potentially have multiple kernel requests. Say for example you're rendering another action from your controller in a twig template using {% render ... %}. In this case you have a master request coming from the visitor/browser plus a sub-request coming from our application internally. We only want our code to run on the initial master request coming from the visitor. We achieve this with the following code.


if ($event->getRequestType() !== \Symfony\Component\HttpKernel\HttpKernel::MASTER_REQUEST) {
    return;
}

Note the two types of request that are available are HttpKernel::MASTER_REQUEST and HttpKernel::SUB_REQUEST.

We can grab the IP address of the visitor from the Event object passed to our function using $event->getRequest()->getClientIp()

If the IP address is in our list of banned IP's then we intercept by setting the response a new response object with an error message and a 403 status code. $event->setResponse(new Response('Your IP is banned', 403));

In order for our code to actually run we have to register the event listener in our services.yml file in the app/config folder.


services:
    app.kernel_listener:
        class: AppBundle\EventListener\KernelListener
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

The 'app.kernel_listener' line is just the name we're giving to our service. The class line points to our event listener class we previously created. The tags, known as dependency injection tags, tell Symfony that we'd like to register this as a listener to a Symfony event. We tell Symfony we want to listener to the request event and to call the function onKernelRequest in our class.