Skip to content

Security

Edit this page

EasyAdmin relies on Symfony Security for everything related to security. That's why before restricting access to some parts of the backend, you need to properly setup security in your Symfony application:

  1. Create users in your application and assign them proper permissions (e.g. ROLE_ADMIN);
  2. Define a firewall that covers the URL of the backend.

Logged in User Information

When accessing a protected backend, EasyAdmin displays the details of the user who is logged in the application and a menu with some options like "logout". Read the user menu reference for more details.

Restrict Access to the Entire Backend

Using the access_control option, you can tell Symfony to require certain permissions to browse the URL associated to the backend. This is simple to do because each dashboard only uses a single URL:

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    # ...

    access_control:
        # change '/admin' by the URL used by your Dashboard
        - { path: ^/admin, roles: ROLE_ADMIN }
        # ...

Another option is to use the #[IsGranted] attribute in the dashboard controller:

1
2
3
4
5
6
7
8
9
10
// app/Controller/Admin/DashboardController.php
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_ADMIN')]
class DashboardController extends AbstractDashboardController
{
    // ...
}

Restrict Access to Some CRUD Controllers

When using more than one Dashboard you might need to restrict which CRUD controllers are accessible for each of them.

Consider that in your application you have two dashboards (DashboardController used by your employees and GuestDashboardController used by external collaborators). In the guest dashboard you only want to allow certain actions related to your blog.

However, when using pretty admin URLs, EasyAdmin will generate the routes for all CRUD controllers in all dashboards. This means that there will be undesired routes like admin_guest_invoice, admin_guest_user_detail, etc. The best way to restrict which CRUD controllers are accessible via each dashboard is to use the #[AdminDashboard] attribute:

1
2
3
4
5
6
7
8
9
// app/Controller/Admin/DashboardController.php
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;

#[AdminDashboard(allowedControllers: [BlogPostCrudController::class, BlogCategoryCrudController::class])]
class DashboardController extends AbstractDashboardController
{
    // ...
}

The allowedControllers option defines the only CRUD controllers that will be available in the dashboard via Symfony routes. In practice, the above configuration will make EasyAdmin to only generate the routes admin_guest_blog_post_* and admin_guest_blog_category_*, skipping all the other routes that would have allowed to access the other controllers.

Tip

You can also define the opposite option (deniedControllers) to allow all controllers except the ones included in that list.

Restrict Access to Menu Items

Use the setPermission() method to define the security permission that the user must have in order to see the menu item:

1
2
3
4
5
6
7
8
9
public function configureMenuItems(): iterable
{
    return [
        // ...

        MenuItem::linkToCrud('Blog Posts', null, BlogPost::class)
            ->setPermission('ROLE_EDITOR'),
    ];
}

Note

This permission only shows/hides menu items. The actions associated to those menu items are still executable, even if the user can't see the menu items. Use the actions permissions to also restrict the access to those actions.

If your needs are more advanced, remember that the dashboard class is a regular Symfony controller, so you can use any service related to security to evaluate complex expressions. In those cases, it's more convenient to use the alternative menu item definition to not have to deal with array merges:

1
2
3
4
5
6
7
8
9
10
public function configureMenuItems(): iterable
{
    yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');

    if ($this->isGranted('ROLE_EDITOR') && '...') {
        yield MenuItem::linkToCrud('Blog Posts', null, BlogPost::class);
    }

    // ...
}

Restrict Access to Actions

Use the setPermission() method to define the security permission required to see the action link/button:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;

public function configureActions(Actions $actions): Actions
{
    $viewInvoice = Action::new('invoice', 'View invoice', 'fa fa-file-invoice')
        ->linkToCrudAction('renderInvoice');

    return $actions
        // ...
        ->add(Crud::PAGE_DETAIL, $viewInvoice)
        // use the 'setPermission()' method to set the permission of actions
        // (the same permission is granted to the action on all pages)
        ->setPermission('invoice', 'ROLE_FINANCE')

        // you can set permissions for built-in actions in the same way
        ->setPermission(Action::NEW, 'ROLE_ADMIN')
    ;
}

Restrict Access to Fields

There are several options to restrict the information displayed in the page depending on the logged in user. First, you can show/hide the entire field with the setPermission() method:

1
2
3
4
5
6
7
8
9
10
11
12
public function getFields(string $action): iterable
{
    return [
        IdField::new('id'),
        TextField::new('price'),
        IntegerField::new('stock'),
        // users must have this permission/role to see this field
        IntegerField::new('sales')->setPermission('ROLE_ADMIN'),
        FloatField::new('commission')->setPermission('ROLE_FINANCE'),
        // ...
    ];
}

You can also restrict which items users can see in the index and detail pages thanks to the setEntityPermission() method. This value is passed as the first argument of the call to is_granted($permissions, $item) function to decide if the current user can see the given item:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace App\Controller\Admin;

use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;

class ProductCrudController extends AbstractCrudController
{
    // ...

    public function configureCrud(Crud $crud): Crud
    {
        return $crud
            ->setEntityPermission('ROLE_ADMIN')
            // ...
        ;
    }
}

In the detail page, if the user doesn't have permission they will see an appropriate error message (and you'll see a detailed error message in the application logs).

In the index page, to avoid confusion and pagination errors, if the user doesn't have permission to see some items, an empty row will be displayed at the bottom of the list with a message explaining that they don't have enough permissions to see some items:

Index page with some results hidden because user does not have enough permissions

Restricting Access with Expressions

4.9.0

The Expressions support was introduced in EasyAdmin 4.9.0.

The Symfony ExpressionLanguage component allows to define complex configuration logic using simple expressions. In EasyAdmin, all setPermission() methods allow to pass not only a string with some security role name (e.g. ROLE_ADMIN) but also a full Expression object.

First, install the component in your project using Composer:

1
$ composer require symfony/expression-language

Now, you can pass a Symfony Expression object to any setPermission() method like this:

1
2
3
4
use Symfony\Component\ExpressionLanguage\Expression;

MenuItem::linkToCrud('Restricted menu-item', null, Example::class)
    ->setPermission(new Expression('"ROLE_DEVELOPER" in role_names and "ROLE_EXTERNAL" not in role_names'));

Expressions enable the definition of much more detailed permissions, based on several role names, user attributes, or the given subject. The expressions can include any of these variables:

  • user - the current user object
  • role_names - all the roles of current user as an array
  • subject or object - the current subject being checked
  • token - the authentication token
  • trust_resolver - the authentication trust resolver
  • auth_checker - an instance of the authorization checker service

Custom Security Voters

EasyAdmin implements a Symfony security voter to check the permissions defined for actions, entities, menu items, etc. The actual security permissions are defined as constants in the EasyCorp\Bundle\EasyAdminBundle\Security\Permission class (e.g. Permission::EA_EXECUTE_ACTION, Permission::EA_VIEW_MENU_ITEM, etc.)

If you define a custom security voter for the backend, consider changing the access decision strategy used by your application. The default strategy, called affirmative, grants access as soon as one voter grants access (if EasyAdmin voter grants access, your custom voter won't be able to deny it).

That's why you should change the default strategy to unanimous, which grants access only if there are no voters denying access:

1
2
3
4
# config/packages/security.yaml
security:
    access_decision_manager:
        strategy: unanimous
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version