Skip to content
Advertisement

symfony 5 authentification problem with getUser()

I am trying to work with Symfony 5 and the security component and I am blocked on a bug. the bug is on authentification success after redirection, $this->getUser() return null but at the same time, the development panel show me my role (which is store in database)

my security.yaml:

security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        in_database:
            entity:
                class: AppEntityUser
                property: email

    role_hierarchy:
        ROLE_CLIENT:      ROLE_USER
        ROLE_ADMIN:       ROLE_CLIENT
        ROLE_DEV: [ROLE_CLIENT, ROLE_ADMIN]

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: true
            provider: in_database
            form_login:
                check_path: app_login
                login_path: app_login
                default_target_path:  /test
                username_parameter: email
                password_parameter: password
                csrf_parameter: _csrf_security_token
                csrf_token_id: a_private_string
            logout:
                path: app_logout
                target: app_login


            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/backoffice, roles: ROLE_ADMIN }
        - { path: ^/profile, roles: ROLE_USER }
    encoders:
        AppEntityUser: plaintext

And I did not modify the User entity.

edit

Following advices, I added those lines in my security.yaml:

guard:
    authenticators:
        - AppSecurityLoginFormAuthentificator

And the LoginFormAuthentificator file (sorry it will be long)

<?php

namespace AppSecurity;

use AppEntityUser;
use DoctrineORMEntityManagerInterface;

use SymfonyComponentHttpFoundationRedirectResponse;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentRoutingRouterInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreEncoderUserPasswordEncoderInterface;
use SymfonyComponentSecurityCoreExceptionCustomUserMessageAuthenticationException;
use SymfonyComponentSecurityCoreExceptionInvalidCsrfTokenException;
use SymfonyComponentSecurityCoreSecurity;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentSecurityCoreUserUserProviderInterface;
use SymfonyComponentSecurityCsrfCsrfToken;
use SymfonyComponentSecurityCsrfCsrfTokenManagerInterface;
use SymfonyComponentSecurityGuardAuthenticatorAbstractFormLoginAuthenticator;
use SymfonyComponentSecurityHttpUtilTargetPathTrait;

class LoginFormAuthentificator extends AbstractFormLoginAuthenticator {
    use TargetPathTrait;

    private $entityManager;
    private $router;
    private $csrfTokenManager;
    private $passwordEncoder;

    public function __construct(EntityManagerInterface $entityManager, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder){
        $this->entityManager = $entityManager;
        $this->router = $router;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    public function supports(Request $request){
        return 'app_login' === $request->attributes->get('_route') && $request->isMethod('POST');
    }

    public function getCredentials(Request $request){
        $credentials = ['email' => $request->request->get('email'), 'password' => $request->request->get('password'), 'csrf_token' => $request->request->get('_csrf_token'),];
        $request->getSession()->set(Security::LAST_USERNAME, $credentials['email']);

        return $credentials;
    }

    public function getUser($credentials, UserProviderInterface $userProvider){
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if(!$this->csrfTokenManager->isTokenValid($token)){
            throw new InvalidCsrfTokenException();
        }


        $user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);

        if(!$user){
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('Email could not be found.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user){
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey){
        if($targetPath = $this->getTargetPath($request->getSession(), $providerKey)){

            return new RedirectResponse($targetPath);
        }

        return new RedirectResponse($this->router->generate('BackOffice_home_index'));
        // throw new Exception('TODO: provide a valid redirect inside '.__FILE__);
    }

    protected function getLoginUrl(){
        return $this->router->generate('app_login');
    }
}

Thank you so much for help and time.

Advertisement

Answer

After some debugging, I found that you implement Serializable interface to your User class. With this approach, methods from this interface should returns correct data, because User object will be serialized to the session by these methods. In your case methods, serialize and unserialize were empty, which causing null value stored in session. So you should remove Serializable interface and it’s methods from your User class (in this case application store some default fields in session), or implement at least minimal functionality to this methods:

public function serialize(){
    return serialize([
        $this->id,
        $this->email,
        $this->password
    ]); 
}

public function unserialize($serialized){
    list(
        $this->id, 
        $this->email, 
        $this->password
    ) = unserialize($serialized, ['allowed_classes' => false]);
}

also, add return value to getUsername method implemented from UserInterface, e.g.

public function getUsername(){
    return $this->email;
}

More information here and here

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement