I'm working for the first time with Silex's Security Provider and I'm having issues with the process. I currently have the basic HTTP auth working (using the coded example user as shown here in the docs).
When switching HTTP out for the form option however the login form is submitting, and returning to itself. I have created a UserProvider class and the loadUserByUsername method is being successfully called, however the email isn't being passed in (being set to "NONE_PROVIDED" - altered from username). This I found when working through the vendor code is because the token isn't being set ($app['security']->getToken() returning null at all points). I've trawled through all the docs I can but I can't find any mention of this.
The main code is included below, let me know if there is anything else, thanks!
Security Provider Configuration
// Protects all routes within /auth, redirecting to /login successfully
$app->register(new SecurityServiceProvider(), array(
'security.firewalls' => array(
'unauth_area' => array(
'pattern' => '^/(?!auth)'
),
'auth_area' => array(
'pattern' => '^/.*$',
'form' => array(
'login_path' => '/login',
'check_path' => '/auth/login_check',
'default_target_path' => '/auth/overview',
),
'users' => $app->share(function () use ($app) {
return new UserProvider($app['db']);
}),
),
),
'access_control' => array(
array('path' => '^/.*$', 'role' => 'ROLE_USER'),
// Include the following line to also secure the /admin path itself
// array('path' => '^/admin$', 'role' => 'ROLE_ADMIN'),
),
));
(My Custom) method - UserProvider class
public function loadUserByUsername($email) {
// Dying at this point shows it reaches here, but $email is null
$stmt = $this->conn->executeQuery('SELECT * FROM user WHERE email = ?', array(strtolower($email)));
if (!$user = $stmt->fetch()) {
throw new UsernameNotFoundException(sprintf('Email "%s" does not exist.', $email));
}
return new User($user['email'], $user['password'], explode(',', $user['roles']), true, true, true, true);
}
Form Class
class LoginType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('_username', 'text', array(
'required' => true,
'constraints' => array(
new Assert\NotBlank(),
new Assert\Email(),
)
))
->add('_password', 'password', array(
'required' => true,
'constraints' => array(
new Assert\NotBlank(),
),
))
->add('Login', 'submit');
}
public function getName() {
return 'login';
}
}
Silex Security Provider docs
It has nothing to do with the token… I just had the same problem with
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'admin' => array(
'pattern' => '^/admin',
'form' => array(
'login_path' => '/',
'check_path' => '/admin/login_check',
'username_parameter'=> 'mail',
'password_parameter' => 'password',
),
'logout' => array('logout_path' => '/logout'),
//'anonymous' => true,
'users' => function () use ($app) {
return new UserProvider($app['db']);
},
)
),
'security.access_rules' => array(
array('^/$', 'IS_AUTHENTICATED_ANONYMOUSLY'),
array('^/admin', 'ROLE_USER')
)
));
After a couple hours trying and testing, I checked the name attribute in my form's input… Saw form[mail]
So I tried
'username_parameter'=> 'form[mail]',
'password_parameter' => 'form[password]',
And … ALLELUIA!!!!! had my mail in loadUserByUsername($mail)
Related
i need to setup my silex firewall like:
www.mysite.com/* => access to all users
www.mysite.com/admin/* => access to only logged in users
i use this set up but it does not work as expected:
$app->register(new SecurityServiceProvider(), array(
'security.firewalls' => array(
'secure' => [
'pattern' => '^/.*$',
'anonymous' => true,
'form' => array(
'login_path' => '/admin/login',
'check_path' => '/admin/auth'
),
'logout' => array(
'logout_path' => '/admin/logout'
),
'users' => $app->share(function() use ($app) {
return new AuthenticationSuccessHandler($app['db']);
}),
]
),
'security.access_rules' => array(
array('^/admin$', 'ROLE_ADMIN')
)
));
Any help?
Many Thanks!! ;-)
'users' => $app->share(function() use ($app) {
return new AuthenticationSuccessHandler($app['db']);
}),
The above function needs to return an object which implements
Symfony\Component\Security\Core\User\UserProviderInterface
Check here for custom user provider documentation
It may also be appropriate to move login_path outside the secured area. Another way of configuring would be:
$app['security.firewalls'] = array(
'secure' => array(
'pattern' => '^/admin/',
'form' => array('login_path' => '/login', 'check_path' => '/admin/auth'),
'users' => $app->share(function () use ($app) {
return new MyUserProvider($app['db']);
}),
),
),
);
$app['security.access_rules'] = array(
array('^/admin', 'ROLE_ADMIN')
);
Make sure you register doctrine dbal.
We're using ZF2 for an web-application and want to test it with phpunit (v4.8.9). Within this application we've got a scheme-route, to be able to switch between http/https-context (Doesnt matter why...). The route looks like this:
'http' => array(
'type' => 'Scheme',
'options' => array(
'scheme' => 'http',
'defaults' => array(
'http' => true
)
),
'child_routes' => array(
'search' => array(
'type' => 'segment',
'options' => array(
'route' => '/search[/:keyword[/:page]]',
'constraints' => array(
'page' => '[1-9]+[0-9]*'
),
'defaults' => array(
'controller' => SearchController::class,
'action' => 'search',
),
),
),
),
),
'https' => array(
'type' => 'Scheme',
'options' => array(
'scheme' => 'https',
'defaults' => array(
'https' => true
)
),
'child_routes' => array(
'search' => array(
'type' => 'segment',
'options' => array(
'route' => '/search[/:keyword[/:page]]',
'constraints' => array(
'page' => '[1-9]+[0-9]*'
),
'defaults' => array(
'controller' => SearchController::class,
'action' => 'search',
),
),
),
),
),
The class of the test looks like this:
class SearchControllerTest extends SynHttpControllerTestCase
{
public function setUp()
{
$this->setApplicationConfig($this->getCurrentBootstrap()->getApplicationConfig());
parent::setUp();
$this->getApplicationServiceLocator()->setAllowOverride(true);
}
public function testSearchActionCanBeAccessed()
{
$this->dispatch('/search');
$this->assertResponseStatusCode(200);
$this->assertControllerName(SearchController::class);
$this->assertControllerClass('SearchController');
$this->assertActionName('search');
$this->assertMatchedRouteName('search');
}
}
FYI:
The "SynHttpControllerTestCase" is an extension from the original AbstractHttpControllerTestCase which comes with Zend-Test. It's modified to get the right bootstrap-file in our tests.
If we're running the tests, this error appears:
Fatal error: Call to a member function getParam() on null in C:\xampp\htdocs\git\xxx\vendor\zendframework\zend-test\src\PHPUnit\Controller\AbstractControllerTestCase.php on line 563
We looked into the AbstractControllerTestCase and this line is throwing the error:
$routeMatch = $this->getApplication()->getMvcEvent()->getRouteMatch();
Because the $routeMatch-Object is empty.
We've some other controllers and tests within our application, they're all fine and not affected from this problem, cause the routes to these controllers arent scheme-routes.
Do you have any ideas how to solve this? In advance: we're not able to use static https-routes in this case.
There is a lot of official documentation on how to write a proper controller test. In your setUp method you need to attach a Router instance to a RouteMatch instance which you attach to a MvcEvent in the controller.
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new IndexController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array('controller' => 'index'));
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
Then you can call dispatch.
UPDATE
Can you not set your route match object like this:
$routeParams = array('controller' => 'search', 'action' => 'search');
$routeMatch = new RouteMatch($routeParams);
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$event = new MvcEvent();
$event->setRouter($router);
$event->setRouteMatch($routeMatch);
$this->getApplication()->setMvcEvent($event);
How in Silex redirect admin (ROLE_ADMIN) to /admin page after successful login and user (ROLE_USER) to / page after successful login?
My config so far:
$app['security.firewalls'] = array(
'login' => array(
'pattern' => '^/login$',
),
'secured' => array(
'pattern' => '^.*$',
'form' => array('login_path' => '/login', 'check_path' => '/login_check'),
'logout' => array('logout_path' => '/logout'),
'users' => $app->share(function() use ($app) {
return new App\User\UserProvider($app['db']);
}),
),
);
$app['security.access_rules'] = array(
array('^/admin', 'ROLE_ADMIN', 'http'),
array('^.*$', 'ROLE_USER'),
);
Thx in advance
I think there are a few ways to do this - I would recommend adding a new controller for /login/redirect and then sending people there after login. The controller can then perform the logic for where to send users based on their roles.
class LoginRedirect implements ControllerProviderInterface {
public function connect(Application $app) {
$controller = $app['controllers_factory'];
$controller->get('/', array($this, 'index'))->bind('login-redirect');
return $controller;
}
public function index(Application $app) {
if ($app['security']->isGranted('ROLE_ADMIN')) {
return $app->redirect($app['url_generator']->generate('admin-home'));
}
return $app->redirect($app['url_generator']->generate('non-admin-home'));
}
}
Add a route for it:
$app->mount('/login/redirect', new Controller\LoginRedirect());
And then in your security firewall settings add the options in the form section to use this route as the default target path - i.e. where all users are redirected to after login. Note that with this setting, you will loose the feature where users are redirected to the HTTP referer.
...
'form' => array(
'login_path' => '/login',
'check_path' => '/login_check',
'always_use_default_target_path' => true,
'default_target_path' => '/login/redirect'
),
...
im developing small app and I decided that i will use CakePhp as a framework, i was doint tutorial to make "posts". But when i wanted to use funcionality Simple Authentication and Authorization Application from here i was doing copy and paste and encountered 2 issues
first my User model doesn't see SimplePasswordHasher
App::uses('SimplePasswordHasher', 'Controller/Component/Auth');
class User extends AppModel {
public $validate = array(
'username' => array(
'required' => array(
'rule' => array('notEmpty'),
'message' => 'A username is required'
)
),
'password' => array(
'required' => array(
'rule' => array('notEmpty'),
'message' => 'A password is required'
)
),
'role' => array(
'valid' => array(
'rule' => array('inList', array('admin', 'author')),
'message' => 'Please enter a valid role',
'allowEmpty' => false
)
)
);
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$passwordHasher = new SimplePasswordHasher(); <---- here
$this->data[$this->alias]['password'] = $passwordHasher->hash(
$this->data[$this->alias]['password']
);
}
return true;
}}
maybe App::uses('SimplePasswordHasher', 'Controller/Component/Auth'); doesn't point in right place but i didn't found a way to check it.
second issue is when i try to enter at login page i got Authentication adapter "Form" was not found. Where i can init that adapter. any help would be great.
You must specify the same SimplePasswordHasher in controller as well as follows:
public $components = array(
'Auth' => array(
'loginAction' => array(
'controller' => 'users',
'action' => 'login'
),
'authError' => 'Did you really think you are allowed to see that?',
'authenticate' => array(
'Form' => array(
'passwordHasher' => array(
'className' => 'Simple',
'hashType' => 'sha256'
)
)
),
'loginRedirect' => array(
'controller' => 'users',
'action' => 'index'
)
)
);
Hope it helps
I have created Auth and Acl modules using zend-framework2, And these two are working good. Now I want to use these modules multiple times with different configuration.
Basically, I have two sections in my project -
User Section
Admin Section
And both have different session variables and different database tables.
I have Auth configuration file as following (module.config.php) -
return array(
'auth' => array(
'db' => array(
'table' => 'user',
'identity' => 'email',
'credential' => 'password',
'credential_treatment' => array(
'class' => '\My\Common',
'method' => 'encrypt'
),
'status' => array(
'is_enabled = true',
),
),
'view' => array(
'label' => array(
'identity' => 'Email',
),
),
'route' => array(
'login' => 'home',
'logout' => 'home',
),
'whitelist' => array(
'home',
)
),
....
....
);
I want to use the same module for both Admin section and User section, But with different configuration settings, Like different database tables and session variables.
Is it possible to do so or I have to create different modules for different section?
Let me know if you need more details.
Here's an simple approach for your use case. I didn't test it, so expect some typos :)
I hope you get the basic idea. If not, let me know.
config/autoload/auth.global.php
return [
'service_manager' => [
'factories' => [
'YourAuthNamespace\Config' => 'YourAuthNamespace\Service\ConfigServiceFactory',
'YourAuthNamespace\AbstractAuthFactoryFactory' => 'YourAuthNamespace\Service\AbstractAuthFactoryFactory',
],
'abstract_factories' => [
'YourAuthNamespace\AbstractAuthFactoryFactory'
]
]
];
src/YourAuth/Service/AbstractAuthFactoryFactory.php
namespace YourAuth\Service;
class AbstractAuthFactoryFactory implements \Zend\ServiceManager\AbstractFactoryInterface
{
public function canCreateServiceWithName(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
$key = $this->getConfigKeyFromServiceName($name);
$config = $this->getConfig($serviceLocator);
return array_key_exists($key, $config);
}
private function getConfig(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator)
{
return $serviceLocator->get('YourAuthNamespace\Config');
}
public function createServiceWithName(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
$key = $this->getConfigKeyFromServiceName($name);
$config = $this->getConfig($serviceLocator);
return new YourAuthClass($config[$key]);
}
private function getConfigKeyFromServiceName($name)
{
return preg_replace('#^YourAuthNamespace\Auth\#i', '', $name);
}
}
src/YourAuth/Service/ConfigServiceFactory.php
namespace YourAuth\Service;
use Zend\ServiceManager\FactoryInterface;
class ConfigServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('Config');
return $config['yourauth'];
}
}
module/MyModuleA/config/module.config.php
return [
'yourauth' => [
'ModuleA' => [ // 'ModuleA' is also the key used by the abstract factory
'db' => [
'table' => 'module_a_auth_table',
// ...
],
'session' => [
'namespace' => 'module_a_session_namespace'
// ...
],
]
],
'service_manager' => array(
'factories' => array(
'MyModuleA\Auth' => function ($sm) {
return $sm->get('YourAuthNamespace\Auth\ModuleA');
}
),
),
];
module/MyModuleA/src/AuthClient.php
namespace MyModuleA;
class AuthClient implements \Zend\ServiceManager\ServiceLocatorAwareInterface
{
public function doSomethingWithAuth()
{
if ($this->getServiceLocator()->get('MyModuleA\Auth')->isAuthorized()) {
// blah...
}
}
}