Symfony2 multi tenant solution

Over the last month I’ve been busy developing a website hosted on Azure (PaaS) which was easier than expected to deploy.

Today I’d like to share a multitenant solution I developed for Symfony which came out very elegant and easy to use:


What I wanted was a way to include several customers under the same domain (and subdomain) with no passing of additional variables to control the current tenant over the different controllers. Specifically, what I wanted was a way to point to a tenant with the following structure:


But of course that wasn’t as easy as expected, so here is the code I used to make it work:


resource: "@VendorMyBundle/Controller/"
type: annotation
prefix: /{tenant}

I used FOSUserBundle, so it had to be configured too:


pattern: ^/
provider: fos_userbundle
login_path: fos_user_security_login
check_path: fos_user_security_check
csrf_provider: form.csrf_provider
success_handler: my.login_success_handler
default_target_path: administration
anonymous: true
path: fos_user_security_logout
target: fos_user_security_login

- { path: .*/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: .*/, role: ROLE_USER }

Note that the last two lines have some minor regex changes so it can accept any tenant

Then I had to include a EventListener to save the tenant configuration in the session after a user has successfully logged in


namespace Vendor\MyBundle\EventListener;

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class TenantListener implements EventSubscriberInterface
private $defaultTenant;
private $router;//add
private $context;

public function __construct($defaultTenant, \Symfony\Component\Routing\Router $router, \Symfony\Component\Security\Core\SecurityContext $context)
$this->defaultTenant = $defaultTenant;
$this->router = $router;
$this->context = $context;

public function onKernelRequest(GetResponseEvent $event)
$request = $event->getRequest();
if ($this->context->isGranted('ROLE_USER')||$this->context->isGranted('ROLE_ADMIN')) {
$this->defaultTenant = $request->getSession()->get('tenant');
} else {
$request->getSession()->set('tenant', $this->defaultTenant);
$request->attributes->set('tenant', $this->defaultTenant);

public static function getSubscribedEvents()
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),

And register the Listener under /app/config/config.yml

class: Vendor\MyBundle\EventListener\TenantListener
arguments: ["%default_tenant%", @router, @security.context]
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

With this solution I can pass the ‘tenant’ variable throughout the session so that every controller or twig can use it. Note that even if the user changes the tenant url, the variable considered is the one that was registered at the time of the login.

Now I can have a custom login theme for every tenant:

Extending the FOSUserBundle Controller in my Bundle:


namespace Vendor\MyBundle\Controller;

use Symfony\Component\HttpFoundation\RedirectResponse;
use FOS\UserBundle\Controller\SecurityController as BaseController;
use Symfony\Component\HttpFoundation\Request;

class SecurityController extends BaseController

protected function renderLogin(array $data)
$tenant = $this->container->get('request')->getSession()->get('tenant');


I can then check the tenant info passed in the Listener and use it to have a different experience for different tenants.


Great, right? Next post will be about checking if the current user belongs to the right tenant… To be continued

Comments (0)