Propel and FOSUserBundle authentication in Symfony2

June 18, 2021

Share

Table of contents

Quick Access

This time I will be explaining how to manage users in Symfony2, basic features of its security system and how to implement an efficient users management system in less than 15 minutes, likewise, I will provide tips along the blog to avoid spending time searching the internet and concentrate in our application. We assume that there is basic knowledge of PHP and you have a basic installation of Symfony2 to test the code provided. One of the most sensitive parts of developing a web application is the management of users, because actions must be taken in relation to safety, or they can become a tedious and repetitive work. Symfony2 has a fairly robust and complete security system, which is one of the main features of the framework, but by default does not have tools such as a login form or other necessary actions for user management. One of the most common implementations of Symfony2 is to use Propel to handle database, Propel is an ORM to PHP5 that allows you to use a set of objects for manipulating database and is one of the Bundles mostly used in Symfony2. Fortunately, contributors of Friends Of Symfony created a set of tools that adapt to Symfony2 security component, enabling a simple and powerful way to manage users call FOSUserBundle, which can work very simply with propel. FOSUserBundle offers User.php default management class to propel users; see how to perform a basic configuration of these tools: In our config.yml put the following lines: [prism:php] providers: fos_userbundle: id: fos_user.user_provider.username firewalls: main: pattern: ^/ form_login: provider: fos_userbundle csrf_provider: security.csrf.token_manager logout: true anonymous: true access_control: - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY } [/prism:php] In this configuration we created a new security provider, indicating symfony to use the class defined by the service: fos_user.user_provider.username to validate users and reload them from the database. In addition, we indicate for the login, register and resseting routes (functionality provided by FosUserBundle) that could be accessed without being authenticated. Then configure FosUserBundle to use the user class propel and use the firewall we created earlier in security.yml: [prism:php] fos_user: db_driver: propel firewall_name: main user_class: FOS\UserBundle\Propel\User [/prism:php] Finally, we must load paths to the functionalities in routing.yml: [prism:php] fos_user: resource: "@FOSUserBundle/Resources/config/routing/all.xml" [/prism:php] With the above configuration loaded, all routes brought by the bundle, but if you want, you can implement specific routes. We have all the functionality that the bundle lists (they require changes in styles), but what happens if we have to overwrite the class FOSUser to propel and implement new features or methods? Well, in theory it would be easy, just extend that class by creating a custom class, adding our methods, and changing the class config.yml as follows: [prism:php] fos_user: db_driver: propel firewall_name: main user_class: Acme\MyBundle\Model\User [/prism:php] Although this solution works, from behind, things that shouldn't happen, happens, like the working class throughout the authentication process is the kind of FOSUser and not our custom class, then, there may be errors in our application as the login always fails even with registered users, corrupting everything related to our application users. This problem is somewhat difficult to recognize but easy to fix, it is simple, where the user resides in is loaded to pass the security component of Symfony2: service security.yml as mentioned above. [prism:php] public function refreshUser(SecurityUserInterface $user) { if (!$user instanceof User && !$user instanceof PropelUser) { throw new UnsupportedUserException(sprintf('Expected an instance of FOS\UserBundle\Model\User, but got "%s".', get_class($user))); } if (!$this->supportsClass(get_class($user))) { throw new UnsupportedUserException(sprintf('Expected an instance of %s, but got "%s".', $this->userManager->getClass(), get_class($user))); } if (null === $reloadedUser = $this->userManager->findUserBy(array('id' => $user->getId()))) { throw new UsernameNotFoundException(sprintf('User with ID "%d" could not be reloaded.', $user->getId())); } return $reloadedUser; } [/prism:php] What can occur in this situation is that the kind that reaches the user provider is not waiting, which is specifically the FOS\UserBundle\Propel\User class which is never going to get, as we have overwritten the class to FOSUser Acme\MyBundle\Model\User. This is a problem with multiple discussions, and basically, the problem is that the class is explicitly written in the user provider, so the solution is to create a custom user provider to work with our custom class. To solve this, first thing to do is to create our user provider that extends the user provider we need to change: [prism:php] //Acme\MyBundle\Security\UserProvider class UserProvider extends use FOS\UserBundle\Security\UserProvider { /** * @var UserManagerInterface */ protected $userManager; /** * Constructor. * * @param UserManagerInterface $userManager */ public function __construct(UserManagerInterface $userManager) { $this->userManager = $userManager; parent::__construct($userManager); } public function refreshUser(UserInterface $user) { if (!$user instanceof User && !$this->supportsClass(get_class($user))) { throw new UnsupportedUserException(sprintf('Expected an instance of FOS\UserBundle\Model\User, but got "%s".', get_class($user))); } if (null === $reloadedUser = $this->userManager->findUserBy(array('id' => $user->getId()))) { throw new UsernameNotFoundException(sprintf('User with ID "%d" could not be reloaded.', $user->getId())); } return $reloadedUser; } } [/prism:php] Then we declare it as a service services.yml in our Bundle: [prism:php] //Acme\MyBundle\Resources\config\services.yml services: my.user.provider: class: Rootstack\Rootnet\UserBundle\Security\UserProvider arguments: - @fos_user.user_manager.default [/prism:php] And finally, we tell FOSUserBundle to use our new user provider in security.yml [prism:php] providers: fos_userbundle: id: my.user.provider [/prism:php] Now, our problem is that although the login and other functions work properly, is still going to the wrong kind in the application. Now our problem is a mapping. For some reason, the user class we extended instead of ours passed and unfortunately, there is no way to solve it without the help of an additional tool to make our class mapping correctly. GlorpenPropelBundle allows propel extend functionality by adding additional events and other features we need, in order to solve our mapping problem. Installation is simple: [prism:php] composer require glorpen/propel-bundle [/prism:php] Then add the following lines in config.yml specifically in the propel section: [prism:php] build_properties: propel.behavior.extend.class: 'vendor.glorpen.propel-bundle.Glorpen.Propel.PropelBundle.Behaviors.ExtendBehavior' propel.behavior.default: "extend" [/prism:php] Finally: [prism:php] glorpen_propel: extended_models: FOS\UserBundle\Propel\User: Acme\MyBundle\Model\User [/prism:php] Now our application works as it should work with our custom user class, sparing future super strange errors in the authentication process. FOSUserBundle is almost an extension of Symfony2, and as we saw, is really simple and incredibly fast to implement with propel to set up as all things that come by default, as indicated only wanted to use it and be ready, we do not have to worry about the logic of authentication and user management and we are dedicated to the application logic. I hope that this mini tutorial helps you working with FOSUserBundle, is the right solution for user management. Soon I will be writing about the handling of sessions with JWT and FOSUserBundle in Symfony2.