I want to redirect an anonymous user to the login form if such user encounters a 403 error.

I have created event subscriber and this is my code, but I end up on loop on the current page.

/**
   * Redirect anonymous user to login page if he encounters 404 or 403
   * response.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $response
   *   The created response object that will be returned.
   * @param string $event
   *   The string representation of the event.
   * @param \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher $event_dispatcher
   *   Event dispatcher that lazily loads listeners and subscribers from the dependency injection
   *   container.
   */
  public function checkLoginNeeded(GetResponseEvent $response, $event, ContainerAwareEventDispatcher $event_dispatcher) {
    $routeMatch = RouteMatch::createFromRequest($response->getRequest());
    $route_name = $routeMatch->getRouteName();
    $is_anonymous = \Drupal::currentUser()->isAnonymous();
    $is_not_login = $route_name != 'user.login';

    if ($is_anonymous && $route_name == 'system.403' && $is_not_login) {
      $query = $response->getRequest()->query->all();
//      $query['destination'] = $routeMatch->getRouteObject()->getPath();
      $query['destination'] = \Drupal::url('<current>');
      $login_uri = \Drupal::url('user.login', [], ['query' => $query]);
      $returnResponse = new RedirectResponse($login_uri, Response::HTTP_FOUND);
      $response->setResponse($returnResponse);
    }
  }

I think this is related to the fact that the response already contains destination(current uri) and system.404 and system.403 have some high priority that prevent me to override this.

  • Can you add your entire class instead of just the method? – googletorp Jul 17 '15 at 13:33
  • There is only $events[KernelEvents::REQUEST] = 'checkLoginNeeded'; in the getSubscribedEvents() method. – user21641 Jul 17 '15 at 15:05

Just looking at the title of this question (= How to redirect user to login form from 403?), there are 2 options to do so, without any custom code involved, as detailed below.

Option 1: Use the CustomError module

The CustomError module allows the site admin to create custom error pages for HTTP status codes 403 (access denied) and 404 (not found), without creating nodes for each of them. Some more details about its features (from its project page):

  • Configurable page title and descriptions.
  • There are no author and date/time headers as with normal nodes.
  • Any HTML formatted text can be be put in the page body.
  • The error pages are themable.
  • Users who are not logged in and try to access an area that requires login will be redirected to the page they were trying to access after they login.
  • Allows custom redirects for 404s.

You'll probably be interested mostly in the part about "Users who are not logged in and try to access an area that requires login will be redirected to the page they were trying to access after they login.".

For D8 there is a 8.x-1.x-dev version for this module available also, more details about it can be found in Issue # 2219227.

Option 2: Use the Rules module

Assume the path of the "Default 403" page is set to no_access (via admin). Then create a rule using the Rules module, with as Event something like "After visiting node no_access". So that the entire rule would look something like so:

  • Events: After visiting node no_access
  • Conditions:

    1. User has role(s) - Parameter: User: [site:current-user], Roles: anonymous user
    2. NOT Text comparison - Parameter: Text: [site:current-page:url], Matching text: user/login
  • Actions: Page redirect - Parameter: URL: user/login

Should you want to do so, you could even add another Action to also display some (informational) message in the Drupal message area, with something like "You tried to visit a page for which login is required ...".

True, it might require you to enable an extra contributed module (Rules). But, as indicated by its growing popularity also, that module is probably already enabled in mostly any site (similar to the Views module), because there are dozens of use-cases for this module.

For D8 there is a 8.x-3.x-alfa1 version for this module available also, more details about its D8 version can be found in Issue # 2574691, which includes a link to the Rules issue "Rules 8.x Roadmap"

If the above doesn't help, then you might want to check if the reason for your loop (or "some high priority" as in your question) cannot be prevented/explained by something similar to the answer to the question "How to specify a Rules event like "Content is 'going to be' viewed"?".

The easiest way is to browse to /admin/config/system/site-information and fill in the field that reads Default 403 (access denied) page with user/login

  • 7
    That is a good solution but it does not take into account the fact that the user might be already logged in which I mentioned in the description. – user21641 Aug 7 '15 at 8:40

