14

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.

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

8 Answers 8

20

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\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
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(ExceptionEvent $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>')->toString();
      $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
  • 5
    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 Dec 11, 2017 at 12:11
  • 4
    This answer deserves to show up higher !
    – pbonnefoi
    Jan 30, 2019 at 14:07
14

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

1
  • 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, 2015 at 8:40
9

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"?".

0
4

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.

2
  • 5
    Hey mate, you should check the tags and the code. This guy is clearly using Drupal 8.
    – alexej_d
    Jul 17, 2015 at 14:56
  • 1
    hi, I gave him the algorithm, he can easily change it to 8 format
    – M a m a D
    Jul 18, 2015 at 6:38
3

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.

5
  • 1
    Why is this answer downvoted? Jul 22, 2015 at 14:18
  • @milos.kroulik I guess because the question is about Drupal 8 but that module is for 7 and 6
    – M a m a D
    Jul 25, 2015 at 3:20
  • 1
    No that module has a Drupal 8 version in development Jul 28, 2015 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). Jul 28, 2015 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, 2015 at 21:16
0

(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

0

Take a look at Redirect 403 to User Login module can help you.

-1

function THEME_preprocess_page(&$variables) {

global $base_url;

$is_anonymous = \Drupal::currentUser()->isAnonymous();

$route_name = \Drupal::routeMatch()->getRouteName();

$is_not_login = $route_name != 'user.login';

if ($is_anonymous && $route_name == 'system.403' && $is_not_login) {

$response = new RedirectResponse( $base_url . '/user/login' );
$response->send();

}

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.