Cannot masquerade - access denied & logged out after masquerade

Created on 11 December 2018, over 6 years ago
Updated 9 May 2023, about 2 years ago

I have a D8.6.1 site with Masquerade enabled. I recently upgraded to 8.6.1 and the masquerade functionality started failing. Here's what happens:

1. I am logged in as admin, go to People and choose "Masquerade as" for a user (have tried several, admin and non-admin)
2a. If the user I'm masq'ing is an admin, I am redirected to the same People screen I was on, receive an "Access denied" message, and appear to be completely logged out.
2b. If the user I'm masq'ing is a non-admin, I am redirected to the front page and I am completely logged out.
3. When I try to log in again as the admin user, I am redirected to "user/1" and receive an Access Denied message and remain logged out
4. If I try once more to log in as the admin user, it succeeds and I am redirected to "user/1" which loads successfully.

It seems that the attempt to masq is causing some sort of authentication confusion that lasts through the next login attempt at which point it is somehow reset and works normally.

๐Ÿ› Bug report
Status

Active

Version

2.0

Component

Code

Created by

๐Ÿ‡บ๐Ÿ‡ธUnited States jptillman

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States frob US

    I think this is more of a Legal module issue. Here is a link to the issue in the legal module https://www.drupal.org/project/legal/issues/2638224 ๐Ÿ› Add exemption api to allow a Masquerade module exemption Needs review

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia Raveen Kumar

    Raveen Thakur โ†’ made their first commit to this issueโ€™s fork.

  • This can't be a bug with Masquerade on its own, as it's working fine.

  • ๐Ÿ‡ต๐Ÿ‡นPortugal introfini

    I encountered and solved this exact issue. The root cause is stale CSRF tokens being cached after session regeneration during the masquerade process.

    Root Cause:

    The masquerade process calls $this->sessionManager->regenerate() in Masquerade::switchUser() which invalidates the CSRF token seed stored in session metadata. When masquerade URLs are cached (which happens with lazy builders and entity operations), the cached CSRF tokens become invalid after the first masquerade attempt.

    Solution:

    I created a custom route processor to ensure fresh CSRF tokens are always generated for masquerade routes. This single solution fixes the issue completely:

    1. Create a custom route processor

    src/Routing/MasqueradeRouteProcessor.php:

    
    namespace Drupal\[YOUR_MODULE]\Routing;
    
    use Drupal\Core\Access\CsrfTokenGenerator;
    use Drupal\Core\Render\BubbleableMetadata;
    use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
    use Symfony\Component\Routing\Route;
    
    /**
     * Processes outbound routes for masquerade to ensure fresh CSRF tokens.
     * 
     * Issue: Masquerade fails with 403 Access Denied on second attempt due to
     * stale CSRF tokens being cached. The masquerade process regenerates the
     * session ID which invalidates previously cached CSRF tokens.
     */
    class MasqueradeRouteProcessor implements OutboundRouteProcessorInterface {
    
      protected $csrfTokenGenerator;
    
      public function __construct(CsrfTokenGenerator $csrf_token_generator) {
        $this->csrfTokenGenerator = $csrf_token_generator;
      }
    
      public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
        // Only process masquerade routes
        if ($route_name === 'entity.user.masquerade') {
          // Force uncacheable to prevent stale CSRF tokens
          if ($bubbleable_metadata) {
            $bubbleable_metadata->setCacheMaxAge(0);
            $bubbleable_metadata->addCacheContexts(['session']);
          }
          
          // Generate a fresh CSRF token for this specific route
          $path = ltrim($route->getPath(), '/');
          foreach ($parameters as $param => $value) {
            $path = str_replace("{{$param}}", $value, $path);
          }
          
          // Add fresh token to parameters
          if (!isset($parameters['token'])) {
            $parameters['token'] = $this->csrfTokenGenerator->get($path);
          }
        }
      }
    }
    

    2. Register the service

    In your module's services.yml:

    services:
    your_module.masquerade_route_processor:
    class: Drupal\your_module\Routing\MasqueradeRouteProcessor
    arguments: [ '@csrf_token' ]
    tags:
    - { name: route_processor_outbound, priority: 1000 }

    Result:

    This solution ensures that:

    • Fresh CSRF tokens are generated for every masquerade request
    • Masquerade URLs are never cached to prevent stale token reuse
    • The fix works consistently across multiple masquerade attempts

    Masquerade now works reliably on subsequent attempts without the 403 Access Denied error.

    This fix addresses the core issue without requiring changes to the masquerade module itself, making it a safe workaround until the module is updated to handle this scenario properly.

Production build 0.71.5 2024