Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony: A Token was not found in the SecurityContext for route behind firewall

In my Symfony2 application, I built an exception listener which lets me know about unhandled errors.

I receive messages about the following error when bots are visiting my page, which is behind a firewall :

A Token was not found in the SecurityContext.

I also retrieve the following data:

User agent  Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)
Trace as string #0 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2951): Symfony\Component\Security\Http\Firewall\AccessListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#1 [internal function]: Symfony\Component\Security\Http\Firewall->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#2 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2205): call_user_func(Array, Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#3 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2138): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#4 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2299): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#5 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(3017): Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#6 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(2990): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#7 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(3139): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#8 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(2383): Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#9 /home/foodmeup.net/production/releases/20150527141710/web/app.php(28): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#10 {main}

This happens on about all my website pages since my last update but I can't figure out what the issue is. If I visit the page myself, there is no problem, no exception is raised.

My understanding of the firewall I set up was that in case somebody tries to access a protected resource, he is redirected to the login page without any error being thrown. Here, I fear that some users may land on an error page instead of being redirected to the login page. And when I want to replicate the error by visiting the referer from when the error is thrown, I am correctly redirected so I don't understand what are the cases when the error is thrown versus the user is redirected.

EDIT :

My exception listener service :

exception_listener:
    class: %exception_listener.class%
    arguments: [@router, @session, @security.token_storage, @email_manager, @doctrine, "@=service('kernel').getEnvironment()", @security.authorization_checker]
    tags:
        - { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: 250 }

My exception listener code:

<?php

namespace AppBundle\EventListener;

use AppBundle\Application\Core\EmailManager;
use AppBundle\Application\Core\JournalManager;
use AppBundle\Entity\User\User;
use AppBundle\Security\Voter\SubscriptionVoter;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;


class ExceptionListener
{
    /**
     * @var Router
     */
    private $router;

    /**
     * @var Session
     */
    private $session;

    /**
     * @var TokenStorage
     */
    private $tokenStorage;

    /**
     * @var EmailManager
     */
    private $emailManager;

    /**
     * @var null
     */
    private $environment;

    /**
     * @var AuthorizationChecker
     */
    private $authorizationChecker;
    /**
     * @var Registry
     */
    private $doctrine;

    public function __construct(Router $router, Session $session, TokenStorage $tokenStorage, EmailManager $emailManager, Registry $doctrine ,$environment=null, AuthorizationChecker $authorizationChecker)
    {
        $this->router = $router;
        $this->session = $session;
        $this->tokenStorage = $tokenStorage;
        $this->emailManager = $emailManager;
        $this->environment = $environment;
        $this->authorizationChecker = $authorizationChecker;
        $this->doctrine = $doctrine;
    }

    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        try {
            /** @var $exception */
            $exception = $event->getException();
            $request = $event->getRequest();
            $referer = $request->headers->get('referer');

            $manager = $this->doctrine->getManager('logging');
            $journalManager = new JournalManager($manager);

            if($exception->getCode() == 403)
            {
                if ($this->authorizationChecker->isGranted(SubscriptionVoter::HAS_SUBSCRIPTION) && !$this->authorizationChecker->isGranted(SubscriptionVoter::SUBSCRIPTION_VALID))
                {
                    $this->session->getFlashBag()->add('warning',"La page précédente n'est pas accessible avec ce portfolio car le paiement n'est pas à jour. Vous devez actualiser votre paiement.");
                    $response = new RedirectResponse($this->router->generate('renew_subscription'));
                }
                else
                {
                    $this->session->getFlashBag()->add('warning',"La page précédente n'est pas accessible avec vos droits d'accès et vous avez été redirigé vers l'accueil du site.");
                    $response = new RedirectResponse($this->router->generate('home'));
                }

                $event->setResponse($response);
            }
            elseif ($exception->getMessage() == "Couldn't connect to host, Elasticsearch down?" || $exception->getCode() == 52)
            {
                $this->session->getFlashBag()->add('warning', "La service de recherche du site a arrêté de fonctionner. Renouvellez votre dernière action si celle si n'a pas été suivie d'effet d'ici 2 minutes.");
                $event->setResponse(new RedirectResponse($request->headers->get('referer') ?: $this->router->generate('home')));
            }
            elseif (
                !($exception->getCode() == 404 && !$referer) &&
                !($this->contains($exception->getMessage(), array('object not found', 'A Token was not found in the SecurityContext', 'No route found for')) && !strpos($referer, 'foodmeup')) &&
                !in_array($this->environment, array('dev', 'test')) &&
                !$journalManager->errorExists($exception, $request->getUri(), 1)
            )
            {
                $user = is_object($this->tokenStorage->getToken()) ? $this->tokenStorage->getToken()->getUser() : null;
                $user = $user instanceOf User ? $user : null;
                $code = $exception->getCode();

                $this->emailManager->sendEmail(
                    '[email protected]',
                    '[email protected]',
                    ':Core/Email:error.html.twig',
                    "Une erreur $code s'est produite sur le site",
                    array(
                        'date' => new \DateTime(),
                        'user' => $user,
                        'exception' => $exception,
                        'referer' => $request->headers->get('referer'),
                        'current' => $request->getUri(),
                        'user_agent' => $_SERVER['HTTP_USER_AGENT']
                    )
                );

                $journalManager->addErrorLog($exception, $request->getUri());
            }
        } catch (\Exception $e)
        {
        }

    }

    private function contains($str, array $arr)
    {
        foreach($arr as $a) {
            if (stripos($str,$a) !== false) return true;
        }
        return false;
    }
}

