Thursday, September 10, 2015

Symfony 2 Authentication and FOSUserBundle


It was easier than I expected, but there are some small pieces of information  missing in the documentation which can make you lose time on StackOverflow :P.


I have a fresh installation of Symfony Framework 2.8 DEV Standard Edition. I've added Bootstap to my template by directly downloading the files locally and including them in base template.

Go to:  http://getbootstrap.com/getting-started
Click Download Bootstrap to download latest compiled Bootstrap files.
Uncompress the downloaded .zip file. We have three folders:

    css
    js
    fonts

Put them all into the public folder of your app, should be called "web"
In the <head> of the main template (base.html.twig found under "..\symfony\app\Resources\views\base.twig.html")  the following should be added in order to use Bootstrap framework features:

        {% block stylesheets %}
            <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
            <!-- Include roboto.css to use the Roboto web font, material.css to include the theme and ripples.css to style the ripple effect -->
            <link href="{{ asset('/css/roboto.min.css') }}" rel="stylesheet">
            <link href="{{ asset('/css/material.min.css') }}" rel="stylesheet">
            <link href="{{ asset('/css/ripples.min.css') }}"  rel="stylesheet">
        {% endblock %}


Also before </body> it should be added a javascript Twig block:

        {% block javascripts %}
            <script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
            <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>

            <script src="{{ asset('/js/ripples.min.js') }}"></script>
            <script src="{{ asset('/js/material.min.js') }}"></script>
            <script>
                $(document).ready(function() {
                    // This command is used to initialize some elements and make them work properly
                    $.material.init();
                });
            </script>   
        {% endblock %}
    </body>



Inside the <body> tag I added a navigation bar ( I've seen this on this tutorial : http://laravel.com/docs/5.1/installation#basic-configuration):

<!-- Navigation bar at the top of all pages    -->
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <!-- Brand and toggle get grouped for better mobile display -->
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="/"> Symfony 2.8 website</a>
                  
                </div>

                <!-- Navbar Right -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav navbar-right">
                        <li class="active"><a href="/">Home</a></li>
                        <li><a href="/about">About</a></li>
                        <li><a href="/users">Users</a></li>
                        <li><a href="/contact">Contact</a></li>
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Member <span class="caret"></span></a>
                            <ul class="dropdown-menu" role="menu">
                                     <li><a href="/register">Register</a></li>
                                    <li><a href="/login">Login</a></li>
                            </ul>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>


---------------------------------------------------------------------------------------------------------------------------
-------------------------------------------   Getting Started With FOSUserBundle  ------------------
----------------------------------------------------------------------------------------------------------------------------
Documentation:
https://symfony.com/doc/master/bundles/FOSUserBundle/index.html

Followed the 7 steps described in documentation. You should do those in parallel with the below notes.

    Download FOSUserBundle using composer
    Enable the Bundle
    Create your User class
    Configure your application's security.yml
    Configure the FOSUserBundle
    Import FOSUserBundle routing
    Update your database schema

-------------------------------------------------------------------------------------------------------------------------
http://stackoverflow.com/questions/19677807/symfony-understanding-super-admin
-------------------------------------------------------------------------------------------------------------------------
Make sure your security.yml contains:

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: 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 }
        - { path: ^/user/, role: ROLE_USER }

       

-------------------------------------------------------------------------------------------------
http://symfony.com/doc/current/bundles/FOSUserBundle/command_line_tools.html

Create a super-admin user from command line:

php app/console fos:user:create admin admin@test.com 1234 --super-admin

Create a normal user:

php app/console fos:user:create regularuser regular@test.com 1234

Make an UserController which will have a path for /admin/{name}  and /user/{name}

    /**
     * @Route("/user/{name}")
     */
    public function indexAction($name)
    {
         return $this->render('AppBundle:User:userhome.html.twig',array('name' => $name));
    }
    /**
     * @Route("/admin/{name}")
     * @Template()
     */
    public function indexAdminAction($name)
    {
        return $this->render('AppBundle:User:adminhome.html.twig');
    }
   

And also you should add templates for each of them at "symfony\src\AppBundle\Resources\views\User\"
Now you can put in your browser http://<your_virtual_host>/user/YourName
You should be redirected to a login page. Login with regularuser and you will be redirected to home page
----------------------------------------------------------------------------------------------------------------------------
--------------------------------   Override templates  ------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------
Documentation:
https://symfony.com/doc/master/bundles/FOSUserBundle/overriding_templates.html

