I tried to create an authenticator for my login form, but I always am unlogged for some unclear reason.
[2016-10-05 18:54:53] security.INFO: Guard authentication successful! {"token":"[object] (Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken: PostAuthenticationGuardToken(user=\"test@test.test\", authenticated=true, roles=\"ROLE_USER\"))","authenticator":"AppBundle\\Security\\Authenticator\\FormLoginAuthenticator"} []
[2016-10-05 18:54:54] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExpiredException(code: 0): at /space/products/insurance/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php:86)"} []
[2016-10-05 18:54:54] security.INFO: The security token was removed due to an AccountStatusException. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExpiredException(code: 0): at /space/products/insurance/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php:86)"} []
I don't understand this "AuthenticationExpiredException" as I have nothing stateless, nor any expiration in any way nowhere in my app.
Does this issue speak to anyone?
Edit 1
After a bunch of hours, it looks like I am unlogged because of the {{ is_granted('ROLE_USER') }}
in Twig. Don't see why anyway.
Edit 2
If I dump() my security token on the onAuthenticationSuccess authenticator's method, authenticated = true
.
But, If I dump() my security token after a redirect or when accessing a new page, 'authenticated' = false
.
Why the hell my authentication isn't stored.
app/config/security.yml
security:
encoders:
AppBundle\Security\User\Member:
algorithm: bcrypt
cost: 12
providers:
members:
id: app.provider.member
role_hierarchy:
ROLE_ADMIN: "ROLE_USER"
firewalls:
dev:
pattern: "^/(_(profiler|wdt|error)|css|images|js)/"
security: false
main:
pattern: "^/"
anonymous: ~
logout: ~
guard:
authenticators:
- app.authenticator.form_login
access_control:
- { path: "^/connect", role: "IS_AUTHENTICATED_ANONYMOUSLY" }
- { path: "^/register", role: "IS_AUTHENTICATED_ANONYMOUSLY" }
- { path: "^/admin", role: "ROLE_ADMIN" }
- { path: "^/user", role: "ROLE_USER" }
- { path: "^/logout", role: "ROLE_USER" }
AppBundle/Controller/SecurityController.php
<?php
namespace AppBundle\Controller;
use AppBundle\Base\BaseController;
use AppBundle\Form\Type\ConnectType;
use AppBundle\Security\User\Member;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
class SecurityController extends BaseController
{
/**
* @Route("/connect", name="security_connect")
* @Template()
*/
public function connectAction(Request $request)
{
$connectForm = $this
->createForm(ConnectType::class)
->handleRequest($request)
;
return [
'connect' => $connectForm->createView(),
];
}
}
AppBundle/Form/Type/ConnectType.php
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Validator\Constraints;
use EWZ\Bundle\RecaptchaBundle\Form\Type\EWZRecaptchaType;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\IsTrue as RecaptchaTrue;
class ConnectType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', Type\EmailType::class, [
'label' => 'Your email',
'required' => true,
'constraints' => [
new Constraints\Length(['min' => 8])
],
])
->add('password', Type\PasswordType::class, [
'label' => 'Your password',
'constraints' => new Constraints\Length(['min' => 8, 'max' => 4096]), /* CVE-2013-5750 */
])
->add('recaptcha', EWZRecaptchaType::class, [
'label' => 'Please tick the checkbox below',
'constraints' => [
new RecaptchaTrue()
],
])
->add('submit', Type\SubmitType::class, [
'label' => 'Connect',
])
;
}
}
AppBundle/Security/Authenticator/FormLoginAuthenticator.php
<?php
namespace AppBundle\Security\Authenticator;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use AppBundle\Form\Type\ConnectType;
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
{
private $container; // ¯\_(ツ)_/¯
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() !== '/connect') {
return null;
}
$connectForm = $this
->container
->get('form.factory')
->create(ConnectType::class)
->handleRequest($request)
;
if ($connectForm->isValid()) {
$data = $connectForm->getData();
return [
'username' => $data['email'],
'password' => $data['password'],
];
}
return null;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return $userProvider->loadUserByUsername($credentials['username']);
}
public function checkCredentials($credentials, UserInterface $user)
{
$isValid = $this
->container
->get('security.password_encoder')
->isPasswordValid($user, $credentials['password'])
;
if (!$isValid) {
throw new BadCredentialsException();
}
return true;
}
protected function getLoginUrl()
{
return $this
->container
->get('router')
->generate('security_connect')
;
}
protected function getDefaultSuccessRedirectUrl()
{
return $this
->container
->get('router')
->generate('home')
;
}
}
AppBundle/Security/Provider/MemberProvider.php
<?php
namespace AppBundle\Security\Provider;
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;
use AppBundle\Security\User\Member;
use Api\Gateway\RequestResponse\RequestResponseHandlerInterface;
use Api\Business\InsuranceWebsite\Action\GetInsuranceMember\GetInsuranceMemberRequest;
use Api\Gateway\Exception\NoResultException;
class MemberProvider implements UserProviderInterface
{
protected $gateway;
public function __construct(RequestResponseHandlerInterface $gateway)
{
$this->gateway = $gateway;
}
public function loadUserByUsername($username)
{
try {
$response = $this->gateway->handle(
new GetInsuranceMemberRequest($username)
);
} catch (NoResultException $ex) {
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
$member = new Member();
$member->setId($response->getId());
$member->setUsername($response->getEmail());
$member->setPassword($response->getPassword());
$member->setCompanyId($response->getCompanyId());
$member->setFirstname($response->getFirstname());
$member->setLastname($response->getLastname());
$member->setIsManager($response->isManager());
$member->setIsEnabled($response->isEnabled());
return $member;
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof Member) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === Member::class;
}
}
AppBundle/Security/User/Member.php
<?php
namespace AppBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
class Member implements UserInterface
{
private $id;
private $username;
private $password;
private $companyId;
private $firstname;
private $lastname;
private $isManager;
private $isEnabled;
private $roles = ['ROLE_USER'];
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
return $this;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getCompanyId()
{
return $this->companyId;
}
public function setCompanyId($companyId)
{
$this->companyId = $companyId;
return $this;
}
public function getFirstname()
{
return $this->firstname;
}
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
public function getLastname()
{
return $this->lastname;
}
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
public function isManager()
{
return $this->isManager;
}
public function setIsManager($isManager)
{
$this->isManager = $isManager;
return $this;
}
public function IsEnabled()
{
return $this->isEnabled;
}
public function setIsEnabled($isEnabled)
{
$this->isEnabled = $isEnabled;
return $this;
}
public function eraseCredentials()
{
$this->password = null;
}
public function hasRole($role)
{
return in_array($role, $this->roles);
}
public function getRoles()
{
return $this->roles;
}
public function addRole($role)
{
if (!$this->hasRole($role)) {
$this->roles[] = $role;
}
return $this;
}
public function removeRole($role)
{
$index = array_search($role, $this->roles);
if ($index !== false) {
unset($this->roles[$index]);
$this->roles = array_values($this->roles);
}
return $this;
}
public function getSalt()
{
return null;
}
}
src/AppBundle/Resources/config/services.yml
imports:
parameters:
app.provider.member.class: AppBundle\Security\Provider\MemberProvider
app.authenticator.form_login.class: AppBundle\Security\Authenticator\FormLoginAuthenticator
services:
app.provider.member:
class: %app.provider.member.class%
arguments: ['@gateway']
app.authenticator.form_login:
class: %app.authenticator.form_login.class%
arguments: ["@service_container"]