My firewalls:

firewalls:
    dev:
        pattern:  ^/(_(profiler|wdt)|css|images|js)/
        security: false
    guest:
        pattern:        /(public/|$|genemu-captcha-refresh|media/cache/)
        anonymous:      true
        context:        main_auth
    main:
        pattern:        ^/
        anonymous:      false
        provider:       main
        context:        main_auth
        switch_user: { role: ROLE_ADMIN, parameter: _switch_user_parameter }
        form_login:
            login_path: fos_user_security_login
            check_path: fos_user_security_check
            success_handler: authentication_site_handler
        logout:
            path:       fos_user_security_logout
            target:     /
        remember_me:
            key:      "%secret%"
            lifetime: 86400 #en secondes
            path:     /
            domain:   ~ # Prend la valeur par défaut du domaine courant depuis $_SERVER
        oauth:
            remember_me: true
            resource_owners:
                facebook:           "/loginhwi/check-facebook"
                github:             "/loginhwi/check-github"
                google:             "/loginhwi/check-google"
                twitter:            "/loginhwi/check-twitter"
                linkedin:           "/loginhwi/check-linkedin"
                flickr:             "/loginhwi/check-flickr"
            login_path:        fos_user_security_login
            check_path:        fos_user_security_check
            failure_path:      fos_user_security_login
            success_handler:  authentication_site_handler
            oauth_user_provider:
                service: fosubuser.provider

Precision :

  • The emailManager just sends an email with from the given parameters. it works fine
  • The journalManager just logs the error and helps me filter out errors already logged
like image 569
Sébastien Avatar asked May 28 '15 09:05

Sébastien


1 Answers

Don not worry about this case/error. When you tested you case by yourself, you got expected behavior (redirect to login page) as any other "real" surfer.

Then

User agent Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)

You can see that the page is requested by Ahrefs bot. And when you get a redirect to other pages as real surfer, it uses "header" action. But bots don't handle headers. So actually it is an error that amateur programmers make often. They put something like

if($notallowed){
 header('Location: /login');
}
//... only logged stuff ...// 

and then it works for "real" surfers, but bots can go through and reach "logged stuff". So in this case it either needs "die" command right after header (in bad style that must be never user) or throw exception (in good style).

So conclusion: You mentioned that you recently got the exception after you changed the code, but most probably the bot started to crawl something new that was not crawled before. So most probably you should add a rule to skip that exception as it is applied to bots only. But of course you also should review you recent changes. Also you can review log of such exception and check user agent to make sure that it is applied to bots only.

like image 193
Evgeniy Kuzmin Avatar answered Oct 11 '22 05:10

Evgeniy Kuzmin