I'm using Silex framework on HHVM and running into some issues when trying to implement a SecurityServiceProvider for login.
When trying to perform a login action (with a correct username & password), I'm redirected to the login page instead of the / page. This is because of the following access rule:
$app['security.access_rules'] = array(
array('^/$', 'ROLE_USER')
);
I've tried dumping $app['security.token_storage']->getToken()
and $app['security.token_storage']->getToken()->getUser()
at the top of the login page:
object(Symfony\Component\Security\Core\Authentication\Token\AnonymousToken)#350 (5) {
["key":"Symfony\Component\Security\Core\Authentication\Token\AnonymousToken":private]=> string(3) "all"
["user":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> string(5) "anon."
["roles":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> array(0) { }
["authenticated":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> bool(true)
["attributes":"Symfony\Component\Security\Core\Authentication\Token\AbstractToken":private]=> array(0) { } }
The username shows as anon.
which isn't my username (test
).
Through hacky debugging, I can confirm that:
- A User object is being created.
- The password is being verified, and returning true.
Relevant parts of the application code (it's roughly based on @mpm's Silex MVC -- haven't confirmed yet if his login code produces better results):
Controller route:
public function login(Application $app)
{
$form = $app['form.factory']->createBuilder('form')
->add('username', 'text', array('label' => 'Username'))
->add('password', 'password', array('label' => 'Password'))
->getForm();
return $app['twig']->render('user/login.tpl', array(
'title' => "Login",
'form' => $form->createView(),
'error' => $app['security.last_error']($app['request'])
));
}
Login template:
{% block content %}
<h1>Login</h1>
{% if error %}
<div>
{{ error }}
</div>
{% endif %}
<form action="{{ path('user_login_check') }}" method="post" novalidate {{ form_enctype(form) }} class="form-vertical">
{{ form_widget(form) }}
<button type="submit">Login</button>
</form>
{% endblock %}
Security-related service declarations:
$app->register(new SecurityServiceProvider(), array(
"security.firewalls" => array(
// All other URLs require authentication.
"all" => array(
"pattern" => '^/.*$',
"form" => array(
"login_path" => '/user/login',
"check_path" => '/user/login_check',
"default_target_path" => '/',
"username_parameter" => 'form[username]',
"password_parameter" => 'form[password]'
),
"anonymous" => true,
"logout" => array(
"logout_path" => "/user/logout"
),
"users" => $app->share(function () use ($app) {
return new UserProvider($app['db']);
})
)
)
));
$app['security.encoder.digest'] = $app->share(function ($app) {
return new PasswordEncoder($app['config']['security']['bcrypt_cost']);
});
$app['security.role_hierarchy'] = array(
"ROLE_ADMIN" => array(
"ROLE_USER",
"ROLE_CREATE_CHARACTER",
"ROLE_CREATE_ORIGIN",
"ROLE_CREATE_COMMISSION",
"ROLE_DELETE_CHARACTER",
"ROLE_DELETE_ORIGIN",
"ROLE_DELETE_COMMISSION"
),
"ROLE_ARTIST" => array(
"ROLE_USER",
"ROLE_CREATE_COMMISSION"
),
"ROLE_WRITER" => array(
"ROLE_USER",
"RULE_CREATE_ORIGIN"
)
);
$app['security.access_rules'] = array(
array('^/$', 'ROLE_ADMIN')
);
My User class:
<?php
namespace Coco\Model;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
class User implements UserInterface, EquatableInterface
{
private $username;
private $password;
private $roles;
public function __construct($username, $password, array $roles)
{
$this->username = $username;
$this->password = $password;
$this->roles = $roles;
}
public function getUsername()
{
return $this->username;
}
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
return $this->roles;
}
public function eraseCredentials()
{
}
public function getSalt()
{
return null;
}
public function isEqualTo(UserInterface $user)
{
if (false === $user instanceof User) {
return false;
} elseif ($this->password !== $user->getPassword()) {
return false;
} elseif ($this->username !== $user->getUsername()) {
return false;
} else {
return true;
}
}
}
My UserProvider class:
<?php
namespace Coco\Provider;
use Coco\Exception\UnsupportedUserException;
use Coco\Exception\UsernameNotFoundException;
use Coco\Provider;
use Coco\Model\User;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserProvider extends Provider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$username = strtolower($username);
$query = "SELECT `username`, `password`, `roles` FROM `user` WHERE `status` = 'active' AND `username` = ?";
$stmt = $this->db->executeQuery($query, array($username));
$user = $stmt->fetch();
if (false === $user) {
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
}
return new User(
$user['username'],
$user['password'],
explode(',', $user['roles'])
);
}
public function refreshUser(UserInterface $user)
{
if (false === $user instanceof User) {
throw new UnsupportedUserException(sprintf('Instance of "%s" not supported.', get_class($user)));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Coco\Model\User';
}
}
I have a SessionServiceProvider
and a FormServiceProvider
running.
I have no idea how to properly debug this issue because of a lack of familiarity with the framework. If anyone could point me in the right direction, it would be appreciated.