Search code examples
phpsymfonyjwtlexikjwtauthbundle

Symfony4 + jwt-auth rescipe always return {"code":401,"message":"Bad credentials"}


I've a Symfony4 installation with few common bundles installed with Flex

  • make
  • jwt-auth
  • annotation
  • behat
  • phpunit
  • server

I've this routing file:

api_login_check:
    path: /api/login_check

I've this configuration:

security:

    encoders:
        App\Security\User: plaintext

    providers:
        app.provider:
            id: App\Security\UserProvider

    firewalls:

        login:
            pattern:  ^/api/login
            stateless: true
            anonymous: true
            provider: app.provider
            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
            provider: app.provider
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator

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

and this user:

<?php

namespace App\Security;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;

class User implements UserInterface, EquatableInterface
{
    private $username;
    private $password;
    private $salt;
    private $roles;

    public function __construct($username, $password, $salt, array $roles)
    {
        $this->username = $username;
        $this->password = $password;
        $this->salt = $salt;
        $this->roles = $roles;
    }

    public function getRoles()
    {
        return $this->roles;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getSalt()
    {
        return $this->salt;
    }

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

    public function eraseCredentials()
    {
    }

    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof User) {
            return false;
        }

        if ($this->password !== $user->getPassword()) {
            return false;
        }

        if ($this->salt !== $user->getSalt()) {
            return false;
        }

        if ($this->username !== $user->getUsername()) {
            return false;
        }

        return true;
    }
}

and finally this user provider:

<?php

namespace App\Security;

use App\Security\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class UserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        $username = 'senso';
        $password = 'rario';
        $salt     = 'sale';
        $roles    = ['ROLE_ADMIN'];

        return new User($username, $password, $salt, $roles);

        throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
        );
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class)
    {
        return User::class === $class;
    }
}

When I run

curl -X POST http://localhost:8000/api/login_check -d _username=senso -d _password=rario {"code":401,"message":"Bad credentials"}

I always get

{"code":401,"message":"Bad credentials"}

My questions are:

  • how to fix this?
  • why UserProvider::loadUserByUsername() was never called?

Solution

  • If you are using the PlaintextPasswordEncoder and providing a salt to your User class, then your User's getPassword method should return plain_password{salt}.

    In this case,

    $username = 'senso';
    $password = 'rario{sale}';
    $salt     = 'sale';
    $roles    = ['ROLE_ADMIN'];
    
    return new User($username, $password, $salt, $roles);
    

    or

    $username = 'senso';
    $password = 'rario';
    $salt     = '';
    $roles    = ['ROLE_ADMIN'];
    
    return new User($username, $password, $salt, $roles);
    

    will be fine to use.