Basic User Management in Symfony2 with FOSUserBundle
Symfony has a very powerful authentication and authorization system, which can cater to a lot of our needs out of the box. FOSUserBundle is a library built on top of Symfony’s security system, which provides functionality such as registration, login, password resets, etc. It has built in support for MongoDB, as well as ORMs such as Doctrine, Propel, etc.
We will use Homestead Improved for our demo.
Let’s add a new site in the Homestead.yml
file.
sites:
- map: symfonylogin.app
to: /home/vagrant/Code/SymfonyLogin/web
type: symfony
databases:
- symfony
This will set up a new site in the VM that points to the web
folder of the Symfony application. When type: symfony
is specified, it will pick an Nginx configuration that is specifically created for Symfony applications.
We should also add 192.168.10.10 symfonylogin.app
into the main operating system’s /etc/hosts
file. Now we can boot the VM with vagrant up
.
Before we begin, let’s quickly set up the Symfony application using Symfony’s installer, a command line tool for installing Symfony projects. To install the installer, we enter the VM using vagrant ssh
and run the following commands.
curl -LsS http://symfony.com/installer > symfony
sudo mv symfony /usr/local/bin/symfony
chmod a+x /usr/local/bin/symfony
The first command downloads the installer from the Symfony website to the current directory. Then, we move it to the bin directory and make it executable.
Now let’s create a Symfony application inside the VM.
cd Code
symfony new SymfonyLogin
This will create a Symfony skeleton application and install all the dependencies from Packagist. Once it’s finished, we can go to the app/config
folder and update parameters.yml
with our database and email configuration values.
We can now open http://symfonylogin.app
in the browser to see the skeleton application running.
Symfony restricts the development environment’s access to the IP 127.0.0.1
. When using Vagrant, you might need to update the app_dev.php
file to add your host IP to the list.
Basic Usage
FOSUserBundle can be easily plugged into Symfony projects with minimal configuration and code changes. Let’s see how we can do that in ours.
Installing the bundle
First, we will install the package:
composer require friendsofsymfony/user-bundle "~2.0@dev"
Configuration
Before we can use FOSUserBundle, we should register the bundle in AppKernel.
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
...
new FOS\UserBundle\FOSUserBundle(),
);
...
return $bundles;
}
FOSUserBundle uses the Symfony translation component for displaying form labels and errors. By default, it is disabled in the configuration, and we need to make the following modification in config.yml
.
translator: { fallbacks: ["%locale%"] }
Now let us configure the security in security.yml
.
security:
encoders:
AppBundle\Entity\User: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
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 }
- { path: ^/admin/, role: ROLE_ADMIN }
Symfony’s security component provides a number of configuration options which will let us configure pretty much every aspect. Here we have specified the only options that we need to change and left everything else at the default value. Let’s see what we have changed.
encoders:
AppBundle\Entity\User: bcrypt
encoders
will tell the application which algorithm to use to encode passwords for the User object. This is our User entity class which we will create in the steps below.
providers:
fos_userbundle:
id: fos_user.user_provider.username
This will register the user provider where fos_user.user_provider.username
is the ID of the service FOS\UserBundle\Security\UserProvider
registered in FOS User Bundle.
firewalls
in Symfony is analogous to the firewalls that we are familiar with. We have created one firewall named main
. The pattern
key specifies the url patterns to which this firewall should match. The key form_login
specifies that we will be using a login form to authenticate users and we want to use our fos_userbundle
as the user provider.
access_control
limits access to URLs based on user roles. Login, Registration and Resetting pages should be accessible publicly, so they require the IS_AUTHENTICATED_ANONYMOUSLY
role, which is given by default to anyone accessing any page.
The User class
FOSUserBundle requires us to create a User entity that needs to be persisted into the database. The bundle already provides an abstract base class with most of the fields that we need. We just need to create the entity that extends this base class.
<?php
// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* @ORM\Table("fos_user")
* @ORM\Entity
*/
class User extends BaseUser
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
}
The base class doesn’t have an ID field, so we should have that in our Entity along with any other fields that we need for our application.
After creating the Entity, we need to tell the application to use this as the user class. There are three fields required by FOSUserBundle that we should specify in config.yml
. We should add the following settings to the end of config.yml
.
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
db_driver : orm
will tell the bundle to use Doctrine.
firewall_name : main
specifies the name of the firewall to use.
user_class: AppBundle\Entity\User
tells the application that we want to use AppBundle\Entity\User
as the user class.
Update Schema
Once we create the Entity, we can also generate the user table using Symfony’s CLI.
php app/console doctrine:schema:update --force
This will create a table named fos_user in our database, with the fields available from FOSUserBundle. If you need any additional fields, you can specify them in the User entity before you generate the schema.
Import routes
Symfony doesn’t import vendor routes by itself. Instead, we need to manually add them in app/config/routing.yml
.
fos_user:
resource: "@FOSUserBundle/Resources/config/routing/all.xml"
This will import all the routes available in FOSUserBundle
.
Now that we have set up the bundle and configured everything, we can check if it is working. If we go to http://symfonylogin.app/register/
, we should see a registration form like this one.
Customizing Templates
We now have a working but basic user registration and login system. The form looks too naive for use in any real projects. Next, we will see how we can customize the templates to look better.
Symfony’s powerful templating system allows us to easily extend and override templates that come with bundles. In order to override the bundle templates, we must create a directory under app/Resources
and give it the name of the bundle. Then, we create a views
directory and place any templates that do overriding in there.
If we check the location vendor/friendsofsymfony/user-bundle/Resources/views
, we will find all the templates used by FOSUserBundle. We need to create the directory structure FOSUserBundle/views/
under app/Resources/
and place our template files there. Let’s copy the Registration
directory from FOSUserBundle
to this location. Now, our app/Resources
directory should look something like this.
// ProjectRoot/app/Resources
├── FOSUserBundle
│ └── views
│ └── Registration
│ ├── checkEmail.html.twig
│ ├── confirmed.html.twig
│ ├── email.txt.twig
│ ├── register.html.twig
│ └── register_content.html.twig
└── views
├── base.html.twig
└── default
└── index.html.twig
Now we can update register.html.twig
based on our requirements.
{% extends "base.html.twig" %}
{% block body %}
<div class="row">
<div class="col s12 m8 offset-m2">
<h4 class="card-panel teal lighten-2 white-head">Signup</h4>
</div>
</div>
<div class="row">
<div class="col s12 m8 offset-m2">
{% include "FOSUserBundle:Registration:register_content.html.twig" %}
</div>
</div>
{% endblock body %}
Here I assume you already have a layout file base.html.twig for your application, with body content rendered into the body
block.
We will also change register_content.html.twig
so that we can display the form elements as we wish.
{% trans_default_domain 'FOSUserBundle' %}
{{form_start(form, {'method': 'POST', 'attr':{'class': 'register'}})}}
<div class="row">
<div class="input-field col s12">
{{ form_label(form.username) }}
{{ form_widget(form.username) }}
</div>
{{ form_errors(form.username) }}
</div>
<div class="row no-padding">
<div class="input-field col s12">
{{ form_label(form.email) }}
{{ form_widget(form.email) }}
</div>
{{ form_errors(form.email) }}
</div>
<div class="row no-padding">
<div class="input-field col s12">
{{ form_label(form.plainPassword.first) }}
{{ form_widget(form.plainPassword.first) }}
</div>
{{ form_errors(form.plainPassword.first) }}
</div>
<div class="row no-padding">
<div class="input-field col s12">
{{ form_label(form.plainPassword.second) }}
{{ form_widget(form.plainPassword.second) }}
</div>
{{ form_errors(form.plainPassword.second) }}
</div>
<div align="center" class="button-wrap">
<button class="btn-large waves-effect waves-light" type="submit" name="action">
{{ 'registration.submit'|trans }}
<i class="material-icons">send</i>
</button>
</div>
{{ form_end(form) }}
form_start
will open a form tag with the given attributes and form_end
will close that form. If we check the actual template, we will see {{ form_widget(form) }}
only, which will render the entire form. But since we need our own styles for form elements, we are rendering individual form elements using form_label
and form_widget
. If any element fails the validation when the form is submitted, form_errors
will render the error message for the corresponding field.
We can customize all the other templates to match rest of the application.
Likewise, if we want to customize the labels and other messages, we can copy the translations
directory from FOSUserBundle and place it in our app/Resources/FOSUserBundle
directory. Then, we edit FOSUserBundle.en.yml
and change any values we need.
Registration Confirmation
In most cases, we need the user to confirm their email address before completing the registration process. This can be easily done by updating the fos_user
configuration in config.yml
. Now it should look something like this.
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
from_email:
address: admin@example.com
sender_name: Example.com
registration:
confirmation:
enabled: true
template: FOSUserBundle:Registration:email.txt.twig
Notice the new registration
key we have added here. enabled: true
for confirmation
will enable the confirmation process and we are using FOSUserBundle:Registration:email.txt.twig
template for the email content.
Also note the from_email
key that we have added. It will tell the bundle to use this email as the from address for all emails we are sending.
Now, users trying to register will get a confirmation email with a link to confirm their email address.
For sending emails from Symfony, make sure you have a working smtp configuration in the parameters.yml
file.
Login
So far, we have seen how to set up the registration form and customize the templates. Logging in doesn’t need any additional setup, the built in functionality is enough for any application. If we navigate to http://symfonylogin.app/login
, we will see the login form. If we are already logged in, we will also see a logout link in the default template.
Symfony’s security system intercepts the login form submission and tries to authenticate the user using the authentication provider that we have specified in the security configuration. Most of this is done by Symfony’s security component itself, documented here.
Forgot Password
FOSUserBundle also provides the ability to reset a password. Users can navigate to http://symfonylogin.app/resetting/request
and provide their username or email to reset their password. If that user exists, the application will send an email to the registered email address.
User Profile
FOSUserBundle comes with a basic profile page. Once logged in, if we navigate to http://symfonylogin.app/profile/
, we will see the default profile page. Of course, this page also needs to be customized for use in any non-default projects.
{% trans_default_domain 'FOSUserBundle' %}
{% extends "base.html.twig" %}
{% block body %}
<div class="fos_user_user_show">
<p>{{ 'profile.show.username'|trans }}: {{ user.username }}</p>
<p>{{ 'profile.show.email'|trans }}: {{ user.email }}</p>
</div>
<a href="{{ path('fos_user_profile_edit') }}">Edit Profile</a>
{% endblock body %}
The user
object holds the details of the currently logged in user. Right now, we have only the username and email available in the database.
We can also update the profile from the profile edit page (http://symfonylogin.app/profile/edit
). For added security, the user should provide the current password to update the profile.
Summary
FOSUserBundle is a plug-in for user registration and management in Symfony applications. Built on top of Symfony’s security system, this bundle takes care of most if not all the complexity of Symfony’s security when implementing user registration and login.
For more information, check out the extensive documentation to learn more about this Bundle.
The code for this tutorial is available on Github. Let us know if you have any questions or comments!