Custom route access checking

Last updated on
31 October 2024

This documentation needs work. See "Help improve this page" in the sidebar.

Simplified custom access checker

Sometimes you have to add an access checker which is just used for a single route entry.

In these cases, instead of creating a separate access checker class or service, you can specify function such as a method on your controller on the routing definition:

example:
  path: '/example'
  defaults:
    _controller: '\Drupal\example\Controller\ExampleController::content'
  requirements:
    _custom_access: '\Drupal\example\Controller\ExampleController::access'
namespace Drupal\example\Controller;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;

/**
 * Builds an example page.
 */
class ExampleController {

  /**
   * Checks access for a specific request.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function access(AccountInterface $account) {
    // Check permissions and combine that with any custom access checking needed. Pass forward
    // parameters from the route and/or request as needed.
    return AccessResult::allowedIf($account->hasPermission('do example things') && $this->someOtherCustomCondition());
  }

}

In this case, the access method is in the same class as the controller itself so there is no need for a separate service entry. The available arguments are the same as for the service defined method.

Note: Function arguments are resolved as described in Using parameters in routes with the addition of AccountInterface as typehinted parameter.

Access checking in the controller content method

Beware: Using this approach prevents link access checking ($url->access()). You may get active menu links that point to pages with no access.

If you need or want to do access checking in the controller content method for some reason, you can set _access: 'TRUE' in the route definition and do the access checking in the controller content method. If you take this approach, make sure to return the proper Symfony route exceptions if the page is not found or access should be denied. The above methods where access is decoupled from content are definitely preferred over mixing page logic to access.

1. Controller class to handle access check-in content method.

<?php

namespace Drupal\examples_dten\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Routes Examples.
 */
final class ExamplesController extends ControllerBase {

  /**
   * The controller constructor.
   */
  public function __construct(
    private AccountInterface $account
  ) {
    $this->account = $account;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('current_user'),
    );
  }

  /**
   * {@inheritDoc}
   */
  public function contentAccess() {
    // If an authenticated user shows the user name.
    if ($this->account->isAuthenticated() == TRUE) {
      $build = [
        '#type' => 'markup',
        '#markup' => $this->t('Hello @user', [
          '@user' => $this->account->getDisplayName(),
        ]),
      ];
      return $build;
    }
    else {
      // Show Access denied exception.
      throw new AccessDeniedHttpException();
      // Show Page not found exception.
      //throw new NotFoundHttpException();
    }
  }

}

2. module.routing.yml file to define route.

examples_dten.access_check_method:
  path: '/examples-dten/access-check/method'
  defaults:
    _title: 'Access check in Content method'
    _controller: '\Drupal\examples_dten\Controller\ExamplesController::contentAccess'
  requirements:
    _access: 'TRUE'

Help improve this page

Page status: Needs work

You can: