'Entity provider not working in security.yml php symfony3.4 ({"code":401,"message":"bad credentials"}.)

I'm using symfony 3.4 with DoctrineMongoDBBundle and LexikJWTAuthenticationBundle . I'm trying to create a user login which return JWT token. If i specify the username and password under in_memory provider, it returns the token but if i use a entity provider, it returns {"code":401,"message":"bad credentials"}.

Here is my security.yml

# To get started with security, check out the documentation:
# https://symfony.com/doc/current/security.html
security:

    # https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
    encoders:
        AppBundle\Document\User:
            algorithm: bcrypt
            cost: 12

    providers:  
            webprovider:
                entity:
                    class: AppBundle\Document\User
                    property: username

    firewalls:

        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        
        login:

            pattern:  ^/api/login
            stateless: true
            anonymous: true
            provider: webprovider
            form_login:
                username_parameter: username
                password_parameter: password
                check_path:               /api/login_check
                success_handler:          lexik_jwt_authentication.handler.authentication_success
                failure_handler:          lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false

        api:
            pattern:   ^/api
            stateless: true
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
    

        main:
            anonymous: ~
            # activate different ways to authenticate

            # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
            #http_basic: ~

            # https://symfony.com/doc/current/security/form_login_setup.html
            #form_login: ~
    



    access_control:
        - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }

Here is my User class

<?php

// /AppBundle/Document/User.php
namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Bundle\MongoDBBundle\Validator\Constraints\Unique as MongoDBUnique;
use Symfony\Component\Security\Core\User\UserInterface;
/**
 * @MongoDB\Document(collection="users")
 * @MongoDBUnique(fields="email")
 */
class User implements UserInterface
{


    /**
     * @MongoDB\Id
     */
    protected $id;

    /**
     * @MongoDB\Field(type="string")
     * @Assert\Email()
     */
    protected $email;
    /**
     * @MongoDB\Field(type="string")
     * @Assert\NotBlank()
     */
    protected $username;
    /**
     * @MongoDB\Field(type="string")
     * @Assert\NotBlank()
     */
    protected $password;

    /**
     * @MongoDB\Field(type="boolean")
     */
    private $isActive;

    public function __construct()
    {
        var_dump("1");
        $this->isActive = true;
        // may not be needed, see section on salt below
        // $this->salt = md5(uniqid('', true));
    }

    public function getId()
    {

        return $this->id;
    }

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

    public function setEmail($email)
    {
        $this->email = $email;
    }
    public function setUsername($username)
    {
        $this->username = $username;
    }

    public function getUsername()
    {
        var_dump($this->username);  
        return $this->username;
    }
    public function getSalt()
    {
        return null;
    }
    public function getPassword()
    {
        return $this->password;
    }
    public function setPassword($password)
    {
        $this->password = $password;
    }
    public function getRoles()
    {
        return array('ROLE_USER');
    }
    public function eraseCredentials()
    {
    }

}

Would really appreciate if someone could help, Thanks.



Solution 1:[1]

You should use FOS bundle components by extending Base model User calss in entity, better than using directly Implementing UserInterface, In your case problem could be your password is not getting encoded correct. It's better to encode using security.password_encoder. To more better understanding i am sharing an example to setup login and generating token.

Your security.yml should look like this

`# Symfony 3.4 security.yml
security:
    encoders:
        FOS\UserBundle\Model\UserInterface: bcrypt
        Symfony\Component\Security\Core\User\User: plaintext



    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN
        ROLE_API:         ROLE_USER, ROLE_MEDIC, ROLE_STUDENT



    # https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email

        in_memory:
            memory: ~



    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false


        api_login:
            pattern:  ^/api/login
            stateless: true
            anonymous: true
            form_login:
                check_path:               /api/login_check
                success_handler:          lexik_jwt_authentication.handler.authentication_success
                failure_handler:          lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false

        api:
            pattern:  ^/(api)
            stateless: true  #false (not assign cookies)
            anonymous: ~
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
                provider: fos_userbundle


    access_control: .................`

Your User class should be like this:

// /AppBundle/Document/User.php
namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use FOS\UserBundle\Model\User as BaseUser;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Bundle\MongoDBBundle\Validator\Constraints\Unique as MongoDBUnique;
/**
 * @MongoDB\Document(collection="users")
 * @MongoDBUnique(fields="email")
 */
class User implements BaseUser
{


    /**
     * @MongoDB\Id
     */
    protected $id;

    /**
     * @MongoDB\Field(type="string")
     * @Assert\Email()
     */
    protected $email;
    /**
     * @MongoDB\Field(type="string")
     * @Assert\NotBlank()
     */
    protected $username;
    /**
     * @MongoDB\Field(type="string")
     * @Assert\NotBlank()
     */
    protected $password;

    /**
     * @MongoDB\Field(type="boolean")
     */
    private $isActive;

