I'm having a little issue using Silex and security service.
When a user enter his data (correctly) into my login form, it doesn't get redirected to app url. He remains in the same page, and debuging, in the login form page, donesn't have anything in security provider indicating that he is authenticated. But, after a 'successful login', if I type the url directly in the browser, I can access because I'm authenticated. Something like this process:
Home Page -> Login Check (login ok) -> Home page (unauthenticated) -> /app (authenticated)
I'd like it to redirect directly to /app if the login works fine, and understand why in my home page, even after a successful login, the security provider keep saying I'm not authenticated.
I'm writing the followin code:
index.php
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints as Assert;
require_once __DIR__.'/../vendor/autoload.php';
$app = new Silex\Application();
/**
* App Registrations & Debug Setting
*/
$app
->register(new Silex\Provider\TwigServiceProvider(), array('twig.path' => __DIR__.'/../views'))
->register(new Silex\Provider\UrlGeneratorServiceProvider())
->register(new Silex\Provider\SessionServiceProvider())
->register(new Silex\Provider\FormServiceProvider())
->register(new Silex\Provider\ValidatorServiceProvider())
->register(new Silex\Provider\TranslationServiceProvider(), array(
'translator.messages' => array(),
))
->register(new Silex\Provider\DoctrineServiceProvider(), array(
'db.options' => array(
'driver' => 'pdo_mysql',
'dbname' => 'pomodesk',
'host' => 'localhost',
'user' => 'root',
'password' => 'root'
)
))
->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'app' => array(
'pattern' => '^/app',
'http' => true,
'form' => array('login_path' => '/', 'check_path' => '/app/login_check'),
'logout' => array('logout_path' => '/app/logout'),
'anonymous' => false,
'users' => $app->share(function () use ($app) {
return new Pomodesk\Provider\UserProvider($app['db']);
})
),
),
'security.access_rules' => array(
array('^/app', 'ROLE_USER')
)
));
$app['debug'] = true;
/**
* App Routes
*/
$app->get('/', function(Request $request) use ($app) {
$form = $app['form.factory']
->createBuilder('form')
->add('name', 'text')
->add('email', 'text')
->add('password', 'password')
->getForm();
if ('POST' == $request->getMethod()) {
$form->bind($request);
$data = $form->getData();
$constraint = new Assert\Collection(array(
'name' => array(new Assert\Length(array('min' => 5)), new Assert\NotBlank()),
'email' => new Assert\Email(),
'password' => array(new Assert\Length(array('min' => 6)), new Assert\NotBlank())
));
$errors = $app['validator']->validateValue($data, $constraint);
$userProvider = new Pomodesk\Provider\UserProvider($app['db']);
try {
$duplicated = $userProvider->loadUserByUsername($data['email']);
} catch (Exception $e) {
$duplicated = false;
}
if ($form->isValid() && count($errors) < 1 && !$duplicated) {
$user = new \Symfony\Component\Security\Core\User\User($data['email'], '', array('ROLE_USER'));
$encoder = $app['security.encoder_factory']->getEncoder($user);
$insertion = $app['db']->insert(
'user',
array(
'email' => $data['email'],
'name' => $data['name'],
'password' => $encoder->encodePassword($data['password'], $user->getSalt()),
'roles' => 'ROLE_USER'
)
);
return $app['twig']->render('home.html.twig', array(
'username' => $data['email'],
'signup' => true
));
}
return $app['twig']->render('home.html.twig', array(
'username' => $data['email'],
'signup' => true
));
}
return $app['twig']->render('home.html.twig', array(
'error' => $app['security.last_error']($request),
'last_username' => $app['session']->get('_security.last_username'),
'form' => $form->createView()
));
})
->method('GET|POST')
->bind('home');
$app->get('/app', function() use ($app) {
$app['app_js'] = $app['twig']->render('script.js.twig');
$data = array();
return $app['twig']->render('app.html.twig', $data);
})
->bind('app_home');
$app->run();
UserProvider.php
<?php
namespace Pomodesk\Provider;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Doctrine\DBAL\Connection;
class UserProvider implements UserProviderInterface
{
private $conn;
public function __construct(Connection $conn)
{
$this->conn = $conn;
}
public function loadUserByUsername($username)
{
$stmt = $this->conn->executeQuery('SELECT * FROM user WHERE email = ?', array(strtolower($username)));
if (!$user = $stmt->fetch()) {
throw new UsernameNotFoundException(sprintf('Email "%s" does not exist.', $username));
}
return new User($user['email'], $user['password'], explode(',', $user['roles']), true, true, true, true);
}
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 $class === 'Symfony\Component\Security\Core\User\User';
}
}
Thanks a lot!
Change your code like so :
->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'app' => array(
'pattern' => '^/',
'http' => true,
'form' => array('login_path' => '/', 'check_path' => '/app/login_check'),
'logout' => array('logout_path' => '/app/logout'),
'anonymous' => true,
'users' => $app->share(function () use ($app) {
return new Pomodesk\Provider\UserProvider($app['db']);
})
),
),
'security.access_rules' => array(
array('^/app', 'ROLE_USER')
)
));
allow anonymous users in the firewall, and just protect the /app route with the access rules. If you dont do that , you'll have problems with context , let's say you want a custom menu if users are logged in on all the pages of your app ,even those that are not secured , you will not be able to do that if you dont share the security context accross all the website.
these are some options you can use in the form array,according to symfony doc :
# login success redirecting options (read further below)
always_use_default_target_path: false
default_target_path: /
target_path_parameter: _target_path
use_referer: false
http://symfony.com/doc/2.1/reference/configuration/security.html
so the redirect can either be handled by a hidden input the login form , or setting default_target_path
'form' => array(
'login_path' => '/',
'check_path' => '/app/login_check',
'default_target_path' => '/app',
'always_use_default_target_path' => true
),