Question

I've got an issue with Symfony 2.4(.4) that has been driving me nuts for 3 days. I'm using FOSUserBundle and CouchDB to authenticate my users through a basic login form. I've setup a firewall to protect the all site.

The logs show the authentication is successful but the session token is never set and thus Symfony brings me back to the login page. I know the authentication is successful because if I submit a wrong login/password I get an error message, so CouchDB is not an issue.

Here is what I've already done with no luck :

  • upgraded symfony to the last version
  • checked session : OK (I'm able to manually set/get a session variable) through all pages
  • checked that token is set in Symfony\Component\Security\Http\Firewall\ContextListener::onKernelResponse() : OK
  • modified NativeSessionStorage:regenerate() to avoid session regeneration : no effet
  • add a context variable in my firewall : no effet
  • cleared all cookies : no effect
  • put multiple breakpoints on every Listner/Handler/Dispatcher/ I could think of : found nothing

The only thing that allows me to see my main page is to comment the whole content of Fos\UserBundle\Model\User::serialize() but then of course I'm not even authenticated anonymously so I can't access any user information.

I've been playing around with Symfony for a few years now and even if I'm not an expert I've never had such a blocking issue. I'm SURE this is a stupid mistake I've made that is under my nose but I just can't find it.

Here is my security.yml

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false
            anonymous: ~
        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                csrf_provider: form.csrf_provider
            logout:  true
            anonymous: true

    access_control:
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/bo, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/, role: ROLE_ADMIN }
        - { path: ^/, role: ROLE_USER }

Here is the log file

