Symfony 4 / Sonata: Create a frontend authentication

To use a different table from the default one for authentication,

First we need to create our entity that will manage our users.

This entity must then be implemented to UserInterface.

So we need to add a reference to the component and implement it:

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Index;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\WcoconRepository")
 * @Table(name="wcocon")
 */
class Wcocon implements UserInterface
{

Now that the entity is an implement of UserInterface, we need to add the implementation methods.


    /*Authentication méthods implements interface User*/
    public function getRoles(){
        return ['ROLE_USER'];
    }
    

    public function getPassword(){
        return $this->getWcoPassword();
    }

    public function getSalt() {
        return false;
    }
    

    public function getUsername(){
        return $this->getWcoDossier();
    }
    

    public function eraseCredentials(){
        
    }
    

The default getRoles function should return an array of roles. Here 'ROLE_USER' but we could add more complex logic here.

Next, we need to create the controller that will handle our authentication.
Using CLI commands, just run: php bin/console make:controller
And create our SecurityController.

Sélection_038

In our base.html.twig (/templates/base.html.twig), we will create a login block, which will serve as a placeholder for our form

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.bundle.min.js" integrity="sha384-6khuMg9gaYr5AxOqhkVIODVIvm9ynTT5J4V1cfthmT+emCG6yVmEZsRHdxlotUnm" crossorigin="anonymous"></script>
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
    
        <div class="container">
          <div class="row">
            <div class="col-sm-3">
                {% block login %}{% endblock %}
            </div>
            <div class="col-sm-9"> 
                {% block body %}{% endblock %}
            </div>
        
          </div>
        </div>    
        {% block javascripts %}{% endblock %}
    </body>
</html>

And we will create our login form template in /var/www/cfc/delef/templates/security/login.html.twig.
This being an extension of our base html and for our login block:

{% extends 'base.html.twig' %}

{% block login %}
	<h1>Connexion !</h1>
	<form action="{{ path('security_login') }}" method="post">
    	<div class="form-group">
    		<input placeholder="N° de dosser" 	required name="_username" type="text" 		class="form-control">
    	<div class="form-group">
    	</div>
    		<input placeholder="password" 		required name="_password" type="password" 	class="form-control">
    	</div>
    	<div class="form-group">
    		<button type="submit" class="btn btn-success">Connexion</button>
    	</div>
	</form>
{% endblock %}

At this stage, we have the following rendering:

Sélection_040

Be careful, the login form must contain the fields _username and _password.

Then we need to configure the encoder, then the provider, and the firewall.
Note that the encoder for plain text is "plaintext". it would be better to rather use bcrypt or another sha.

security:
    encoders:
        App\Entity\Wcocon:
            algorithm: plaintext
    providers:
        frontend_users:
            entity:
              class: App\Entity\Wcocon
              property: wco_dossier
    firewalls:
        main:
            anonymous: true
            provider: frontend_users
            form_login:
                login_path: security_login
                check_path: security_login

The entire file looks like this:

security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    encoders:
        FOS\UserBundle\Model\UserInterface: bcrypt
        App\Entity\Wcocon:
            algorithm: plaintext
    providers:
        in_memory: { memory: null }
        fos_userbundle:
            id: fos_user.user_provider.username
        frontend_users:
            entity:
              class: App\Entity\Wcocon
              property: wco_dossier
        



    firewalls:
        # Disabling the security for the web debug toolbar, the profiler and Assetic.
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        # -> custom firewall for the admin area of the URL
        admin:
            pattern:            /admin(.*)
            context:            user
            form_login:
                provider:       fos_userbundle
                login_path:     /admin/login
                use_forward:    false
                check_path:     /admin/login_check
                failure_path:   null
            logout:
                path:           /admin/logout
                target:         /admin/login
            anonymous:          true

        # -> end custom configuration

        # default login area for standard users

        # This firewall is used to handle the public login area
        # This part is handled by the FOS User Bundle
        main:
            anonymous: true
            provider: frontend_users
            form_login:
                login_path: security_login
                check_path: security_login
            
    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        # Admin login page needs to be accessed without credential
        - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }

        # Secured part of the site
        # This config requires being logged for the whole site and having the admin role for the admin part.
        # Change these rules to adapt them to your needs
        - { path: ^/admin/, role: [ROLE_ADMIN, ROLE_SONATA_ADMIN, ROLE_SUPER_ADMIN] }
        - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }

We then need to configure our logout url.
For this, we must add a route in our controller, and register it in the configuration.

In the controller add the method:

    /**
     * @Route("/deconnexion", name="security_logout")
     */    
    public function logout(){}

And in the configuration:
Path refers to the route, and target allows you to give a redirect url once the authentication is reset.

        main:
            anonymous: true
            provider: frontend_users
            form_login:
                login_path: security_login
                check_path: security_login
            logout:
                path: security_logout
                target: /