I've seen that this question was never answered how to do this programmatically. The code actually does work, when placed in an Exception Subscriber:

/src/EventSubscriber/RedirectOn403Subscriber.php:

<?php

namespace Drupal\mymodule\EventSubscriber;

use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;

class RedirectOn403Subscriber extends HttpExceptionSubscriberBase {

  protected $currentUser;

  public function __construct(AccountInterface $current_user) {
    $this->currentUser = $current_user;
  }

  protected function getHandledFormats() {
    return ['html'];
  }

  public function on403(GetResponseForExceptionEvent $event) {
    $request = $event->getRequest();
    $is_anonymous = $this->currentUser->isAnonymous();
    $route_name = $request->attributes->get('_route');
    $is_not_login = $route_name != 'user.login';
    if ($is_anonymous && $is_not_login) {
      $query = $request->query->all();
      $query['destination'] = Url::fromRoute('<current>');
      $login_uri = Url::fromRoute('user.login', [], ['query' => $query])->toString();
      $returnResponse = new RedirectResponse($login_uri);
      $event->setResponse($returnResponse);
    }
  }

}

mymodule.services.yml:

services:
  mymodule.exception403.subscriber:
    class: Drupal\mymodule\EventSubscriber\RedirectOn403Subscriber
    tags:
      - { name: event_subscriber }
    arguments: ['@current_user']
  • 2
    This works and is probably the best answer until the relevant modules (Rules included) have stable releases. I had to add ->toString() to $query['destination'] = Url::fromRoute('<current>') to get this to work – Colin Shipton Dec 11 '17 at 12:11

I guess the solution to your problem is simple. You can easily create a custom page for 404 and 403 then inside this page, redirect anonymous users to the /user page.

You can use this code

function YOURTHEME_preprocess_page(&$vars) {
   $header = drupal_get_http_header('status'); 
   if ($header == '404 Not Found') {     
       $vars['theme_hook_suggestions'][] = 'page__404';
   }
}

Whenever 404 occurs your page--404.tpl.php will be used. In this page use this code

if(user_is_logged_in() == false){
       /// You can do anything here.
}

The How to make custom 403 and 404 page in Drupal explains another method of creating a page for 403 and 404.

  • 4
    Hey mate, you should check the tags and the code. This guy is clearly using Drupal 8. – alexej_d Jul 17 '15 at 14:56
  • 1
    hi, I gave him the algorithm, he can easily change it to 8 format – Drupalist Jul 18 '15 at 6:38

The easiest way to do that is to use the Redirect 403 to User Login module (a dev version of it exists for D8). Here is a quote about it (from its project page):

Redirect the HTTP 403 error page to the Drupal /user/login page with an optional message that reads:

"Access denied! You must login to view this page."

Also, the desired page is appended in the url query string so that, once login is successful, the user is taken directly where they were originally trying to go.

  • 1
    Why is this answer downvoted? – Miloš Kroulík Jul 22 '15 at 14:18
  • @milos.kroulik I guess because the question is about Drupal 8 but that module is for 7 and 6 – Drupalist Jul 25 '15 at 3:20
  • 1
    No that module has a Drupal 8 version in development – Colin Shipton Jul 28 '15 at 11:39
  • 1
    Pretty sure the downvote was because of the answer being a "link-only" answer (a typical reason for answers being downvoted, or even deleted ...). Such answers typically also show up in the "low quaity answers" review queue ... Refer to my edited version of this answer now for an enhanced verion of it (no longer at risk of being considered as a link only answer). – Pierre.Vriens Jul 28 '15 at 13:20
  • This answer does not take into account if the user is already logged or if the site is using fast404 and 403. – user21641 Jul 28 '15 at 21:16

(1) create a page node with an alias like "/my403.html"

(2) go to /admin/config/system/site-information and set "/my403.html" as 403-Errorpage

(3) go to /admin/structure/block and add the "User login" block (Sec. Forms) to the main content region. Restrict block's appearance to page "my403.html" and role "guest".

That's it.

If you need more control, create a controller for your 403 page and add the UserLoginForm by hand.

Best regards, Rainer

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.