[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Bundle\FrameworkBundle\EventListener\SessionListener::onKernelRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\FragmentListener::onKernelRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest". [] []
[2014-04-28 19:37:43] request.INFO: Matched route "fos_user_security_check" (parameters: "_controller": "FOS\UserBundle\Controller\SecurityController::checkAction", "_route": "fos_user_security_check") [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.request" to listener "FOS\RestBundle\EventListener\BodyListener::onKernelRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\Security\Http\Firewall::onKernelRequest". [] []
[2014-04-28 19:37:43] security.INFO: User "user3@user.fr" has been authenticated successfully [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "security.interactive_login" to listener "FOS\UserBundle\EventListener\LastLoginListener::onSecurityInteractiveLogin". [] []
[2014-04-28 19:37:43] event.DEBUG: Listener "Symfony\Component\Security\Http\Firewall::onKernelRequest" stopped propagation of the event "kernel.request". [] []
[2014-04-28 19:37:43] event.DEBUG: Listener "Symfony\Bundle\AsseticBundle\EventListener\RequestListener::onKernelRequest" was not called for event "kernel.request". [] []
[2014-04-28 19:37:43] event.DEBUG: Listener "Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener::injectLogger" was not called for event "kernel.request". [] []
[2014-04-28 19:37:43] event.DEBUG: Listener "Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener::injectLogger" was not called for event "kernel.request". [] []
[2014-04-28 19:37:43] event.DEBUG: Listener "Stof\DoctrineExtensionsBundle\EventListener\BlameListener::onKernelRequest" was not called for event "kernel.request". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\Security\Http\Firewall\ContextListener::onKernelResponse". [] []
[2014-04-28 19:37:43] security.DEBUG: Write SecurityContext in the session [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\ResponseListener::onKernelResponse". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\Security\Http\RememberMe\ResponseListener::onKernelResponse". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.response" to listener "Sensio\Bundle\FrameworkExtraBundle\EventListener\HttpCacheListener::onKernelResponse". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelResponse". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener::onKernelResponse". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.response" to listener "Symfony\Component\HttpKernel\EventListener\StreamedResponseListener::onKernelResponse". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.finish_request" to listener "Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelFinishRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.finish_request" to listener "Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelFinishRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.finish_request" to listener "Symfony\Component\Security\Http\Firewall::onKernelFinishRequest". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.terminate" to listener "Symfony\Bundle\SwiftmailerBundle\EventListener\EmailSenderListener::onTerminate". [] []
[2014-04-28 19:37:43] event.DEBUG: Notified event "kernel.terminate" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelTerminate". [] []
[2014-04-28 19:37:44] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelRequest". [] []
[2014-04-28 19:37:44] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Bundle\FrameworkBundle\EventListener\SessionListener::onKernelRequest". [] []
[2014-04-28 19:37:44] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\FragmentListener::onKernelRequest". [] []
[2014-04-28 19:37:44] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest". [] []
[2014-04-28 19:37:44] request.INFO: Matched route "ocp_front_default_index" (parameters: "_controller": "OCP\FrontBundle\Controller\DefaultController::indexAction", "_route": "ocp_front_default_index") [] []
[2014-04-28 19:37:44] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest". [] []
[2014-04-28 19:37:44] event.DEBUG: Notified event "kernel.request" to listener "FOS\RestBundle\EventListener\BodyListener::onKernelRequest". [] []
[2014-04-28 19:37:44] event.DEBUG: Notified event "kernel.request" to listener "Symfony\Component\Security\Http\Firewall::onKernelRequest". [] []
[2014-04-28 19:37:44] security.DEBUG: Read SecurityContext from the session [] []
[2014-04-28 19:37:44] security.DEBUG: Reloading user from user provider. [] []
[2014-04-28 19:37:44] security.WARNING: Username "" could not be found. [] []
[2014-04-28 19:37:44] security.INFO: Populated SecurityContext with an anonymous Token [] []

Here is the User.php

<?php

namespace OCP\BackBundle\CouchDocument;

use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ODM\CouchDB\Mapping\Annotations as CouchDB;

/**
 * User
 *
 * @CouchDB\Document
 */
class User extends BaseUser
{
    /**
     * @var integer
     *
     * @CouchDB\Id
     */
    protected $id;

    /**
     * @var string
     *
     * @CouchDB\Field(type="string")
     * @Assert\NotBlank(message="Vous devez spécifier un prénom.")
     * @Assert\Length(
     *      min = "2",
     *      max = "20",
     *      minMessage = "Le prénom doit faire au moins {{ limit }} caractères.",
     *      maxMessage = "Le prénom ne doit pas faire plus de {{ limit }} caractères."
     * )
     */
    protected $firstname;

    /**
     * @var string
     *
     * @CouchDB\Field(type="string")
     * @Assert\NotBlank(message="Vous devez spécifier un nom.")
     * @Assert\Length(
     *      min = "2",
     *      max = "20",
     *      minMessage = "Le nom doit faire au moins {{ limit }} caractères.",
     *      maxMessage = "Le nom ne doit pas faire plus de {{ limit }} caractères."
     * )
     */
    protected $lastname;

    /**
     * @var string
     *
     * @CouchDB\Field(type="string")
     * @Assert\Length(
     *      min = "10",
     *      max = "10",
     *      minMessage = "Le numéro de téléphone doit faire au moins {{ limit }} chiffres.",
     *      maxMessage = "Le numéro de téléphone ne peut pas faire plus de {{ limit }} chiffres."
     * )
     * @Assert\Regex(
     *     pattern="/^[0][0-9]{9}$/",
     *     match=true,
     *     message="Le numéro de téléphone ne peut contenir des chiffres."
     * )
     */
    protected $phoneNumber;

    /**
     * @var string
     *
     * @CouchDB\Field(type="string")
     * @Assert\Length(
     *      min = "1",
     *      max = "10",
     *      minMessage = "Le code commercial doit faire au moins {{ limit }} chiffres.",
     *      maxMessage = "Le code commercial ne peut pas faire plus de {{ limit }} chiffres."
     * )
     */
    protected $commercialCode;

    /**
     * @var string
     *
     * @CouchDB\Field(type="string")
     */
    protected $tmpPassword;

    /**
     * @var \DateTime
     *
     * @Gedmo\Timestampable(on="create")
     * @CouchDB\Field(type="datetime")
     */
    private $creationDate;

    /**
     * @var \DateTime
     *
     * @Gedmo\Timestampable(on="update")
     * @CouchDB\Field(type="datetime")
     */
    private $updateDate;

    /**
     * @var User $creationUser
     *
     * @Gedmo\Blameable(on="create")
     * @CouchDB\ReferenceOne(targetDocument="User")
     */
    private $creationUser;

    /**
     * @var User $updateUser
     *
     * @Gedmo\Blameable(on="update")
     * @CouchDB\ReferenceOne(targetDocument="User")
     */
    private $updateUser;

    /**
     * @CouchDB\ReferenceOne(targetDocument="DC")
     */
    private $dc;

    /**
     * @CouchDB\ReferenceOne(targetDocument="REC")
     */
    private $rec;

    /**
     * @CouchDB\ReferenceOne(targetDocument="DVOC")
     */
    private $dvoc;

    /**
     * @CouchDB\ReferenceOne(targetDocument="BRanch")
     */
    private $branch;

    /**
     * @CouchDB\ReferenceOne(targetDocument="CommercialArea")
     */
    private $commercialArea;


    public function __construct()
    {
        parent::__construct();
        // your own logic
        $this->roles = ["ROLE_USER"];
    }


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set firstname
     *
     * @param string $firstname
     * @return User
     */
    public function setFirstname($firstname)
    {
        $this->firstname = $firstname;

        return $this;
    }

    /**
     * Get firstname
     *
     * @return string 
     */
    public function getFirstname()
    {
        return $this->firstname;
    }

    /**
     * Set lastname
     *
     * @param string $lastname
     * @return User
     */
    public function setLastname($lastname)
    {
        $this->lastname = $lastname;

        return $this;
    }

    /**
     * Get lastname
     *
     * @return string 
     */
    public function getLastname()
    {
        return $this->lastname;
    }

    /**
     * Set phoneNumber
     *
     * @param string $phoneNumber
     * @return User
     */
    public function setPhoneNumber($phoneNumber)
    {
        $this->phoneNumber = $phoneNumber;

        return $this;
    }

    /**
     * Get phoneNumber
     *
     * @return string 
     */
    public function getPhoneNumber()
    {
        return $this->phoneNumber;
    }

    /**
     * Set creationDate
     *
     * @param \DateTime $creationDate
     * @return User
     */
    public function setCreationDate($creationDate)
    {
        $this->creationDate = $creationDate;

        return $this;
    }

    /**
     * Get creationDate
     *
     * @return \DateTime 
     */
    public function getCreationDate()
    {
        return $this->creationDate;
    }

    /**
     * Set updateDate
     *
     * @param \DateTime $updateDate
     * @return User
     */
    public function setUpdateDate($updateDate)
    {
        $this->updateDate = $updateDate;

        return $this;
    }

    /**
     * Get updateDate
     *
     * @return \DateTime 
     */
    public function getUpdateDate()
    {
        return $this->updateDate;
    }

    /**
     * Set commercialCode
     *
     * @param string $commercialCode
     * @return User
     */
    public function setCommercialCode($commercialCode)
    {
        $this->commercialCode = $commercialCode;

        return $this;
    }

    /**
     * Get commercialCode
     *
     * @return string 
     */
    public function getCommercialCode()
    {
        return $this->commercialCode;
    }

    /**
     * Set dc
     *
     * @param \OCP\BackBundle\CouchDocument\DC $dc
     * @return User
     */
    public function setDc(\OCP\BackBundle\CouchDocument\DC $dc)
    {
        $this->dc = $dc;

        return $this;
    }

    /**
     * Get dc
     *
     * @return \OCP\BackBundle\CouchDocument\DC
     */
    public function getDc()
    {
        return $this->dc;
    }

    /**
     * Set rec
     *
     * @param \OCP\BackBundle\CouchDocument\REC $rec
     * @return User
     */
    public function setRec(\OCP\BackBundle\CouchDocument\REC $rec)
    {
        $this->rec = $rec;

        return $this;
    }

    /**
     * Get rec
     *
     * @return \OCP\BackBundle\CouchDocument\REC
     */
    public function getRec()
    {
        return $this->rec;
    }

    /**
     * Set dvoc
     *
     * @param \OCP\BackBundle\CouchDocument\DVOC $dvoc
     * @return User
     */
    public function setDvoc(\OCP\BackBundle\CouchDocument\DVOC $dvoc)
    {
        $this->dvoc = $dvoc;

        return $this;
    }

    /**
     * Get dvoc
     *
     * @return \OCP\BackBundle\CouchDocument\DVOC
     */
    public function getDvoc()
    {
        return $this->dvoc;
    }

    /**
     * Set branch
     *
     * @param \OCP\BackBundle\CouchDocument\Branch $branch
     * @return User
     */
    public function setBranch(\OCP\BackBundle\CouchDocument\Branch $branch)
    {
        $this->branch = $branch;

        return $this;
    }

    /**
     * Get branch
     *
     * @return \OCP\BackBundle\CouchDocument\Branch
     */
    public function getBranch()
    {
        return $this->branch;
    }

    /**
     * Set creationUser
     *
     * @param \OCP\BackBundle\CouchDocument\User $creationUser
     * @return User
     */
    public function setCreationUser(\OCP\BackBundle\CouchDocument\User $creationUser = null)
    {
        $this->creationUser = $creationUser;

        return $this;
    }

    /**
     * Get creationUser
     *
     * @return \OCP\BackBundle\CouchDocument\User
     */
    public function getCreationUser()
    {
        return $this->creationUser;
    }

    /**
     * Set updateUser
     *
     * @param \OCP\BackBundle\CouchDocument\User $updateUser
     * @return User
     */
    public function setUpdateUser(\OCP\BackBundle\CouchDocument\User $updateUser = null)
    {
        $this->updateUser = $updateUser;

        return $this;
    }

    /**
     * Get updateUser
     *
     * @return \OCP\BackBundle\CouchDocument\User
     */
    public function getUpdateUser()
    {
        return $this->updateUser;
    }

    /**
     * Set commercialArea
     *
     * @param \OCP\BackBundle\CouchDocument\CommercialArea $commercialArea
     * @return User
     */
    public function setCommercialArea(\OCP\BackBundle\CouchDocument\CommercialArea $commercialArea)
    {
        $this->commercialArea = $commercialArea;

        return $this;
    }

    /**
     * Get commercialArea
     *
     * @return \OCP\BackBundle\CouchDocument\CommercialArea
     */
    public function getCommercialArea()
    {
        return $this->commercialArea;
    }

    /**
     * Set tmpPassword
     *
     * @param string $tmpPassword
     * @return User
     */
    public function setTmpPassword($tmpPassword)
    {
        $this->tmpPassword = $tmpPassword;

        return $this;
    }

    /**
     * Get tmpPassword
     *
     * @return string 
     */
    public function getTmpPassword()
    {
        return $this->tmpPassword;
    }

    public function getWSUser()
    {
        return array(
            "id" => $this->id,
            "firstname" => $this->firstname,
            "lastname" => $this->lastname,
            "email" => $this->email,
            "phone_number" => $this->phoneNumber,
            "commercial_code" => $this->commercialCode,
            "dc" => $this->dc->getId(),
            "rec" => (!$this->rec) ? $this->rec : $this->rec->getId(),
            "dvoc" => (!$this->dvoc) ? $this->dvoc : $this->dvoc->getId(),
            "branch" => (!$this->branch) ? $this->branch : $this->branch->getId(),
            "commercial_area" => (!$this->commercialArea) ? $this->commercialArea : $this->commercialArea->getId(),
            "creation_date" => $this->creationDate,
            "update_date" => $this->updateDate,
            "creation_user" => $this->creationUser->getId(),
            "update_user" => (!$this->updateUser) ? $this->updateUser : $this->updateUser->getId(),
        );
    }
}
Was it helpful?

Solution

OK I found a solution for this issue.

FOS\UserBundle\Security\UserProvider::refreshUser calls

$this->userManager->findUserBy(array('id' => $user->getId()))

as I said before.

This ends up calling DocumentRepository::find($criteria), which basically request the equal_constraint view and set the filter key to document type (here, User), the criteria key (here, id) and the criteria value (here, $user->getId()).

The map function of the equal_constraint view just loop through document indexes and emit a row for each index that has a value. Of course there isn't any "id" index because very document has its "_id" field...

So querying equal_constraint against document "id" is totally illogical. There are of course some ways to query against id (like a straight call to DocumentRepository::find($id)) but given UserProdiver doen't have no reference to an ObjectManager or the document repository, I chose make the following change :

--- UserProvider_old.php    2014-04-29 23:04:17.486752758 +0200
+++ UserProvider.php    2014-04-29 23:04:35.362752432 +0200
@@ -64,7 +64,7 @@
             throw new UnsupportedUserException(sprintf('Expected an instance of %s, but got "%s".', $this->userManager->getClass(), get_class($user)));
         }

-        if (null === $reloadedUser = $this->userManager->findUserBy(array('id' => $user->getId()))) {
+        if (null === $reloadedUser = $this->userManager->findUserBy(array('username' => $user->getUsername()))) {
             throw new UsernameNotFoundException(sprintf('User with ID "%d" could not be reloaded.', $user->getId()));
         }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top