Security

Contao uses Symfony’s Security Component to handle front end and back end user authentication and authorization. The Contao Managed Edition provides its own firewall, user providers and access control via the contao/manager-bundle. If you do not use the Managed Edition and added Contao to your custom Symfony application, you will have to register Contao’s security settings yourself, as mentioned in the Getting Started article. Contao’s Symfony Security configuration in Contao 4.9 looks like this for example:

Contao's Symfony Security Configuration

If you want to learn more about Symfony’s Security Component use the provided links to read up on. This documentation will only cover implmentation details that are unique to Contao.

Since within Contao you can put a login form on basically any page, Contao does not utilise Symfony’s built-in form_login Authentication Provider. Instead, Contao implements its own user checker and request matcher service, the latter of which checks the request scope. For example in the front end, all URLs to Contao pages will have the _scope request attribute set to frontend and the contao_frontend firewall will thus be applicable to all these URLs. Contao implements an authentication listener which will check for any POST request containing the parameters username and password and the parameter FORM_SUBMIT with the value tl_login (as these are the parameters used by Contao’s login module).

Voters

Starting with Contao 4.7 Contao implements Voters in order to easily check whether an authenticated user is authorized to access specific resources. These voters are automatically added to Symfony’s security system and then invoked when the respective permission is accessed via the Security Helper.

This feature is available in Contao 4.7 and later.

The security helper can be used to check whether the currently authenticated user has access in the back end to specific forms, table fields (as defined via the DCA), folders and modules for example:

// User has access to form ID 5
$security->isGranted('contao_user.forms', 5);

// User is allowed to access field "published" of table "tl_page"
$security->isGranted('contao_user.alexf', 'tl_page::published');

// Check access to folder
$security->isGranted('contao_user.filemounts', '/files/foo/bar');

This feature is available in Contao 4.10 and later.

You can also use the security helper to check wether the user can edit a page or is allowed to edit fields in a specific DCA for example:

// whether the user can access any field in tl_page
$security->isGranted('contao_user.can_edit_fields', 'tl_page');

// whether user can edit the given page (array of row or page model)
$security->isGranted('contao_user.can_edit_page', [/* row of page data */]);
$security->isGranted('contao_user.can_edit_page', $pageModel);

This feature is available in Contao 4.12 and later.

The security helper can also be used to check whether a front end user belongs to any of the specified user groups:

$security->isGranted('contao_member.groups', $groupId);
$security->isGranted('contao_member.groups', [/* array of group IDs */]);

Since Contao 4.10 there are class constants available for the various permission attributes, so that you do not have to remember them yourself and instead can use your IDE to find the correct attribute. For the Contao Core these constants are available in Contao\CoreBundle\Security\ContaoCorePermissions while the permissions of the additional bundles are available in Contao\NewsBundle\Security\ContaoNewsPermissions, Contao\CalendarBundle\Security\ContaoCalendarPermissions, Contao\NewsletterBundle\Security\ContaoNewsletterPermissions and Contao\FaqBundle\Security\ContaoFaqPermissions respectively. The PHP documentation of these constants also contain hints about what type the subject should be. Examples:

use Contao\CoreBundle\Security\ContaoCorePermissions;
use Contao\NewsBundle\Security\ContaoNewsPermissions;

// Whether user can access the news module in the back end
$security->isGranted(ContaoCorePermissions::USER_CAN_ACCESS_MODULE, 'news');

// Whether user can use the hidden input field in the form generator
$security->isGranted(ContaoCorePermissions::USER_CAN_ACCESS_FIELD, 'hidden');

// Whether user has access to any field of tl_content
$security->isGranted(ContaoCorePermissions::USER_CAN_EDIT_FIELDS_OF_TABLE, 'tl_content');

// Whether user can create news archives
$security->isGranted(ContaoNewsPermissions::USER_CAN_CREATE_ARCHIVES);

Custom Back End Access Rights

To implement your own back end access rights (e.g. for custom modules in the back end) the following steps are necessary:

  1. Add your new permission to $GLOBALS['TL_PERMISSIONS']. This registers this permission to be used by Contao’s contao_user voter.
  2. Add your new permission to tl_user and tl_user_group.
// contao/config/config.php
$GLOBALS['TL_PERMISSIONS'][] = 'my_permissions';
// contao/dca/tl_user.php
use Contao\CoreBundle\DataContainer\PaletteManipulator;

$GLOBALS['TL_DCA']['tl_user_group']['fields']['my_permissions'] = [
    'exclude' => true,
    'inputType' => 'checkbox',
    'eval' => ['multiple' => true],
    'options' => [
        'first_permission' => 'First permission',
        'second_permission' => 'Second permission',
    ],
    'sql' => ['type' => 'blob', 'notnull' => false],
];

	'exclude'                 => true,
	'inputType'               => 'checkbox',
	'foreignKey'              => 'tl_calendar.title',
	'eval'                    => array('multiple'=>true),
	'sql'                     => "blob NULL"

PaletteManipulator::create()
    ->addLegend('my_legend', null)
    ->addField('my_permission', 'my_legend', PaletteManipulator::POSITION_APPEND)
    ->applyToPalette('extend', 'tl_user')
    ->applyToPalette('custom', 'tl_user')
;
// contao/dca/tl_user_group.php
use Contao\CoreBundle\DataContainer\PaletteManipulator;

$GLOBALS['TL_DCA']['tl_user_group']['fields']['my_permissions'] = [
    'exclude' => true,
    'inputType' => 'checkbox',
    'options' => [
        'first_permission' => 'First permission',
        'second_permission' => 'Second permission',
    ],
    'sql' => ['type' => 'blob', 'notnull' => false],
];

PaletteManipulator::create()
    ->addLegend('my_legend', null)
    ->addField('my_permission', 'my_legend', PaletteManipulator::POSITION_APPEND)
    ->applyToPalette('default', 'tl_user_group')
;

Once that is done you can check for this permission in your own back end controller for example. These permissions can be checked via the security helper by using the contao_user.* attribute. We also check that the current user is not ROLE_ADMIN because administrators should always be able to access the controller.

// src/Controller/BackendController.php
namespace App\Controller;

use Contao\CoreBundle\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Twig\Environment;

/**
 * @Route("/contao/my-backend-route",
 *     name=BackendController::class,
 *     defaults={"_scope": "backend"}
 * )
 */
class BackendController
{
    private $twig;
    private $security;

    public function __construct(Environment $twig, Security $security)
    {
        $this->twig = $twig;
        $this->security = $security;
    }

    public function __invoke(): Response
    {
        if (!$this->security->isGranted('ROLE_ADMIN') && !$this->security->isGranted('contao_user.my_permissions', 'first_permission')) {
            throw new AccessDeniedException('Not enough permissions to access this controller.');
        }

        return new Response($this->twig->render('my_backend_route.html.twig', []));
    }
}

We do not need to check whether a user is logged in this case, since the Contao firewall automatically checks this for this controller with the configured routing parameters (see also the back end route guide).

Instead of extending Contao’s own permissions system you are also free to implement your own voter.