On hiding the main Symfony front controller

Published on 2019-08-23 • Modified on 2020-01-31

In this post, we will see how to hide the front controller's file name of a Symfony application so it can't be accessed when typing it: "index.php". The less the users (or hackers of course) will know about the technical implementation of your website, the better it is. Let's go! 😎

It's the summer so this post will be very light. It's a test I have done on this blog. I wanted to have a specific controller for the production environment (like with Symfony3) and to hide the main controller so typing "/index.php" will give a 404 error without using a .htaccess file nor redirections (when using Apache as the webserver). It's not considered as a good practice, so use at your own risk!

» Published in "A week of Symfony 661" (26 August - 1 September 2019).

Use at your own risk

Configuration

  • PHP 8.3
  • Symfony 6.4
  • Apache 2.4

Introduction

The main front controller is the file that handles all HTTP requests of a Symfony application. When using Symfony4 or 5, it's the index.php file and it's located in the "public" directory. For example, when accessing the official Symfony website, you will notice that these URLs are available:

The goal here will be to prevent the users, bots or search indexes to access URLs through the index.php file.

Creation of a specific production front controller

Add a file in the "public" folder of your application and name it with a random string. (eg: ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php) Add the following code:

<?php

declare(strict_types=1);

use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return static fn (array $context) => new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

// declare(strict_types=1);
//
// // public/ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php
// // this is a fake name, not the one used by this website! 😁
//
// // use App\CacheKernel;
// use App\Kernel; // https://symfony.com/doc/current/http_cache.html#symfony-reverse-proxy
// use Symfony\Component\Dotenv\Dotenv;
// use Symfony\Component\HttpFoundation\Request;
//
// require __DIR__.'/../vendor/autoload.php';
//
// (new Dotenv())->load(__DIR__.'/../.env');
// $kernel = new Kernel('prod', false);
// // $kernel = new CacheKernel($kernel);
//
// $request = Request::createFromGlobals();
// $response = $kernel->handle($request);
// $response->send();
// $kernel->terminate($request, $response);

We have removed all dev and debug stuff to keep only the minimum code. (the performance difference with the default index.php isn't substantial) And we still have our index.php, I have renamed it to index_dev.php in this project but it's Ok to keep the original name.

Be careful that if you set TRUSTED_PROXIES or TRUSTED_HOSTS environment variables, you should keep the following lines:

if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {
    Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
}

if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {
    Request::setTrustedHosts(explode(',', $trustedHosts));
}

Hiding the main front controller name

Now, let's hide the main controller. The first thing is to exclude the index.php file from your deployment process. To deploy this blog I use EasyDeploy, you will find my deploy file here. I delete the index_dev.php file with the controllersToRemove() function. Now, we have to tell our webserver to use the new file, for example when using Apache:

# https://stackoverflow.com/questions/59921221/apache-how-to-handle-unknown-php-file-like-standard-urls
# configuration/vhosts/prod/www.strangebuzz.com-le-ssl.conf
<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName www.strangebuzz.com
        ServerAlias strangebuzz.com
        DocumentRoot /var/www-protected/strangebuzz.com/public
        DirectoryIndex /ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php

        <Directory /var/www-protected/strangebuzz.com/public>
            AllowOverride All
            Require all granted
            # this is a fake one, again! 😁
            FallbackResource /ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php
            #FallbackResource /index.php

            # 1st test for move .htaccess here
            RewriteEngine On
            RewriteBase /

            RewriteCond %{THE_REQUEST} ^.*/index\.php
            RewriteRule ^(.*)index.php$ /$1 [R=301,L]

            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteCond %{REQUEST_FILENAME} !-d
            RewriteRule . /ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php [L]
        </Directory>

        ErrorDocument 404 https://www.strangebuzz.com/404

        ErrorLog /var/log/apache2/strangebuzz.com_error.log
        CustomLog /var/log/apache2/strangebuzz.com_access.log combined

        #SSLCertificateFile /etc/letsencrypt/live/www.strangebuzz.com/fullchain.pem
        #SSLCertificateKeyFile /etc/letsencrypt/live/www.strangebuzz.com/privkey.pem
        #Include /etc/letsencrypt/options-ssl-apache.conf

        # . vers www
        RewriteEngine on
        RewriteCond %{SERVER_NAME} =strangebuzz.com
        RewriteRule ^ https://www.strangebuzz.com [END,NE,R=permanent]

Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/strangebuzz.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/strangebuzz.com/privkey.pem
    </VirtualHost>
</IfModule>
# Used as full snippet in templates/blog/posts/_39.html.twig

As you can see, we have modified the fallback resource to be our new production controller instead of index.php. Et voilà! Now, when accessing /index.php, we get a 404 so there is only one URL associated with each resource. And the canonical URL will always be the version without the file inside.

Bonus:
1) There is a post in this blog where you could find the real main controller file name of this website, can you find it?
2) Why is it still indicated index.php?

Click here to see the answers!

1) It's here: The Symfony Request class interactive cheatsheet. The main front controller file name is returned by the getScriptName() function of the Request value object.
2) I am cheating, I simply put index.php manually instead of getting the result of the function! 😁

[
    'name' => 'getScriptName',
    'parameters' => '',
    'type' => 'string',
    //'result' => $r->getScriptName(),
    'result' => 'index.php', // hide real controller name
    'show' => !$refresh,
    'url' => 812
],

That's it! I hope you like it. Check out the links below to have additional information related to the post. As always, feedback, likes and retweets are welcome. (see the box below) See you! COil. 😊

 The Symfony doc  More on Stackoverflow  More on the web

  Work with me!


Call to action

Did you like this post? You can help me back in several ways: (use the Tweet on the right to comment or to contact me )

Thank you for reading! And see you soon on Strangebuzz! 😉

COil