I'm implementing a custom authentication provider for using an external api following roughly the cookbook on the symfony website. It works almost everything correctly, the listener listens the login form properly, then it calls the authenticate function which returns the authenticated token, the problem is that even if i set a authenticated token to the securityContextInterface, the system returns to the login page with wrong credentials. Under the code i've used What could it be?
security.yml
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/app/login$
security: false
api_secured:
provider: in_memory
pattern: ^/app
form_login:
login_path: /app/login
check_path: /app/login_check
logout:
path: /app/logout
target: /
api: true
services.yml
api.security.authentication.provider:
class: Manuel\Myapp\MyAppBundle\Security\Authentication\Provider\ApiProvider
arguments: ['', %kernel.cache_dir%/security/nonces]
api.security.authentication.listener:
class: Manuel\Myapp\MyAppBundle\Security\Firewall\ApiListener
arguments: [@security.context, @security.authentication.manager, %api.url%]
ApiFactory.php
namespace Manuel\Myapp\MyAppBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class ApiFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.api.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('api.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.api.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('api.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'api';
}
public function addConfiguration(NodeDefinition $node)
{
}
}
ApiListener.php
namespace Manuel\Myapp\MyAppBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Manuel\Myapp\MyAppBundle\Security\Authentication\Token\ApiUserToken;
use Httpful\Request;
class ApiListener implements ListenerInterface {
protected $securityContext;
protected $authenticationManager;
protected $container;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, $api)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
//Prendo l'url delle api
//Viene passato da services.yml alla classe
$this->api = $api;
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
$data = $request->request->all();
//Esiste username e password ?
if(!array_key_exists('_username', $data) || !array_key_exists('_password', $data)) {
//Ritorna alla pagina di login con bad credentials
$this->securityContext->setToken(null);
return;
}
//Autentico in remoto
$username = $data['_username'];
$password = $data['_password'];
$response = Request::post($this->api."/token/new.json")
->body(array(
'username'=> $username,
'password'=> $password))
->expectsJson()
->sendsForm()
->send();
$decode = json_decode($response);
//Se esiste allora vado avanti se no muoio
if(!$decode->success) {
$this->securityContext->setToken(null);
return;
}
$token = new ApiUserToken();
$token->setUser(''.$decode->user);
$token->token = $decode->token;
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->securityContext->setToken($authToken);
} catch (AuthenticationException $failed) {
// ... si potrebbe loggare qualcosa in questo punto
// Per negare l'autenticazione, pulire il token. L'utente sarà rinviato alla pagina di login.
$this->securityContext->setToken(null);
return;
// Negare l'autenticazione con una risposta HTTP '403 Forbidden'
//$response = new Response();
//$response->setStatusCode(403);
//$event->setResponse($response);
}
}
}
If i write:
$authToken = $this->authenticationManager->authenticate($token);
var_dump($authToken); die();
$this->securityContext->setToken($authToken);
The results is:
object(Manuel\Myapp\MyAppBundle\Security\Authentication\Token\ApiUserToken)#4780 (5) {["user":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> object(Symfony\Component\Security\Core\User\User)#4782 (7) { ["username":"Symfony\Component\Security\Core\User\User":private]=> string(4) "user" ["password":"Symfony\Component\Security\Core\User\User":private]=> string(15) "10dmao!?postino" ["enabled":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["accountNonExpired":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["credentialsNonExpired":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["accountNonLocked":"Symfony\Component\Security\Core\User\User":private]=> bool(true) ["roles":"Symfony\Component\Security\Core\User\User":private]=> array(1) { [0]=> string(9) "ROLE_USER" } } ["roles":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> array(1) { [0]=> object(Symfony\Component\Security\Core\Role\Role)#4779 (1) { ["role":"Symfony\Component\Security\Core\Role\Role":private]=> string(9) "ROLE_USER" } } ["authenticated":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> bool(true) ["attributes":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> array(0) { } }
So it is correct.
ApiUserToken.php
namespace Manuel\Myapp\MyAppBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class ApiUserToken extends AbstractToken
{
public $token;
public function __construct(array $roles = array())
{
parent::__construct($roles);
// If the user has roles, consider it authenticated
$this->setAuthenticated(true);
}
public function getCredentials()
{
return '';
}
}
ApiProvider.php
namespace Manuel\Myapp\MyAppBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Manuel\Myapp\MyAppBundle\Security\Authentication\Token\ApiUserToken;
class ApiProvider implements AuthenticationProviderInterface
{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir = $cacheDir;
}
public function authenticate(TokenInterface $token)
{
//Devo aggiungere utente
$user = $this->userProvider->loadUserByUsername("user");
if ($user) {
$authenticatedToken = new ApiUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
throw new AuthenticationException('The API authentication failed.');
}
public function supports(TokenInterface $token) {
return $token instanceof ApiUserToken;
}
}