To override the layout template located at Resources/views/layout.html.twig in the FOSUserBundle directory,  you would place your new layout template at app/Resources/FOSUserBundle/views/layout.html.twig.

As you can see the pattern for overriding templates in this way is to create a folder with the name of the bundle class in the app/Resources directory. Then add your new template to this folder, preserving the directory structure from the original bundle.

----------------------------------------------------------------------------------------------------------------------------
------   Adding new fields to the User object/ Overriding Default FOSUserBundle Forms  -----------
----------------------------------------------------------------------------------------------------------------------------
https://symfony.com/doc/master/bundles/FOSUserBundle/overriding_forms.html
Following the instructions in the documentation I created the file \src\AppBundle\Entity\User.php
Make sure to add the below line in Entity\User.php

    use Symfony\Component\Validator\Constraints as Assert;

I added two new properties:

   /**
     * @ORM\Column(type="string", length=255)
     *
     * @Assert\NotBlank(message="Please enter your name.", groups={"Registration", "Profile"})
     * @Assert\Length(
     *     min=3,
     *     max=255,
     *     minMessage="The name is too short.",
     *     maxMessage="The name is too long.",
     *     groups={"Registration", "Profile"}
     * )
     */

    protected $name;
    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Assert\NotBlank(message="Please enter your last name.", groups={"Registration", "Profile"})
     * @Assert\Length(
     *     min=3,
     *     max=255,
     *     minMessage="The name is too short.",
     *     maxMessage="The name is too long.",
     *     groups={"Registration", "Profile"}
     * )
     */

    protected $last_name;


Use command line to automatically generate getters and setters for your new fields:

    php app/console doctrine:generate:entities AppBundle

   
After this update the DB using Doctrine command line:

    php app/console doctrine:schema:update --force

Make sure to add the new service in services.yml:

    services:
    #    service_name:
    #        class: AppBundle\Directory\ClassName
    #        arguments: ["@another_service_name", "plain_value", "%parameter_name%"]
        app.form.registration:
            class: AppBundle\Form\RegistrationType
            tags:
                - { name: form.type, alias: app_user_registration }


Now you should be able to register a new user
----------------------------------------------------------------------------------------------------------------------------
-----------------------------------------------  Handling the log out --------------------------------------------
----------------------------------------------------------------------------------------------------------------------------

Modify the base.html.twig template which contains buttons for login and register. Verify if the user is logged and if it is, display only Logout button:

                                {% if app.user %}
                                    # user is logged in
                                    <li><a href="/logout">Logout</a></li>
                                {% else %}
                                    # user is not logged in
                                    <li><a href="/register">Register</a></li>
                                    <li><a href="/login">Login</a></li>
                                {% endif %}

----------------------------------------------------------------------------------------------------------------------------
-------------------- Show hello "Name Last name" of the logged user ------------------------------------
----------------------------------------------------------------------------------------------------------------------------
After login the user is redirected to the home page. Let's modify the template in order to show
Hello "Name Last name"!

                    {% if app.user %}
                       
                        <h3 class="text-center margin-top-100 editContent">
                            Home page. Welcome {{ app.user.name }} {{ app.user.lastname }} !
                        </h3>
                    {% else %}
                       
                        <h3 class="text-center margin-top-100 editContent">Home page. Welcome! </h3>       
                    {% endif %}

"name" and "lastname" are two properties added by me to the AppBundle\Entity\User.php class. In fact I added "last_name" in User.php  but Doctrine made a setter and getter for "lastname": getLastName.

-----------------------------------------------------------------------------------------------------------------------------
------------------------------ FOSUserBundle form labels  --------------------------------------------------
-----------------------------------------------------------------------------------------------------------------------------
You could see some weird labels in the login and registation forms. You can make them all disappear by turning on Internationalization (translator service).
This is very simple, have a look on the official documentation: http://symfony.com/doc/current/best_practices/i18n.html

Uncomment the following translator configuration option and set your application locale

# app/config/config.yml
framework:
    # ...
    translator: { fallbacks: ["%locale%"] }

# app/config/parameters.yml
parameters:
    # ...
    locale:     en

After this you can check the Login page to see the results.
---------------------------------------------------------------------------------------------------------------------
How to overwrite fos user bundle form labels?

You can read the answer on StackOverflow: http://stackoverflow.com/questions/13473329/how-to-overwrite-fos-user-bundle-form-labels  or here:   "Copy/paste vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/translations/FOSUserBundle.xx.yml files in your  app/Resources/FOSUserBundle/translations directory (with the same directory structure and the same file  name) and redefine translations to your convenience."

No comments:

Post a Comment