Restricting Access to Drupal 8 Controllers

Joel Steidl

Controllers in Drupal 8 are the equivalent of hook_menu in Drupal 7. A controller lets you define a URL and what content or data should appear at that URL. If you’re like me, limiting access to my controllers is sometimes an afterthought. Limiting access is important because it defines who can and can’t see a page.

Controllers are defined in a YAML file called module_name.routing.yml. Access and permission rules are defined in the the module_name.routing.yml under _requirements. Most of the code examples will be from a module_name.routing.yml file added to my_module in the top level.

Note: There is a lot of existing documentation on how to create controllers in Drupal 8, so I won’t focus on that here.

I’ve outlined some of the most useful approaches for limiting access below. You can jump straight to the most relevant section using the following links: limit by permission, limit by role, limit by one-off custom code, limit by custom access service.

Limit by permission

In this case, a permission from the Drupal permissions page is given. Permissions can be found at /admin/people/permissions. Finding the exact permission name can be tricky. Look for module.permissions.yml files in the module providing the permission.

my_module.dashboard:
  path: 'dashboard'
  defaults:
    _controller: '\Drupal\my_module\Controller\DashboardController::content'
    _title: 'Dashboard'
  requirements:
    _permission: 'access content'

Key YAML definition:

_permission: 'THE PERMISSION NAME'

Limit by role

You can also limit access by role. This would be useful in cases where users of a specific role will be the only ones needing access to your controller. You can define user roles at /admin/people/roles.

my_module.dashboard:
  path: 'dashboard'
  defaults:
    _controller: '\Drupal\my_module\Controller\DashboardController::content'
    _title: 'Dashboard'
  requirements:
    _role: 'administrator'

Key YAML definition:

_role: 'THE ROLE NAME'

You can specify multiple roles using "," for AND and "+" for OR logic.

Limit by one-off custom code

In cases where you have custom access requirements, adding an access method to your controller might make sense. In this example, the page should not be viewed before a specified date.

my_module.dashboard:
  path: 'dashboard'
  defaults:
    _controller: '\Drupal\my_module\Controller\DashboardController::content'
    _title: 'Dashboard'
  requirements:
   _custom_access: '\Drupal\my_module\Controller\DashboardController::access

Key YAML definition:

_custom_access: '\Drupal\my_module\Controller\DashboardController::access

The access method in my controller would look like:

<?php
namespace Drupal\my_module\Controller;
 
use Drupal\Core\Access\AccessResult; 
use Drupal\Core\Controller\ControllerBase;
 
/**
 * Defines the Dashboard controller.
 */
class DashboardController extends ControllerBase { {
 
 /**
  * Returns content for this controller.
  */
 public function content() {
   $build = [];
   return $build;
 }
 
 /**
  * Checks access for this controller.
  */
 public function access() {
   // Don’t allow access before Friday, November 25, 2016.
   $today = date("Y-m-d H:i:s");
   $date = "2016-11-25 00:00:00";
   if ($date < $today) {
     // Return 403 Access Denied page.  
     return AccessResult::forbidden();
    }
    return AccessResult::allowed();
  }
}

Limit by custom access service

This is similar to having an access method in your controller, but allows the code to be reused across many controllers. This is ideal when you are doing the same access check across many controllers.

my_module.dashboard:
  path: 'dashboard'
  defaults:
    _controller: '\Drupal\my_module\Controller\DashboardController::content'
    _title: 'Dashboard'
  requirements:
    _custom_access_check: 'TRUE'

Key YAML definition:

_custom_access_check: 'TRUE'

Proving the _custom_access_check service requires creating two files in my_module.

my_module/my_module.services.yml (defines the Access service and where to find our Access class)

services:
  my_module.custom_access_check:
    class: Drupal\my_module\Access\CustomAccessCheck
    arguments: ['@current_user']
    tags:
      - { name: access_check, applies_to: _custom_access_check }

my_module/src/Access/CustomAccessCheck.php

<?php
namespace Drupal\my_module\Access;
 
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
 
/**
 * Class CustomAccessCheck.
 *
 * @package Drupal\my_module\Access
 */
class CustomAccessCheck implements AccessInterface {
 
  /**
   * A custom access check.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for the logged in user.
   */
  public function access(AccountInterface $account) {
    // User has a profile field defining their favorite color.
    if ($account->field_color->hasField() && !$account->field_color->isEmpty() && $account->field_color->getString() === 'blue') {
      // If the user's favorite color is blue, give them access.
      return AccessResult::allowed();
    }
    return AccessResult::forbidden();
  }
 
}

While the above covers some of the most useful ways to restrict access to a controller, there are additional options. Drupal.org has a couple of good resources including Structure of Routes and Access Checking on Routes.


About the Author

With over ten years of professional experience, Joel's joint background in design, development and information architecture allows him to lead our development team with a focus on building user-centered web applications.

Read More