logo

Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll

Restrict access to Taxonomy terms vocabulary using Event Subscriber

11/06/2025, by Ivan

Menu

Sometimes you need fixed, permanent Categories on the site, which should be updated accedentially. In this case you can utilize custom code with Event Subscribe. 

Let's add a new Event Subscriber class in custom module.

drupalbook_custom.services.yml

services:  
  drupalbook_custom.tag_redirect_subscriber:
    class: Drupal\drupalbook_custom\EventSubscriber\TagRedirectSubscriber
    arguments:
      - '@entity_type.manager'
      - '@current_user'
    tags:
      - { name: event_subscriber }

And include our Event Subscriber in drupalbook_custom/src/EventSubscriber/TagRedirectSubscriber:

<?php

namespace Drupal\drupalbook_custom\EventSubscriber;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Redirects non-administrators from Tag vocabulary admin pages.
 *
 * A Request-level subscriber runs early, allowing us to short-circuit the
 * request and return a redirect response before the matched controller
 * executes.
 */
class TagRedirectSubscriber implements EventSubscriberInterface {

  /**
   * The entity-type manager service.
   *
   * Kept as an example dependency; not strictly required for the current
   * logic but useful if future enhancements require entity loading.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * The current user proxy service.
   *
   * Used for quick role checks in order to bypass the redirect for
   * administrators.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

  /**
   * Constructs the subscriber.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity-type manager.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   The user currently making the request.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    AccountProxyInterface $current_user,
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    // Priority 32 ensures route parameters are available but the controller
    // has not yet executed.
    return [
      KernelEvents::REQUEST => ['onKernelRequest', 32],
    ];
  }

  /**
   * Performs the redirect when a non-administrator accesses Tag admin routes.
   *
   * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
   *   The kernel event carrying the request.
   */
  public function onKernelRequest(RequestEvent $event): void {
    // Only act on the main (master) request.
    if (!$event->isMainRequest()) {
      return;
    }

    // Allow administrators through without redirection.
    if ($this->currentUser->hasRole('administrator')) {
      return;
    }

    $request = $event->getRequest();
    $route_name = $request->attributes->get('_route');

    // Destination for all blocked attempts.
    $redirect_to = 'https://drupalbook.org/admin/structure'
      . '/taxonomy/manage/tag/overview';

    switch ($route_name) {
      case 'entity.taxonomy_vocabulary.overview_form':
      case 'entity.taxonomy_vocabulary.overview_terms':
      case 'entity.taxonomy_term.add_form':
        // Confirm we are dealing with the "tag" vocabulary.
        $vocabulary = $request->attributes->get('taxonomy_vocabulary');
        if (!empty($vocabulary) && $vocabulary->id() === 'tag') {
          $event->setResponse(new TrustedRedirectResponse($redirect_to));
        }
        return;

      case 'entity.taxonomy_term.edit_form':
      case 'entity.taxonomy_term.delete_form':
        /** @var \Drupal\taxonomy\Entity\Term|null $term */
        $term = $request->attributes->get('taxonomy_term');
        // bundle() returns the vocabulary machine name.
        if ($term && $term->bundle() === 'tag') {
          $event->setResponse(new TrustedRedirectResponse($redirect_to));
        }
        return;

      default:
        return;
    }
  }

}

The TagRedirectSubscriber class is a custom Event Subscriber for Drupal, designed to restrict access to administration pages of a specific taxonomy vocabulary (in this case, "tag") to non-administrators. Here’s a breakdown of its structure and the key valuable aspects found in the code:

1. Purpose and Use Case

  • Goal: Prevent accidental or unauthorized updates to the "tag" vocabulary by redirecting non-admin users away from its admin routes.
  • Benefit: Provides a layer of UI/UX-based access control for critical taxonomy vocabularies, enforcing stability for fixed categories.

2. Class Structure and Dependencies

  • The class implements EventSubscriberInterface, making it compatible with Symfony’s event system used by Drupal.
  • Dependencies injected via constructor:
    • EntityTypeManagerInterface: Included for potential future entity operations. Not required for current logic, but allows for easy extensibility.
    • AccountProxyInterface: Used to retrieve and check the current user's roles efficiently.
    Dependency injection ensures the subscriber is testable, reusable, and conforms to Drupal’s service container best practices.

3. Subscribed Events

  • The class subscribes to the KernelEvents::REQUEST event with a priority of 32.
    This priority ensures:
    • Route parameters are available (routing is resolved).
    • The controller for the route has NOT been executed yet, allowing the subscriber to intercept and short-circuit the request with a redirect if needed.

4. Redirection Logic

  • The onKernelRequest() method performs all access checks and redirect logic:
    • Acts only on main requests: Avoids duplicate handling on sub-requests.
    • Allows administrators: If the user has the administrator role, access is always permitted.
    • Checks route names: Only certain routes related to the taxonomy vocabulary or its terms are considered.
    • Redirects non-admins:
      • For overview, add, or list routes (entity.taxonomy_vocabulary.overview_form, entity.taxonomy_vocabulary.overview_terms, entity.taxonomy_term.add_form), it checks if the vocabulary is tag.
      • For edit and delete routes (entity.taxonomy_term.edit_form, entity.taxonomy_term.delete_form), it checks if the term’s bundle() (vocabulary machine name) is tag.
    • Uses a trusted redirect: If conditions match, the user is redirected to a safe admin overview page for the "tag" vocabulary.
  • Extensibility: The logic is easy to extend for additional vocabularies or roles by adjusting conditions.

5. Security and Best Practices

  • Early interception: By running on the request event, the subscriber can enforce access before any sensitive data is processed or displayed.
  • Role-based bypass: Efficiently checks user roles to avoid blocking site builders or admins.
  • Clear separation of concerns: Keeps routing logic, user checks, and redirection cleanly separated for maintainability.

6. Potential Enhancements

  • Since EntityTypeManagerInterface is injected, you can easily add entity-based checks in the future (e.g., permissions based on specific term properties or related content).
  • You could generalize the class to handle multiple vocabularies or to provide customizable redirects via config.

7. Key Takeaways

  • This Event Subscriber demonstrates a practical approach for access control in Drupal, leveraging Symfony’s event-driven architecture for early, efficient request handling.
  • The approach is ideal for protecting taxonomy vocabularies that should be managed only by trusted users, reducing the risk of accidental changes.