Skip to main content

Drupal 10 Route: Mastering custom route access checker

Drupal 10 Route: Mastering custom route access checker

Drupal 10 Route: Mastering Custom Route Access Checker

Providing strong defences against illegal access, creating safe routes, and boosting security with custom route access checker to gain control over pages. Routing is the process that determines how URLs are interpreted and mapped to controllers. According to the Symfony Docs:

When your application receives a request, it calls a controller action to generate the response. The routing configuration defines which action to run for each incoming URL. It also provides other useful features, like generating SEO-friendly URLs (e.g. /read/intro-to-symfony instead of index.php?article_id=57). 

How to create a custom route in Drupal 10

A new route (web url) can be easily created in Drupal by creating a modulename.routing.yml file inside your custom module root folder. For example, our module lives in /web/modules/custom/bhimmu_styles, so our routing file would be /web/modules/custom/bhimmu_styles/bhimmu_styles.routing.yml.

# /web/modules/custom/bhimmu_styles/bhimmu_styles.routing.yml
bhimmu_styles.myroute:
  path: '/bhimmu-styles/myroute'
  defaults:
    _title: 'Bhimmu Route Demo'
    _controller: '\Drupal\bhimmu_styles\Controller\BhimmuStylesController::inIt'
  requirements:
    _role: 'administrator'

What do we have here in the above sample file to implement custom route?

bhimmu_styles.myroute:

This line specifies the machine name of the route.

path: '/bhimmu-styles/myroute'

Path (required) is an internal URL that will be appended to the base URL. for example, https://example.com/bhimmu-styles/myroute 

defaults:
    _title: 'Bhimmu Route Demo'
    _controller: '\Drupal\bhimmu_styles\Controller\BhimmuStylesController::inIt'

defaults (required) tells you the default properties of a route. In this example, we have two things under defaults.

  1. _title: Title of the page.
  2. _controller: Which controller method would be called.
requirements:
    _role: 'administrator'

In this section, we can define what condition must be applied to let the visitor see the page content. In our example, any user with an administrator role can see this page. All other role users will see a 403 (access denied) page when they visit this page.

Need for Custom Route Access Checker in Drupal 10

In general, you can control access via roles and permissions; however, in some situations, you may want to control just one route for some users of the same role. Here we can leverage the power of the Drupal custom route access check.

Problem:

If a user reaches a specific level on my website, I would like to display a new menu item. Levels can be attained in a variety of methods, including through page views, comments, and likes. You may write whatever logic you choose, however in this instance, we'll add a new field to the user entity called User Level (field_user_level).

Solution: How to create custom route access checker in Drupal 8, 9 and 10

Step 1: Create a new route into module.routing.yml file and use custom rote access check

# /web/modules/custom/bhimmu_styles/bhimmu_styles.routing.yml
bhimmu_styles.level:
  path: '/reward/my-level'
  defaults:
    _title: 'My Level'
    _controller: '\Drupal\bhimmu_styles\Controller\BhimmuStylesController::getMyLevel'
  requirements:
    _custom_access: '\Drupal\bhimmu_styles\Controller\BhimmuStylesController::levelCheck'

Step 2: Create a controller class that will define the logic for the custom route access checker task.

# /web/modules/custom/bhimmu_styles/src/Controller/BhimmuStylesController.php


namespace Drupal\bhimmu_styles\Controller;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Session\AccountInterface;

/**
 * Returns responses for Bhimmu Styles routes.
 */
final class BhimmuStylesController extends ControllerBase {

  /**
   * Check if user has earned any level.
   *
   * @param AccountInterface $account
   *   Current logged in user.
   *
   * @return AccessResultInterface
   *   Based on condition we can allow or denied the request.
   */
  public function levelCheck(AccountInterface $account): AccessResultInterface {
    // Perform some database queries and check if user can be alloed or not.
    $userQuery = $this->entityTypeManager()->getStorage('user')->getQuery();
    $userQuery->accessCheck()
      ->condition('uid', $account->id())
      ->condition('field_user_level.value', 1, '>=');
    $userQueryResult = $userQuery->execute();

    if (!empty($userQueryResult)) {
      return AccessResult::allowed();
    }
    return AccessResult::forbidden('You do not have achieved any level');
  }

  /**
   * Callback method to return level when user navigate to the /reward/my-level url.
   */
  public function getMyLevel() {
    return ['#markup' => $this->t('Here is your new level')];
  }

}

The getMyLevel method will return the level achieved by the current user.

The levelCheck method will check whether the user has earned any level or not.

Step 3: Next, we need to create a menu link that will be visible only if the user has a value greater than or equal to 1 in field_user_level.

The menu link can be created manually by the user admin interface, or you can create it via code. Code has one benefit over the manual process, which is that the link will be created automatically when you install this module.
 

# /web/modules/custom/bhimmu_styles/bhimmu_styles.links.menu.yml
bhimmu_styles.demo_link:
  title: 'My Level'
  description: 'Check your new level'
  parent: footer
  menu_name: footer
  route_name: bhimmu_styles.level

Now create two authenticated users and assign value 0 to user one and value 1 to user two. Install the module if it is not already installed. Clear the cache and see the result by logging in from newly created users. 

This is just an example of using custom route access checks, but in reality, you can do many more things.

For further information, view my YouTube video as well.