· 3 min read

Une authentification MAGIQUE dans Symfony !

Aujourd'hui nous allons voir comment mettre en place une authentification magique dans Symfony ! Notre objectif sera de créer un lien qui permettra à un utilisateur de se connecter sans avoir à saisir son mot de passe.

Aujourd'hui nous allons voir comment mettre en place une authentification magique dans Symfony ! Notre objectif sera de créer un lien qui permettra à un utilisateur de se connecter sans avoir à saisir son mot de passe.

Création d’un projet

symfony new magicLink --full
cd magickLink
symfony console make:docker:database
symfony console make:user
docker-compose up -d
symfony serve -d
symfony console make:migration
symfony console d:m:m

Mise en place d’un HomeController et protection

 symfony console make:controller Home

On le modifie un tout petit peu.

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    /**
     * @Route("/home", name="home")
     */
    public function home(): Response
    {
        return $this->render('home/index.html.twig', [
            'controller_name' => 'HomeController',
        ]);
    }

    /**
     * @Route("/", name="index")
     */
    public function index(): Response
    {
        return $this->redirectToRoute('home');
    }
}

Modification du security.yaml.

    access_control:
        # - { path: ^/admin, roles: ROLE_ADMIN }
         - { path: ^/home, roles: ROLE_USER }

Et test de l’url /home

Création d’un formulaire d’enregistrement

#sans mail ni connexion auto
symfony console make:registration-form

Et stylisons le formulaire (histoire de voir des trucs)

twig:
    default_path: '%kernel.project_dir%/templates'
    form_themes: ['bootstrap_5_layout.html.twig']


when@test:
    twig:
        strict_variables: true

Mettons en place un Formulaire de Login “Classique”

symfony console make:auth

Et modifions le AppAutheticator

<?php

public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
    if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
        return new RedirectResponse($targetPath);
    }
    // For example:
    return new RedirectResponse($this->urlGenerator->generate('home'));
}
protected function getLoginUrl(Request $request): string
{
    return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}

Création du firewall

security:
    # https://symfony.com/doc/current/security/experimental_authenticators.html
    enable_authenticator_manager: true
    # https://symfony.com/doc/current/security.html#c-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
        App\Entity\User:
            algorithm: auto

    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            lazy: true
            provider: app_user_provider
            custom_authenticator: App\Security\AppAuthenticator
            logout:
                path: app_logout
                # where to redirect after logout
                # target: app_any_route
            login_link:
                check_route: login_check
                signature_properties: ['id']

            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        # - { path: ^/admin, roles: ROLE_ADMIN }
         - { path: ^/, roles: ROLE_USER }

Création de la route

#route.yaml
login_check:
    path: /login_check
<?php

    /**
     * @Route("/magic", name="app_magic")
     */
    public function magic(UserRepository $userRepository, LoginLinkHandlerInterface $loginLinkHandler, MailerInterface $mailer): Response
    {
        $users = $userRepository->findAll();

        foreach ($users as $user) {
            $loginLinkDetails = $loginLinkHandler->createLoginLink($user);
            $email = (new Email())
                ->from('bot@test.com')
                ->to($user->getEmail())
                ->subject('Magic login link')
                ->text('You can use this link to login: ' . $loginLinkDetails->getUrl());

            $mailer->send($email);
        }

        return new Response('Magic!');

    }

Pour que cela fonctionne, nous devons ajouter un Mailer. Utilisons Docker pour cela.

version: '3.7'
services:
    database:
        image: 'mysql:latest'
        environment:
            MYSQL_ROOT_PASSWORD: password
            MYSQL_DATABASE: main
        ports:
            # To allow the host machine to access the ports below, modify the lines below.
            # For example, to allow the host to connect to port 3306 on the container, you would change
            # "3306" to "3306:3306". Where the first port is exposed to the host and the second is the container port.
            # See https://docs.docker.com/compose/compose-file/#ports for more information.
            - '3306'
    mailer:
        image: schickling/mailcatcher
        ports: [1025, 1080]

Et demarrons le docker-compose.yaml

docker-compose up -d

Quelques options

        login_link:
            check_route: login_check
            signature_properties: ['id']
            max_uses: 1
            lifetime: 300
Back to Blog