Mini série: création d'un espace membre sur Symfony 4 (1/5)

publié le 02/02/2018

symfony 4 authentification

Symfony, nouvelle version, 4ème volet, avec son lot de bonnes et moins bonnes surprises. Je passe sur les bonnes, qui ne sont pas l'objet de cette mini-série. La moins bonne concerne FOSUserBundle, qui semble pour le moment ne pas supporter cette nouvelle version. C'est bien dommage. Ce bundle m'avait jusqu'à maintenant rendu de fiers services. On ne se laisse pas abattre pour autant. En s'appuyant sur la documentation Symfony, on devrait pouvoir mettre sur pied un espace membre robuste et sécurisé. Let's code it!


Il s'agit de développer un espace membre. Je ne prétends pas à l'exhaustivité. Cette mini-série est clairement une application pratique d'un projet en cours. J'ai simplement copié le code au fur et à mesure que je développais cet espace. J'espère n'avoir rien omis. Quoi qu'il en soit, si vous repérez des erreurs, merci de m'adresser un message afin que je puisse améliorer l'article. Le plan de cette série:

  • système d'authentification (partie 1)
  • entité User et User provider (partie 2)
  • formulaire d'inscription (partie 3)
  • récupération du mot de passe en cas d'oubli (partie 4)
  • création d'une commande pour créer un utilisateur à la console (partie 5)

Partie 1: création d'un système d'authentification

Commençons par installer les composants dont nous aurons besoin tout au long de cette série. En ligne de commande:

composer require security twig doctrine maker validator form annotations profiler security-csrf

Configuration de security.yaml

# config/packages/security.yaml
security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        in_memory: { memory: ~ }
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: ~

            # https://symfony.com/doc/current/security/form_login_setup.html
            form_login:
                login_path: connexion
                check_path: connexion

Le controlleur

bin/console make:controller

puis saisissez SecurityController. Ce controlleur contiendra toute la logique d'authentification.

<?php

namespace App\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\HttpFoundation\Request;

class SecurityController extends Controller
{
    /**
     * @Route("/connexion", name="connexion")
     */
    public function login(Request $request, AuthenticationUtils $authUtils)
    {
        // get the login error if there is one
        $error = $authUtils->getLastAuthenticationError();

        // last username entered by the user
        $lastUsername = $authUtils->getLastUsername();

        return $this->render('security/login.html.twig', array(
            'last_username' => $lastUsername,
            'error'         => $error,
        ));
    }
}

Le template

{# templates/security/login.html.twig #}
{# ... you will probably extend your base template, like base.html.twig #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>

{% if error %}
    <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}

<form action="{{ path('connexion') }}" method="post">
    <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="_username" value="{{ last_username }}" />
    </div>

    <br>

    <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="_password" />
    </div>

    <br>
    {#
    If you want to control the URL the user
    is redirected to on success (more details below)
    <input type="hidden" name="_target_path" value="/account" />
    #}

    <button type="submit">login</button>
</form>

</body>
</html>

La protection CSRF

Cette protection est indispensable pour améliorer la sécurité des formulaires. On va le configurer dès maintenant.

#config/packages/framework.yaml
framework:
    csrf_protection: ~
# config/packages/security.yaml
security:
    # ...
    firewalls:
        main:
            form_login:
                # ...
                csrf_token_generator: security.csrf.token_manager

Enfin, modifiez le template pour activer la protection CSRF sur ce formulaire

{# templates/security/login.html.twig #}

<form action="{{ path('connexion') }}" method="post">

    {# … #}

    <input type="hidden" name="_csrf_token"
        value="{{ csrf_token('authenticate') }}"
    >

    <button type="submit">login</button>
</form>

Déconnecter un utilisateur

# config/packages/security.yaml
security:
        main:
            # ...
            logout:
                path: /deconnexion
                target: /
# config/routes.yaml
logout:
    path: /deconnexion

Test de la fonctionnalité

A ce stade des utilisateurs devraient pouvoir se logger. On ne dispose pas encore d'une entité mais on peut tout de même charger des membres, en utilisant le memory provider de Symfony. Modifiez votre security.yaml:

security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext
    providers:
        in_memory:
            memory:
                users:
                    test_user:
                        password: test_password
                        roles: 'ROLE_USER'

    # ...

Vous pouvez désormais tester votre formulaire de connexion en rentrant test_user en identifiant et test_password en mot de passe. Si votre PC ne vous braille pas à la figure en arborant un gros message d'erreur, et qu'en outre la barre du Profiler vous certifie que vous êtes bel et bien connectés, vous pouvez crier victoire. Vous avez vaincu le 1er boss de l'espace membre !

Dans le cas contraire, je vous renvois à la documentation Symfony qui est formidable. Aussi j'ai créé un dépôt sur Github, au cas où vous seriez bloqué. Voici les ressources de cet article: