Official documentation of Silex provides a lot of very useful examples to start a project. However, there is no complete example of setting up MySQL authentication. Here are the needed steps to accomplish that.
Introduction
For our example, we will secure the entire application, this means that to access any URL, user will need the role ROLE_USER, which corresponds to an authenticated user.
Prerequisites
1. DoctrineServiceProvider
$app->register(new Silex\Provider\DoctrineServiceProvider(), array( 'db.options' => array( 'driver' => 'pdo_mysql', 'dbhost' => 'localhost', 'dbname' => 'mydbname', 'user' => 'root', 'password' => '', ), )); |
2. SessionServiceProvider
$app->register(new Silex\Provider\SessionServiceProvider()); |
3. Namespace App
We will also need a specific class for authentication, here is how to declare a namespace called App. You can rename it as you wish.
Modify file composer.json
:
"autoload": { "psr-0": { "App": "app/src/" // path to specific classes of our application } } |
Register SecurityServiceProvider
This provider needs to update dependencies in composer.json
file. Add the following:
"require": { "symfony/security": "2.1.*" } |
Registering provider:
$app->register(new Silex\Provider\SecurityServiceProvider(), array( 'security.firewalls' => array( 'foo' => array('pattern' => '^/foo'), // Example of an url available as anonymous user 'default' => array( 'pattern' => '^.*$', 'anonymous' => true, // Needed as the login path is under the secured area 'form' => array('login_path' => '/', 'check_path' => 'login_check'), 'logout' => array('logout_path' => '/logout'), // url to call for logging out 'users' => $app->share(function() use ($app) { // Specific class App\User\UserProvider is described below return new App\User\UserProvider($app['db']); }), ), ), 'security.access_rules' => array( // You can rename ROLE_USER as you wish array('^/.+$', 'ROLE_USER'), array('^/foo$', ''), // This url is available as anonymous user ) )); |
App\User\UserProvider class
<?php // app/src/App/User/UserProvider.php namespace App\User; 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\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; 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 users WHERE username = ?', array(strtolower($username))); if (!$user = $stmt->fetch()) { throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); } return new User($user['username'], $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'; } } |
Structure of table `users`
CREATE TABLE `users` ( `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `username` VARCHAR(100) NOT NULL DEFAULT '', `password` VARCHAR(255) NOT NULL DEFAULT '', `roles` VARCHAR(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `unique_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
User creation for test
-- Password is: password INSERT INTO `users` (`username`, `password`, `roles`) VALUES ('johann', 'BFEQkknI/c+Nd7BaG7AaiyTfUFby/pkMHy3UsYqKqDcmvHoPRX/ame9TnVuOV2GrBH0JK9g4koW+CgTYI9mK+w==', 'ROLE_USER'); |
The password has been generated with code below:
<?php echo $app['security.encoder.digest']->encodePassword('password', ''); // BFEQkknI/c+Nd7BaG7AaiyTfUFby/pkMHy3UsYqKqDcmvHoPRX/ame9TnVuOV2GrBH0JK9g4koW+CgTYI9mK+w== |
Example of use with Twig
Add dependency in composer.json
file:
"require": { "symfony/twig-bridge": "2.1.*" } |
use Symfony\Component\HttpFoundation\Request; $app->register(new Silex\Provider\TwigServiceProvider(), array( 'twig.path' => __DIR__ . '/views', )); $app->get('/', function(Request $request) use ($app) { return $app['twig']->render('index.twig', array( 'error' => $app['security.last_error']($request), 'last_username' => $app['session']->get('_security.last_username'), )); }); |
{% if is_granted('ROLE_USER') %} <p>Welcome {{ app.security.token.user.username }}!</p> <p><a href="{{ path('logout') }}">Log out</a></p> {% else %} <form action="{{ path('login_check') }}" method="post"> <p><label for="username">Username: </label><input id="username" type="text" name="_username" value="{{ last_username }}"></p> <p><label for="password">Password: </label><input id="password" type="password" name="_password"></p> <p><input type="submit" value="Log in"></p> {% if error %} <div class="error">{{ error }}</div> {% endif %} </form> {% endif %} |
Conclusion
This is a simple example that could be improved by using an ORM and our own User class, maybe in another article
24 Responses to “MySQL authentication in Silex, the PHP micro-framework”
Great article. But are you sure about dependencies ? I have
ReflectionException: Class Request does not exist
And I have no I idea which class php can’t find.
Request class comes from Symfony\Component\HttpFoundation\Request
You should add this line in top of your file :
use Symfony\Component\HttpFoundation\Request;
Thanks, It helps. But now I have error with Twig it can’t recognize is_granted(). Is this additional extention?
Yes you have to add dependency manually:
“require”: {
“symfony/twig-bridge”: “2.1.*”
}
‘Identifier “security.authentication_providers” is not defined. Very wired … I’m already have ‘users’ => $app->share(function() use ($app)
{
// Specific class App\User\UserProvider is described below
return new App\User\UserProvider($app['db']);
})
Great article. Having it in the cookbook for Silex documentation would make it accessible to everybody.
Nice article. However, I keep getting an annoying error saying:
Twig_Error_Syntax: The function “path” does not exist in “index.twig” at line 3
Any idea? I have looked around the web but no one seems to have had the same problem.
Try twig.path
In fact ignore my previous comment, that doesn’t work.
Instead make sure you have registered UrlGeneratorServiceProvider, this solved it for me.
Thanks for the great article !
Thank you, very good article. Good start to “full feature” authentication.
Hi,
Thanks for writing this tutorial. I am very new to Silex/Symfony and coming from Zend Framework I am in sort of a transition phase.
I tried to integrate this into a simple project, but I keep getting: Bad credentials.
Would anyone have a look at the example code here: https://github.com/9livesdevelopment/silex-mongoauth-skeleton
I’ve commented all MySQL code, and hardcoded some username and password in. MySQL will be replaced with Mongo in my project.
Thanks in advance for anyone who can shed some light on this.
Problem solved. Everything works like a charm now.
The problem was I tested without encrypted passwords first and after that an encrypted version of an empty string which is no good.
All works perfectly now with an encrypted string > 0 chars.
Hi,
I have the same issue with error ‘Bad credentials’, I also debug the user object in the App\User\UserProvider.php is correctly fetched from the database. I understand that this is not the only part of user authentication, and it looks like that error message came from:
vendor/symfony/security/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php
Where should I go to debug the underlying problem?
Thanks
Do you have a link to your code? My problem was located in the supportsClass function. Make sure it uses the correct user class
I’m getting a fatal error:
Fatal error: Class ‘Doctrine\DBAL\Configuration’ not found in (..) vendor/silex/silex/src/Silex/Provider/DoctrineServiceProvider.php
How can I fix this?
Bonjour,
Juste une question … Faut il écrire la fonction qui correspond à la route du check_path ?
Quelque chose du genre
$app->post(‘/connexion’…
Par avance meri
Non, check_path est définie automatiquement par Silex.
Extrait de la doc : “The admin_login_check route is automatically defined by Silex and its name is derived from the check_path value (all / are replaced with _ and the leading / is stripped).”
Très bon article, que je bookmark de ce pas !
Thanks for the article! This utilises PHP for the config – I’m using YAML. Is there any way to link the users to the right class using YAML instead of PHP?
Alors là un grand merci pour ton article.
newbie sur ce framework, j’avais juste oublié d’ajouter /index.php dans mon URL pour que ça fonctionne (vu que je ne me suis pas encore occupé du htaccess)
Reste maintenant à décortiquer le code pour être capable de le reproduire …
Very simplified version…
One question-
I am new to silex…What are the considerations if we want to just use Rest api based authentication tokens for server and client side…
Thank you for providing this simple tutorial allowing me to create my skeleton for several projects.
That being said, I kept having the “bad credentials” error and none of the exposed solutions did work for me.
But, after looking at the source code, I saw that the password validity was checked with the “password_verify()” function of php. So, instead of creating the password with “$app['security.encoder.digest']->encodePassword(‘password’, ”);”, I created it with “password_hash(‘password’,PASSWORD_BCRYPT)” and it worked. But I’m not happy with it, I would rather create the password with the “encodePassword” of the Symfony’s security encoder.
Any suggestion ?
Je suis constamment redirigé sur login meme quand la connexion est bonne?!
Comment rediriger ailleurs ?
Merci
What’s up to every body, it’s my first go to see of this weblog; this web site consists of amazing and actually fine data in favor of readers.