    public function __construct()
    {
        var_dump("1");
        $this->isActive = true;
        // may not be needed, see section on salt below
        // $this->salt = md5(uniqid('', true));
    }

    public function getId()
    {

        return $this->id;
    }

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

    public function setEmail($email)
    {
        $this->email = $email;
    }
    public function setUsername($username)
    {
        $this->username = $username;
    }

    public function getUsername()
    {
        var_dump($this->username);  
        return $this->username;
    }
    public function getSalt()
    {
        return null;
    }
    public function getPassword()
    {
        return $this->password;
    }
    public function setPassword($password)
    {
        $this->password = $password;
    }
    public function getRoles()
    {
        return array('ROLE_USER');
    }
    public function eraseCredentials()
    {
    }

}

Now your Api tokenController.php to generate token and validate token

namespace \your_namespace\Api;

use FOS\RestBundle\Context\Context;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;


use AppBundle\Document\User;
# Below most of the components belongs to Nelmio api or Sensio or Symfony
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException;
use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserToken;

use Nelmio\ApiDocBundle\Annotation\ApiDoc;


    class TokenController extends FOSRestController
    {
        /**
         * @Rest\Post("/tokens", name="api_token_new",
         *     options={"method_prefix" = false},
         *     defaults={"_format"="json"}
         * )
         *
         * @ApiDoc(
         *    section     = "Security",
         *    description = "Get user token",
         *    parameters={
         *      {"name"="username", "dataType"="string", "required"=true, "description"="username or email"},
         *      {"name"="pass",     "dataType"="string", "required"=true, "description"="password "},
         *    }
         * )
         */
        public function newTokenAction(Request $request)
        {
            $em = $this->getDoctrine()->getManager();

            $username = $request->get('username');
            /** @var User $user */
            $user = $em->getRepository('AppBundle:User')->findOneBy(['email' => $username]);
            if (!$user) {
                throw $this->createNotFoundException();
            }
            if (!$user->isEnabled()){
                throw $this->createNotFoundException($this->get('translator')->trans('security.user_is_disabled'));
            }


            $pass = $request->get('pass');
            $isValid = $this->get('security.password_encoder')->isPasswordValid($user, $pass);
            if (!$isValid) {
                throw new BadCredentialsException();
            }

            $token = $this->get('lexik_jwt_authentication.encoder')->encode([
                'username' => $user->getUsername(),
                'id'       => $user->getId(),
                'roles'    => $user->getRoles(),
                'exp'      => time() + (30 * 24 * 3600) // 30 days expiration -> move to parameters or config
            ]);


            // Force login
            $tokenLogin = new UsernamePasswordToken($user, $pass, "public", $user->getRoles());
            $this->get("security.token_storage")->setToken($tokenLogin);

            // Fire the login event
            // Logging the user in above the way we do it doesn't do this automatically
            $event = new InteractiveLoginEvent($request, $tokenLogin);
            $this->get("event_dispatcher")->dispatch("security.interactive_login", $event);

            $view = $this->view([
                'token' => $token,
                'user'  => $user
            ]);
            $context = new Context();
            $context->addGroups(['Public']);
            $view->setContext($context);

            return $this->handleView($view);
        }


         /**
 * @Rest\Post("/validate", name="api_token_validate",
 *     options={"method_prefix" = false},
 *     defaults={"_format"="json"}
 * )
 *
 * @ApiDoc(
 *    section     = "Security",
 *    description = "Get user by token",
 *    parameters={
 *      {"name"="token", "dataType"="textarea", "required"=true, "description"="token"},
 *    }
 * )
 */
public function validateUserToken(Request $request)
{
    $token = $request->get('token');
    //get UserProviderInterface
    $fos  = $this->get('fos_user.user_provider.username_email');
    //create PreAuthToken
    $preAuthToken = new PreAuthenticationJWTUserToken($token);
    try {
        if (!$payload = $this->get('lexik_jwt_authentication.jwt_manager')->decode($preAuthToken)) {
            throw new InvalidTokenException('Invalid JWT Token');
        }
        $preAuthToken->setPayload($payload);
    } catch (JWTDecodeFailureException $e) {
        if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
            throw new ExpiredTokenException();
        }
        throw new InvalidTokenException('Invalid JWT Token', 0, $e);
    }
    //get user
    /** @var User $user */
    $user = $this->get('lexik_jwt_authentication.security.guard.jwt_token_authenticator')->getUser($preAuthToken, $fos);
    $view = $this->view([
        'token' => $token,
        'user'  => $user
    ]);
    $context = new Context();
    $context->addGroups(array_merge(['Public'],$user->getRoles()));
    $view->setContext($context);
    return $this->handleView($view);
}

if you face any problem then , let me know.

Solution 2:[2]

you need to add the provider before json_login tag this the case for me

    provider: fos_userbundle

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 M.Sofien