Login is verplicht

Drupal 9

Een enkele keer heb je een website waar (een deel van) de pagina’s alleen toegankelijk mogen zijn na inloggen. De hier beschreven Drupal module maakt dit eenvoudig: als een bezoeker niet ingelogd is, dan wordt automatisch eerst het inlogscherm getoond waarna, als ingelogd is, automatisch de oorspronkelijk gewenste pagina wordt getoond.

Hieronder wordt ervan uitgegaan dat altijd ingelogd moet worden. Als maar voor een deel van de pagina’s ingelogd moet worden, kijk dan naar de module „Leden toegang” op de pagina „Afgeschermde pagina’s”.

De module bestaat uit 4 bestanden, waarvan de eerste drie (de .module en de twee .yml bestanden) in de directory / map van de module komen en het .php bestand in de subdirectory src/EventSubscriber moet staan (conform de Drupal 8/9 conventie).

login_required.module

<?php
 
/**
 * @file
 * Login required module.
 */
 
use Drupal\Core\Routing\RouteMatchInterface;
 
/**
 * Implements hook_help().
 */
function login_required_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.login_required':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>'. t('Login required for visitors.') . '</p>';
      return $output;
 
    default:
  }
}

login_required.info.yml

name: Login Required
description: Login required for visitors
type: module
core_version_requirement: ^9
package: User

login_required.services.yml

services:
  login_required.redirect_subscriber:
    class: \Drupal\login_required\EventSubscriber\LoginRequiredRedirectSubscriber
    arguments: ['@current_user', '@current_route_match']
    tags:
      - { name: event_subscriber, priority: 200 }

LoginRequiredRedirectSubscriber.php

<?php
 
namespace Drupal\login_required\EventSubscriber;
 
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\Routing\LocalRedirectResponse;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
 
/**
 * Event subscriber for redirecting to the loginpage.
 *
 * Subscribes to the Kernel Request event and redirects to the loginpage
 * when the user is not logged in.
 */
class LoginRequiredRedirectSubscriber implements EventSubscriberInterface {
  use RedirectDestinationTrait;
 
  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;
 
  /**
   * The current route match.
   *
   * @var \Drupal\Core\Routing\RouteMatchInterface
   */
    protected $currentRouteMatch;
 
  /**
   * LoginRequiredRedirectSubscriber constructor.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
   *   The current user.
   * @param \Drupal\Core\Routing\RouteMatchInterface $currentRouteMatch
   *   The current route match.
   */
  public function __construct(AccountProxyInterface $currentUser, RouteMatchInterface $currentRouteMatch) {
    $this->currentUser = $currentUser;
    $this->currentRouteMatch = $currentRouteMatch;
  }
 
  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = ['RequiredLogin', 0];
    return $events;
  }
 
  /**
   * Handler for the kernel request event.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
   *   The event.
   */
  public function RequiredLogin(GetResponseEvent $event) {
    // Allready logged in?
    if ($this->currentUser->isAuthenticated()) {
      return;
    }
 
    // Get the route name.
    $route_name = $this->currentRouteMatch->getRouteName();
 
    // Test for system errors (403, 404, ...).
    // When not allowed to register, trying to, results in a system error.
    if (substr($route_name, 0, 7) == 'system.') {
      return;
    }
 
    // Test for any user login, password, register, reset page.
    if (in_array($route_name, ['user.login', 'user.pass', 'user.register',
        'user.reset', 'user.reset.login', 'user.reset.form'])) {
      return;
    }
 
    // Get the route from the user.routing.yml file.
    $url = Url::fromRoute('user.login', [], ['query' => $this->getDestinationArray()]);
    $event->setResponse(new LocalRedirectResponse($url->toString()));
  }
}

Hoe werkt het?

In services.yml geven we op dat we een redirect (een omleiding) gaan maken via LoginRequiredRedirectSubscriber waarvoor het bestand LoginRequiredRedirectSubscriber.php wordt gebruikt. Hierbij willen we 2 soorten gegevens ontvangen: de huidige gebruiker (current_user) en de huidige route (current_route_match). Daarvoor gaan we de Drupal event_subscriber gebruiken. We zetten de prioriteit op een hoge waarde, zodat deze module vóór de originele modules van de routes wordt afgehandeld.
Iedere module die een zichtbare pagina heeft kent een routing.yml bestand waarin de route staat met de daarbij behorende uri ofwel het interne adres van de pagina. De uri (of de alias ervan) vormt samen met de domeinnaam de url ofwel het externe adres van de pagina.

In het .php bestand, waar het echte werk wordt gedaan, ontvangen we in de contructor de opgevraagde gegevens die in vervolgens $currentUser en $currentRouteMatch worden gezet.
In de functie getSubscribedEvents() wordt gekeken of het gewenste event optreedt (in dit geval: een pagina wordt opgevraagd). Zo ja, dan wordt de hierin genoemde functie RequiredLogin aangeroepen.

In RequiredLogin() wordt eerst gekeken of de bezoeker al ingelogd is. Is dat niet zo, dan vragen we de naam van de route op. Is dit een foutboodschap dan begint de naam met „system.”, bijivoorbeeld system.403. Die hoeft natuurlijk niet omgeleid te worden naar de loginpagina.
Als het een login-, password opvraag-, aanmelding voor login- of een wachtwoord herstelpagina is, dan hoeven we ook verder niets te doen.

Alle andere pagina’s worden wel omgeleid naar de loginpagina. Daarbij wordt de originele opgevraagde pagina meegegeven in een „?destination=” string, zodat na de loginpagina automatisch de originele pagina wordt geopend.