Since a post on security was requested, I am going to show you how to secure a route prefix in your application. The symfony2 security component is very powerful and complex. This implementation will be simple, but you should be able to easily build on it. For securing a production application I would strongly recommend using the FOSUserBundle which can be found here. This bundle is written by some of the core developers of symfony2 and will most likely become the “sfGuardPlugin” for symfony2. The symfony1 folks will know what I mean. I am going to forego writing tests for this part because I want to get it out there as fast as possible, since people seem to be having trouble with security. I may write some tests later and update this post with them. [Update: I have done this. The post can be found here.]
In this part we are going to require anyone who tries to access a route that begins with “/admin/” to login using a form. To do this we will need to do a few things. First we need to register the SecurityBundle that ships with the symfony2 framework. Lets do that now. Open up the AppKernel.php file located in the app directory. We need to register the SecurityBundle so find the registerBundles method and add the following bundle to bundles array:
new Symfony\Bundle\SecurityBundle\SecurityBundle()
Now that we have registered the bundle we can start having fun. Before we start to write some code, we need to understand the fundamentals of how the security component in symfony2 operates. The symfony2 security component can at a high level be broken down into three different subcomponents; Users, Authentication and Authorization. The Users subcomponent simply represents the client that is using your application. The Authentication subcomponent tries to ensure that the user is who he claims to be. The Authorization subcomponent decides whether or not the user, once authorized, is allowed to perform certain actions, view certain data, etc.
Now we are ready configure our application security. In our configuration we are going to tell symfony2 that we want to use our Company\BlogBundle\Entity\User entity as our User, how to encode user passwords, which part of the application should be secure and what role the user should have to access those parts. Open up the config.yml in the app/config directory and add the following configuration:
## Security Configuration
security:
encoders:
Company\BlogBundle\Entity\User:
algorithm: sha512
encode-as-base64: true
iterations: 10
providers:
main:
entity: { class: BlogBundle:User, property: username }
firewalls:
main:
pattern: /.*
form_login:
check_path: /login_check
login_path: /login
logout: true
security: true
anonymous: true
access_control:
- { path: /admin/.*, role: ROLE_ADMIN }
- { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
Lets go over each section in our security configuration. First, take a look at the encoders section. In this section, we define how the user passwords will be encoded for the Company\BlogBundle\Entity\User entity. We will be using the MessageDigestPasswordEncoder that comes with the symfony2 framework, but it is certainly possible to write your own. Here we specify that we want to use sha512 as our encoding algorithm, iterated 10 times and to encode as a base64 string. We will explore encoders in more detail when we modify our fixtures in a just a moment. You can read more about encoders here if you want.
Next is the providers section. The providers is where we tell symfony2 how to retrieve users. We use the entity entry to tell symfony2 that we want to use the Doctrine Entity Provider. Other available providers are In-Memory Provider and Chain Provider. You can read about them here. Under the entity entry we have to define the entity class and the username property. The class tells symfony2 what entity to load to represent a user. Here we have specified that we want to use the User class in the BlogBundle. The property entry is the PHP column name for the username and in our entity class it will be username.
Authorization is managed by the Firewall system in symfony2. This system is made up of listeners that listen for the core.security event and then redirect the request based on the credentials of the user if necessary. The firewalls entry defines the routing pattern for which we want the security listeners to listen. As of right now, the recommended way to secure an application is to define a single firewall that listens to all routes and then use the access_control entry to allow or disallow access based on roles. We will see the access_control entry in just a moment. In the firewalls entry we have used the pattern entry to specify that we want the security listeners to listen for every route. The form_login entry specifies that our authentication method is to login via a form. You can read about other methods here. Under the form_login entry we specify the routes for the login_path and the check_path. The form_login entry has many more options which you can read about here.
Lastly, we have the access_control entry. The entries under access_control specify routing patterns and the roles necessary to access them. We have specified that for any route starting with “/admin/” the ROLE_ADMIN role is required for access otherwise the only role needed is the IS_AUTHENTICATED_ANONYMOUSLY. This is a special role provided by symfony2 that every user has.
Now that our security configuration is in place we need to update our entity classes to conform to the interfaces required by the SecurityBundle. We need to modify our User entity to implement the UserInterface interface. We also need to create a Role class that implements the RoleInterface. Then we will need to modify our fixtures to load the new data into our database.
Create a new file named Role.php in the src/Company/BlogBundle/Entity directory. Here is the full Role class:
namespace Company\BlogBundle\Entity; use Symfony\Component\Security\Core\Role\RoleInterface; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="role") */ class Role implements RoleInterface { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") * * @var integer $id */ protected $id; /** * @ORM\Column(type="string", length="255") * * @var string $name */ protected $name; /** * @ORM\Column(type="datetime", name="created_at") * * @var DateTime $createdAt */ protected $createdAt; /** * Gets the id. * * @return integer The id. */ public function getId() { return $this->id; } /** * Gets the role name. * * @return string The name. */ public function getName() { return $this->name; } /** * Sets the role name. * * @param string $value The name. */ public function setName($value) { $this->name = $value; } /** * Gets the DateTime the role was created. * * @return DateTime A DateTime object. */ public function getCreatedAt() { return $this->createdAt; } /** * Consturcts a new instance of Role. */ public function __construct() { $this->createdAt = new \DateTime(); } /** * Implementation of getRole for the RoleInterface. * * @return string The role. */ public function getRole() { return $this->getName(); } } |
The Role entity is quite simple. There is only one property named name that houses the name of the role. The class also implements the RoleInterface interface by supplying a getRole method, which just returns the name of the role. Now that the Role class has been created, we need to update the User entity. Open up the User.php file in the src/Company/BlogBundle/Entity. Here are the relevant changes to the code:
namespace Company\BlogBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; /** * @ORM\Entity * @ORM\Table(name="user") */ class User implements UserInterface { // ... /** * @ORM\Column(type="string", length="255") * * @var string username */ protected $username; /** * @ORM\Column(type="string", length="255") * * @var string password */ protected $password; /** * @ORM\Column(type="string", length="255") * * @var string salt */ protected $salt; /** * @ORM\ManyToMany(targetEntity="Role") * @ORM\JoinTable(name="user_role", * joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")} * ) * * @var ArrayCollection $userRoles */ protected $userRoles; // ... /** * Gets the username. * * @return string The username. */ public function getUsername() { return $this->username; } /** * Sets the username. * * @param string $value The username. */ public function setUsername($value) { $this->username = $value; } /** * Gets the user password. * * @return string The password. */ public function getPassword() { return $this->password; } /** * Sets the user password. * * @param string $value The password. */ public function setPassword($value) { $this->password = $value; } /** * Gets the user salt. * * @return string The salt. */ public function getSalt() { return $this->salt; } /** * Sets the user salt. * * @param string $value The salt. */ public function setSalt($value) { $this->salt = $value; } /** * Gets the user roles. * * @return ArrayCollection A Doctrine ArrayCollection */ public function getUserRoles() { return $this->userRoles; } /** * Constructs a new instance of User */ public function __construct() { $this->posts = new ArrayCollection(); $this->userRoles = new ArrayCollection(); $this->createdAt = new \DateTime(); } /** * Erases the user credentials. */ public function eraseCredentials() { } /** * Gets an array of roles. * * @return array An array of Role objects */ public function getRoles() { return $this->getUserRoles()->toArray(); } /** * Compares this user to another to determine if they are the same. * * @param UserInterface $user The user * @return boolean True if equal, false othwerwise. */ public function equals(UserInterface $user) { return md5($this->getUsername()) == md5($user->getUsername()); } // ... } |
If you would rather just download the new User.php than make all of the changes by hand, you can find it here. Nothing special in our new User entity really. We have conformed to the UserInterface interface and also set up a new many-to-many relationship with the Role entity. There is also an AdvancedUserInterface that provides even more functionality, but I am not going to use it in this tutorial. You can read more about it here.
Now that our entities have been updated, we need to update our fixtures so that we can update the data in our database. Open up the FixtureLoader.php file in the src/Company/BlogBundle/DataFixtures/ORM folder. Here are the relevant code changes:
namespace Company\BlogBundle\DataFixtures\ORM; use Doctrine\Common\DataFixtures\FixtureInterface; use Company\BlogBundle\Entity\Category; use Company\BlogBundle\Entity\Post; use Company\BlogBundle\Entity\Tag; use Company\BlogBundle\Entity\User; use Company\BlogBundle\Entity\Role; use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; class FixtureLoader implements FixtureInterface { public function load($manager) { // create the ROLE_ADMIN role $role = new Role(); $role->setName('ROLE_ADMIN'); $manager->persist($role); // create a user $user = new User(); $user->setFirstName('John'); $user->setLastName('Doe'); $user->setEmail('john@example.com'); $user->setUsername('john.doe'); $user->setSalt(md5(time())); // encode and set the password for the user, // these settings match our config $encoder = new MessageDigestPasswordEncoder('sha512', true, 10); $password = $encoder->encodePassword('admin', $user->getSalt()); $user->setPassword($password); $user->getUserRoles()->add($role); $manager->persist($user); // ... } |
A new role with the name ROLE_ADMIN has been created. We have also added a username of john.doe to the user and set a random salt. The only new functionality here is the encoding of the password. Remember back in our security.encoders configuration entry we setup the configuration for a MessageDigestPasswordEncoder. Here we are creating an instance of that class and passing in the parameters as configured in our security.encoders entry. Then we are encoding the password “admin” and setting that encoded password as the password for our user. We have to encode the password using the same settings as we have told the security component of the framework to use or else we wont be able to supply credentials for authentication.
Before we can update our routing and create new controllers and views, we have to run a few commands from the console. Open up a terminal and change to your base project directory. First run the following command to update our database schema to match that of our entities.
php app/console doctrine:schema:update --force
Next, run the following command to clear out the current contents of the database and reload the data using the new updated fixtures.
php app/console doctrine:data:load
At this point we have our security configuration, entities and data all set up. Now we are going to update our routing and create some new controllers and views to implement the form login. Open up the routing.yml file in the src/Company/BlogBundle/Resources/config directory. Add the following routes to the top of the file.
_security_login:
pattern: /login
defaults: { _controller: BlogBundle:Security:login }
_security_check:
pattern: /login_check
_security_logout:
pattern: /logout
admin_home:
pattern: /admin/
defaults: { _controller: BlogBundle:Admin:index }
We have added some special security routes to our routing file. These routes will be used by us as well as the security component of the symfony2 framework. You might be wondering why we did not define controllers for two of the routes. Remember how I said that the security component worked by listening to the core.security event? As a result of this, the controllers for these routes will never need to be resolved because the security component will intercept the request and handle it. So we can safely use the route names _security_check and _security_logout in our templates. Also, we have added the admin_home route which will act as the home page for the admin section of our application. Since we configured the security.access_control entry of our configuration to only allow a user with the role ROLE_ADMIN to access any route pattern that started with “/admin/”, the admin_home route is the secured route we will be trying to access in just a bit.
You may have also noticed that we are going to be creating two new controllers, the SecurityController and AdminController, so lets do that now. Create a new file named AdminController.php in the src/Company/BlogBundle/Controller directory. Here is the code for the AdminController class.
namespace Company\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class AdminController extends Controller { public function indexAction() { return $this->render('BlogBundle:Admin:index.html.twig'); } } |
Nothing fancy about the indexAction. It simply renders the index.html.twig template. So lets create that now. Create a new folder in the src/Company/BlogBunde/Resources/views folder named Admin. In this new folder create a file named index.html.twig. Here is the template for the admin home page.
{% extends "BlogBundle::layout.html.twig" %}
{% block title %}
symfony2 Blog Tutorial | Admin | Home
{% endblock %}
{% block content %}
<h2>
Welcome to the Admin Homepage {{ app.user.username }}!
</h2>
{% endblock %} |
The only new thing in this template is the app.user template variable. The app.user variable is how you access the currently logged in user. So, in our application the app.user variable translates to an instance of Company\BlogBundle\Entity\User.
Now that we have our secured page created lets create the SecurityController. Create a new file named SecurityController.php in the src/Company/BlogBundle/Controller folder. Here is the code for the SecurityController class.
namespace Company\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Security\Core\SecurityContext; class SecurityController extends Controller { public function loginAction() { if ($this->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { $error = $this->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR); } else { $error = $this->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR); } return $this->render('BlogBundle:Security:login.html.twig', array( 'last_username' => $this->get('request')->getSession()->get(SecurityContext::LAST_USERNAME), 'error' => $error )); } } |
We created a loginAction method which is what we mapped the _security_login route to. In this action we are first checking for an error and then rendering the login.html.twig template as a response. The error checking may look weird, but basically we are just checking to see if we have been forwarded or redirected to this action. If we have, then we are getting the exception that was generated.
The security component will handle all of the credential validation for us, but we must create the template and supply the necessary parameters. In order for the security component to do the validation for us we must submit a form with _username and _password fields to the _security_check route. Lets create a template that does this. Create a new folder in the src/Company/BlogBunde/Resources/views folder named Security. In this new folder create a file named login.html.twig. Here is the code for the new login template.
{% extends "BlogBundle::layout.html.twig" %}
{% block title %}
symfony2 Blog Tutorial | Login
{% endblock %}
{% block content %}
{% if error %}
<div class="error">{{ error.message }}</div>
{% endif %}
<form action="{{ path('_security_check') }}" method="POST">
<table>
<tr>
<td>
<label for="username">Username:</label>
</td>
<td>
<input type="text" id="username" name="_username" value="{{ last_username }}" />
</td>
</tr>
<tr>
<td>
<label for="password">Password:</label>
</td>
<td>
<input type="password" id="password" name="_password" />
</td>
</tr>
</table>
<input type="submit" name="login" value="submit" />
</form>
{% endblock %} |
There is nothing that you should not understand in this template. It is simply submitting our form to the _security_check route. When we submit a form to this route the security component of symfony2 will intercept the request and handle the user authentication for us. If the user is authenticated then he will be redirected to the initial destination otherwise he will be redirected back to the login page.
At some point you may want to logout of the application. Lets create a logout link that is only shown when a user has logged in. Open the layout.html.twig file in the src/Company/BlogBundle/Resources/views directory. Here is the relevant code change to that file.
// ...
{% block body %}
<div id="container">
<header class="clearfix">
<h1>
symfony2 Blog Tutorial
</h1>
<nav>
<ul>
<li>
<a href="{{ path('show_page', { 'page' : 'about' }) }}">
About
</a>
</li>
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
<li>
<a href="{{ path('_security_logout') }}">
Logout
</a>
</li>
{% endif %}
</ul>
</nav>
</header>
// ... |
We have used a new twig method named is_granted to check to see if the current user is a specific role. The role we check is IS_AUTHENTICATED_FULLY. This is a special role that a user will have if they have been authenticated by the symfony2 security component. If the user has this role then we add another item to our navigation which is a link to the _security_logout route that we defined earlier.
We are finally ready to try our new secured page. Before we do I would suggest you clear your cache. There is no clear cache command yet, but you can either manually delete all of the files and folders in the app/cache directory or if you are on linux/mac you can run the command rm -rf app/cache/* from your base project directory. [Update: There is now a clear cache command. It is php app/console cache:clear.] Now that you have cleared your cache navigate to the /admin/ path in your web browser. You should be redirected to the login page which looks similar to the following image:
Enter john.doe for the username and admin for the password. When you submit these credentials you should then be redirected to the admin home page which looks similar to this image:
You should also see the Logout link in the header navigation. If you click the Logout link you should be logged out and then redirected to the home page. Whew! That was a lot of work and a lot of trial and error on my part that you didn’t see! We have successfully secured a route prefix in our application. As I said earlier, I would strongly recommend using the FOSUserBundle in a production application. It has a lot more functionality than I have described in this tutorial. I hope you were able to follow along. If any part needs clarification, just ask. Leave me a comment and let me know what you would like to see next. As of right now, I am planning on exploring the symfony2 container and other internals. Until next time…
Update: I created a test for the admin index action that involves logging in using the security bundle. Unfortunately at this time sessions are not supported in the testing framework, so the test will always fail. I am only posting this so you all can learn more about testing. You can follow the comments to understand what is going on. If you want to you could edit your config_test.yml file to change your security configuration and create an http basic user and supply the credentials with the request. Right now this is really the only way to test secured routes. An example of this can be found in the Testing section of the symfony2 book on the symfony2 website.
namespace Company\BlogBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class AdminControllerTest extends WebTestCase { public function testIndex() { $client = $this->createClient(); $client->followRedirects(true); // request the index action $crawler = $client->request('GET', '/admin/'); $this->assertEquals(200, $client->getResponse()->getStatusCode()); // select the login form $form = $crawler->selectButton('submit')->form(); // submit the form with bad credentials $crawler = $client->submit( $form, array( '_username' => 'john.doe', '_password' => 'wrong_password' ) ); // response should be success $this->assertTrue($client->getResponse()->isSuccessful()); // we should have been redirected back to the login page because // invalid credentials were supplied $this->assertTrue($crawler->filter('title:contains("Login")')->count() > 0); // select the login form $form = $crawler->selectButton('submit')->form(); // submit the form with valid credentials $crawler = $client->submit( $form, array( '_username' => 'john.doe', '_password' => 'admin' ) ); // response should be success $this->assertTrue($client->getResponse()->isSuccessful()); // check the title of the page matches the admin home page $this->assertTrue($crawler->filter('title:contains("Admin | Home")')->count() > 0); // check that the logout link exists $this->assertTrue($crawler->filter('a:contains("Logout")')->count() > 0); } } |


great tutorial. am new to symfony and decide to move my wheel with symfony2.
currently can not find any useful material like this.
please keep updating .
thansk
Same thing for me… Your tutorial is very interesting. It’s the first that I find about symfony2.
I follow you and build my blog with your tuto !
Thanks a lot !
It is possible to have a tutorial about form editing and eventually integration of Ajax request (wtih Jquery for example?)
Wow, Thanks, All things works except doctrine:data:load. I updated recent repository from github today. But I put fixture code in action then all work just expected.
And One additional change is security config becomes separate file.
Finally, Thank you very much,
Symfony Rock !!!
@titou
I am waiting on a form tutorial because the Form component of symfony2 is currently undergoing large changes.
@Erkhembayar Gantulga
It is not completely necessary to have a separate security.yml file. I have an up-to-date repository as of this morning and my doctrine fixtures work just fine. You may want to double check your code.
Also latest symfony head has a cache:clear command.
@Greg
I just noticed this earlier. Post updated.
A simple test for the admin_home route (AdminControllerTest.php)
namespace Company\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class AdminControllerTest extends WebTestCase
{
public function testIndex()
{
$client = $this->createClient();
$client->followRedirects(false);
$crawler = $client->request(‘GET’, ‘/admin/’);
// Not logged in should get a 302 redirect somewhere.
$this->assertTrue($client->getResponse()->getStatusCode() == ’302′ );
// Follow it and that should bring us to the login page.
$crawler = $client->followRedirect();
$this->assertTrue($client->getResponse()->getStatusCode() == ’200′ );
$this->assertTrue($crawler->filter(‘title:contains(“Login”)’)->count() > 0);
}
}
@Greg
I have written a test for admin index action, but the testing framework does not support sessions yet so it will always fail. I will post it in a bit.
@Dustin
Yeah I was trolling about trying to figure out how to test the login action and discovered that
Wow, that was fast
One thing I noticed is that when using 2.0PR7 (as I haven’t had time to update to PR8), you must define a `__toString()` method on your user Entity. Returning the username here is a sensible default.
Also, Symfony2.0′s docs indicate that you should be able to grab the currently configured encoder, though I haven’t tried this yet. Using this method means you would not have to put your encoder definition in two places:
$factory = $this->container->get(‘security.encoder_factory’);
$user = new User();
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword(‘MyPass’, $user->getSalt());
$user->setPassword($password);
@Craige
You should not have to implement a __toString() method because that is not part of the UserInterface. You must be trying to echo the User object somewhere in your code or something.
The way you described to get the encoder factory can be done in a Controller class, or more specifically a class that implements the ContainerAwareInterface. The method you posted would not work inside the fixture class. I am not sure of a way to access the container in the fixture class.
@Dustin – Nope. Exact error:
Catchable fatal error: Object of class xxxxxxxxx\UserBundle\Entity\User could not be converted to string in /src/project/vendor/symfony/src/Symfony/Component/Security/Http/Firewall/ContextListener.php on line 158
It’s something in the core of PR7.
@Craige
Odd. Must be PR7 specific. I have the latest head of the master branch and I get no such error.
@Dustin – Yes, that’s what I’m guessing.
You seem to to have been keeping along with Symfony pretty well. I’m going to update to PR8, but I’m not exactly sure what would need to change in my PR7 code. Any tips?
@Craige
I wish I could be more help on that, but I have been working off of the master branch for a few months now. I’m not sure exactly what has changed between PR7 and PR8. If you have trouble, feel free to contact me through the form on this site and I will try to help you out.
Great tutorial !
Did you write this with PR8 ? Is is compliant with PR7 or PR6 ?
@Christophe
I am using the master branch, but this should work with PR7 and PR8 I believe.
Yeah!
Great material indeed.
So. What about a new step beyond that blog application about the way to generate the admin section and working with forms furthermore data manipulation.
Thanks.
Regards.
Excellent!!
Best Symfony2 tuts collection out there
Very nice and complete guide.
But what Im starting to hate is the way you have to write everything from scratch. Do you know if there is any posibility that the core team will make life easier?
hmmm, I am getting this error:
Fatal error: Uncaught exception ‘LogicException’ with message ‘Trying to register two bundles with the same name “Security”‘ in /home/cordoval/sites/Symfony/symfony-standard/app/bootstrap.php.cache:691 Stack trace: #0 /home/cordoval/sites/Symfony/symfony-standard/app/bootstrap.php.cache(564): Symfony\Component\HttpKernel\Kernel->initializeBundles() #1 /home/cordoval/sites/Symfony/symfony-standard/app/bootstrap.php.cache(584): Symfony\Component\HttpKernel\Kernel->boot() #2 /home/cordoval/sites/Symfony/symfony-standard/web/app_dev.php(19): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request)) #3 {main} thrown in /home/cordoval/sites/Symfony/symfony-standard/app/bootstrap.php.cache on line 691
@Luis
For those who see in their config.yml an import of this sort
- { resource: security.yml }
you should rather than editing the config.yml, edit the security.yml and merge your annotations with the suggested above on this tutorial.
@Luis
Also I forgot to mention that the line:
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
was already present on the file and did not need to be added, actually it creates a problem when adding it for a second time. As described above.
For those who had not commented the acme example, it can interfere with this BlogBundle tutorial so I recommend to comment the routing out for the acme demo and the /admin section when authenticating it will not return a 500 internal server error
Also looking forward to learn more Symfony2, at this time you are the teacher so you decide on what is best for us to learn in order to work on applications, treat us as newbies since that is what most of us are. Don’t assume too much from us. Thanks!
FOSUserBundle sounds interesting but daunting!
I get this error
Fatal error: Uncaught exception ‘Doctrine\ORM\ORMException’ with message ‘Unknown Entity namespace alias ‘BlogBundle’.’ in \vendor\doctrine\lib\Doctrine\ORM\ORMException.php on line 114
Any ideas how to fix it?:(
Found it, had to rename BlogBundle to Blog in config.yml
I guess it’s because of the new change with getting rid of Bundle
Thank you so much for doing these guides, I have managed to get my head around most of Symfony2 but reading these guides help me understand it a little better, and I wish I found this site sooner.
Thank you so much for doing these guides, I have managed to get my head around most of Symfony2 but reading these guides help me understand it a little better, and I wish I found this site sooner [2]
Thanks for the detailed tutorial, I have a quick question on a problem I’m having.
When I created an Doctrine Entity implementing UserInterface as suggested and try generating doctrine:entities using console (php app/console doctrine:generate:entities AuthBundle) All my variables and public functions are being duplicated on the User class (or which ever class implements UserInterface). Can you confirm this does or does not happening to you too..
I’m using Latest Symfony 2 PR 12 !!!
Thanks..
@Jeevan
If you write out the entity and use annotations for the mapping then there is no need to run doctrine:generate:entities. That command is used when you write your mapping in yml or xml.
@Dustin
Thant make sense.. Thanks Dustin.. I was following the Tutorial on symfony and its asking to run that command to create the get/set.. Thanks for the info, I’ll continue without it
Hi everybody.
I just started using symfony 2 and having problems to understand what action will be perfomed within the login process?
Where does symfony the check, if somebody is logged in within the Security Controller?
Thx for any help…
Hi,
@Dustin you can access the container. Your DataFixture musst implements the ContainerAwareInterface and declare the Method setContainer. Then the DataFixture Loader set the container in your DataFixture and you can use it
Sorry for my english….
@David
Yes David, I learned that shortly after I wrote this, but was too lazy to update. For anyone reading this now there is a new DoctrineFixturesBundle that you should use for fixtures that allows for much better fixture coding. Check out the cookbook on symfony.com for details.
I tried on Ubuntu 10.x with no problems, but testing on RHEL 6.x login authentication is not working (the rest of the app works fine), have anyone test on RHEL and/or faced with the same issue, seems like session data lost
By the way I just test the default demo AcmeBundle and authentication is not working on RHEL same as this demo
Thanks in advanced
Here is a repository I made following the chapters in this tutorial:
https://github.com/VelvetMirror/dobervich
I used Symfony2 beta3, so the code is updated from the Symfony2 version used in this tutorials.
Hi, great tutorial. Is it possible to intercept the request or have intermediate processing before the security component does anything with it? I’m trying to pass on the credentials to a set of scripts running on the server during login process.
Thanks
@ByBy SV
You mean something like this: http://www.dobervich.com/2011/05/19/more-sophisticated-symfony2-login-redirection/
hi,
I’m new to symfony 2, is that someone can help me with these error messages?
1. in /Applications/MAMP/htdocs/Symfony2/app/bootstrap.php.cache line 1079
2. at Kernel->initializeBundles() in /Applications/MAMP/htdocs/Symfony2/app/bootstrap.php.cache line 867
3. at Kernel->boot() in /Applications/MAMP/htdocs/Symfony2/app/bootstrap.php.cache line 1038
4. at Kernel->loadClassCache() in /Applications/MAMP/htdocs/Symfony2/web/app_dev.php line 19
Looks like that’s what I’m after! Thanks
I keep getting this error after attempting to log in using the admin account credentials:
Unable to find template “CompanyBlogBundle:Info:login_check>.html.twig”
Anyone have any ideas?
Awesome tutorial, really great work! Thanks!
Dustin! Awesome… Thank you very much for this tutor…
Hi Dustin,
Thanks for interesting in-depth tutorial.
I want to create “always-successfull” dummy user provider. For that purpose I’ve created following class:
class User implements UserInterface
{
// …
/**
* @ORM\Column(type=”string”, length=”255″)
*
* @var string username
*/
protected $username;
/**
* @ORM\Column(type=”string”, length=”255″)
*
* @var string password
*/
protected $password;
/**
* @ORM\Column(type=”string”, length=”255″)
*
* @var string salt
*/
protected $salt;
/**
* Gets the username.
*
* @return string The username.
*/
public function getUsername()
{
return “me”;
}
/**
* Sets the username.
*
* @param string $value The username.
*/
public function setUsername($value)
{
$this->username = $value;
}
/**
* Gets the user password.
*
* @return string The password.
*/
public function getPassword()
{
return “me”;
}
/**
* Sets the user password.
*
* @param string $value The password.
*/
public function setPassword($value)
{
return “me”;
}
/**
* Gets the user salt.
*
* @return string The salt.
*/
public function getSalt()
{
return $this->salt;
}
/**
* Sets the user salt.
*
* @param string $value The salt.
*/
public function setSalt($value)
{
$this->salt = $value;
}
/**
* Compares this user to another to determine if they are the same.
*
* @param UserInterface $user The user
* @return boolean True if equal, false othwerwise.
*/
public function equals(UserInterface $user)
{
return true;
}
}
no roles implementation at all. Unfortunately I’m never authorized positively for me/me (user/passwd) maybe i’m wrong with assumption that only getuser and getpassword implementation is neccessary. Could you point which functions of User Interface are called to verify access, and which are neccessary to implement?
For all people using this tut:
You must confirm data insertion by using flush(). Otherwise your test user will be not written to database.
Also Doctrine needs id for each intity which is not placed in the example code.
Hello Dustin, thanks for the tutorial: it is one of the few organic and comprehensive ones on the Internet, so far.
I’m implementing form-login with Symfony2 and I want to load user data from my underlying database…
my question is: can I give my UserInterface implementation a name other than “User”? Will the framework recognize my implementation even if it is named “Account” (and not: “User”)???
The reason I want to do that is because I already have an “Account” entity which stores user data, and I am compelled to use it as I cannot modify my database schema.
This is what I’ve done:
1- make Account.php entity implement UserInterface interface and – thus – implemented all of the interface methods
2- changed my security.yml file’s user providers lines to:
providers:
main:
entity: { class: myBundle\Entity\Account, property: username }
3 – and I’m getting this error when trying to access login form:
There is no user provider for user “Symfony\Component\Security\Core\User\User”.
(500 Internal Server Error – RuntimeException)
Thanks for any help you would kindly give me!
Hi, I fixed the problem, I will post my fix for everyone to use..I had a look at StackOverflow (http://stackoverflow.com/questions/6588133/there-is-no-user-provider-for-user-symfony-component-security-core-user-user) and then changed my security.yml file’s user providers lines to:
providers:
chain_provider:
providers: [user_db, in_memory]
user_db:
entity: { class: myBundle\Entity\Account, property: username }
in_memory:
users:
[ etc etc etc...]
and magically everything was fine!
@Hubert
you’re totally right…
ATTENTION:
Doctrine needs id for each entity which is not placed in the example code…
This is part five of a series of tutorials. If you had taken time to read the others you would see that the $id member var does indeed exist in the User class.
Oops !!
my bad !!!
sorry…
it’s just this page kept showing on my google search, so i read just this part.
But still, i think users taking the same path i did (even though partial part) should be aware !!!
Thank you…
Great tutorial, thanks!!!
I define my db through yml files, so getUserRoles() method returns objects of Role.
I needed to perform a small modification which might help others:
Entity/User.php:
:
:
public function getRoles()
{
$callback = create_function(‘$obj’,'return $obj->getName();’);
$roleObjsArray = $this->getUserRoles()->toArray();
return array_map($callback,$roleObjsArray);
}
BTW, I wonder if someone can help:
By definition my application generates dirrefent urls which are linked to different users. So the authorization process should also take in account what is the URL, and permit login depending on combination {url + user_id + role}.
I don’t know where to put this check, and what might be the best practice. I saw in the book (http://symfony.com/doc/current/book/security.html#access-control-lists-acls-securing-individual-database-objects) that one might use ACL in order to obtain it, but looks to complicated for me. Any suggestions??
I wonder whether
Thanks buddy.. You are just awesome.. And the tutorial is just brilliant… Thank you once again..
GREAT POST!
Thank you for your tutorial, it was really usefull for me.
Hello
Instead of creating your own twig method ‘is_granted’
You can use:
{% if app.security.isGranted(‘IS_AUTHENTICATED_FULLY’) %}
If you don’t know about the global variables check out this:
http://nerdpress.org/2011/09/12/symfony-2-twig-global-variables/
Elliot
is_granted is not a custom twig function but rather it is a function provided by the SecurityBundle
Hey, did you get any further or update your Roles implementation?
I’m currently trying to figure out the best way to implement doctrine persisted Role entities as a M2M relationship compatible with FOSUserBundle.
Basically, it’s a pain in the ass. The interfaces are built for string representations, not Role entities. Change the implementation, you break a lot of stuff i.e. FOS commands to promote users. And it’s hard to figure out exactly which pieces of the interfaces are needed to allow the security system to continue working correctly.
Anyone had any experience or can think of a decent solution?
Using FOSUserBundle you could use the Groups feature that it ships with to use persisted roles. If you want to role your own Role class then you will have to extend your User class from FOS\UserBundle\Model\User and do all of the mapping yourself. This way you can set up the mapping for the $roles property as you need it.
Just finished your tutorial and had to leave a huge THANKS. As the Docu/SF bible isn’t complete at all I was totally confused when I ran into the fatal error of not overwriting the UserInterface methods. Your guide helped out of this. Good work
Hi, I’ve got a additional question – how can I add a new user with the already existing role? To do this, I need to fill not only the user table, but also users_roles table – how can I achieve that?
I’m adding user like that:
$manager = $this->getDoctrine()->getEntityManager();
$factory = $this->get(‘security.encoder_factory’);
// create new user entity
$user = new \Et\SecurityBundle\Entity\User();
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword(‘test’, $user->getSalt());
$user ->setIsActive(TRUE);
$user ->setEmail(‘test@test.com’);
$user ->setUsername(‘test’);
$user ->setDbName(‘test’);
$user ->setPassword($password);
$manager->persist($user);
$manager->flush();
How can I achieve adding the data to the users_roles table?
@thorin: I stumbled upon the same kind of question, but thinking about this matter made me wonder…
@Dustin: …why were you using a join table for the user role(s)? Why not a Many-to-One (users-to-roles). I think the ACL is what we need to give users a particular role in a particular context.
@thorin: if you read this tutorial correctly in fixture example you’ll see that Dustin add the created roles to the user using this command: $user->getUserRoles()->add($role);
The role itself is created before creating the user.
@Cecil: the reason of using ManyToMany is because each User can have multiple Roles assigned to it.
@Dustin: thanks for providing this tutorial!
voy a probarlo me parece interesante, quisiera saber si en symfony2 se puede lo siguiente:
subir un archivo pdf, y que el formulario que tengo en pantalla, se llene con los datos del archivo que subí.
se puede???
o si existe algo parecido a eso me gustaría saberlo
muchas gracias..
What?
public function equals(UserInterface $user)
{
return md5($this->getUsername()) == md5($user->getUsername());
}
Why not:
public function equals(UserInterface $user)
{
return $this->getUsername() == $user->getUsername();
}
Hi,
thanks for your tutorial! It helped me setting up the whole login.
But now I want to manage user roles via doctrine. I have a role entity. But one thing confuses my, it’s the role_hierarchy section in security.yml.
Do I have to replace it? How do I store the hierarchy in my database? I can’t find anything concerning this in the sf2 documentation.
I’d be glad if someone could help me
Kind regards from Germany
Simon
I had some problems following your article:
“$manager->persist($user);
// …”
The missing $manager->flush() should be mentioned.
Also the DoctrineFixtureBundle is missing in the default Installation. More Info:
http://h4cc.de/symfony-command-doctrinedataload-is-not-defined.html
It should be:
” providers:
main:
entity: { class: CompanyBlogBundle:User, property: username “