Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Voir cette série
Mon CMS - L'authentification
Vendredi 23 août 2024 15:15

Dans cette partie de notre développement du CMS, nous allons nous concentrer sur la mise en place du frontend en commençant par l'intégration des formulaires d'authentification. Bien que Laravel propose des kits d'authentification prêts à l'emploi tels que Breeze et Jetstream, j'ai choisi d'adopter une approche sur mesure. Cette décision nous permettra de créer un système d'authentification parfaitement adapté à nos besoins spécifiques, tout en offrant une flexibilité maximale.

Pour concevoir des formulaires d'authentification à la fois élégants et fonctionnels, nous allons tirer parti des puissantes capacités de MaryUI et Volt. Ces outils nous permettront de créer une interface utilisateur cohérente et intuitive, parfaitement intégrée à l'ensemble de notre application. Il y a plusieurs avantages de l'approche personnalisée :

  • Contrôle total sur le processus d'authentification
  • Possibilité d'ajouter des fonctionnalités spécifiques à notre CMS
  • Meilleure compréhension des mécanismes sous-jacents de Laravel

Cette approche personnalisée avec Laravel, MaryUI et Volt vous offrira une compréhension des concepts fondamentaux de la gestion des utilisateurs et des sessions dans Laravel. À l'issue de ce chapitre, notre CMS disposera d'un système d'authentification robuste, sécurisé et parfaitement adapté à nos besoins.

Comme pour mon article précédent, les parties en bloc de citation concernent principalement les débutants.

Installation de MaryUI avec Livewire et Volt

Nous allons commencer par installer MaryUI :

composer require robsontenorio/mary
php artisan mary:install

Là, vous allez tomber sur deux questions :

Also install `livewire/volt` ?

Vous répondez oui, donc la valeur 0.

La seconde question concerne l'outil à utiliser, choisissez npm. Il va s'installer de nombreuses dépendances, et en particulier DaisyUI et Tailwind. Il ne reste plus qu'à générer avec Vite! :

npm run dev

Votre page d'accueil devrait maintenant avoir cet aspect :
Vous avez à présent quelque chose de fonctionnel qui vous permet de comprendre comme fonction MaryUI, il y a un tuto complet ici. Libre à vous d'explorer tout ça, mais on va l'ignorer pour la suite de notre projet.

Avec Volt, on utilise des composants et avec l'installation initiale de maryUI on a eu la création automatique d'un composant :

Si vous allez voir le code, vous trouverez deux parties :

  • une classe PHP pour la gestion
  • une partie HTML pour la vue

C'est le principe de base de Volt qui concentre dans le même fichier ce qui est habituellement séparé avec Livewire.

En ce qui concerne le layout, il se trouve par défaut ici :

On dispose par défaut d'une page avec une barre de navigation rétractable sur la gauche. On va devoir compléter ça pour notre CMS.

Au niveau de la route qui a été créée pour afficher le composant, ça se passe évidemment dans route/web.php :

use Livewire\Volt\Volt;

Volt::route('/', 'users.index');

On utilise la façade Volt pour créer les routes qui pointent sur un composant de Volt. Remarquez la syntaxe avec le point pour désigner l'emplacement du composant.

Qu'est-ce que Volt ?

Volt est une extension de Livewire qui permet de créer des composants réutilisables et encapsulés. Avec Volt, vous pouvez définir des composants Livewire directement dans vos fichiers Blade, ce qui rend le code encore plus simple et plus lisible.

Comment ça marche ?

    Composants Volt : Vous définissez des composants Volt directement dans vos fichiers Blade en utilisant une syntaxe spéciale.
    Encapsulation : Chaque composant Volt encapsule sa propre logique et son propre template, ce qui rend le code plus modulaire et plus facile à maintenir.
    Interactions dynamiques : Comme avec Livewire, les interactions utilisateur sont gérées par des requêtes au serveur, et la vue est mise à jour sans recharger la page.

Avantages de Volt

    Réutilisabilité : Les composants Volt peuvent être réutilisés dans différentes parties de votre application, ce qui réduit la duplication de code.
    Lisibilité : En définissant les composants directement dans les fichiers Blade, le code devient plus lisible et plus facile à comprendre.
    Encapsulation : Chaque composant Volt est encapsulé, ce qui facilite la gestion et la maintenance du code.

Pour notre CMS on va utiliser plusieurs layouts selon le contexte. En particulier, on va en créer un pour l'authentification :

Avec ce simple code :

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ isset($title) ? $title . ' - ' . config('app.name') : config('app.name') }}</title>

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>

<body class="min-h-screen font-sans antialiased bg-base-200/50 dark:bg-base-200">

    <x-main full-width>
        <x-slot:content>
            {{ $slot }}
        </x-slot:content>
    </x-main>

    <x-toast />

</body>

</html>

Notre intendance est en place, on peut avancer.

Créer un compte

La première des choses à prévoir est évidemment un formulaire pour s'enregistrer sur le site. On va donc créer un composant Volt à cet effet :

php artisan make:volt auth/register --class

Entrez ce code :

<?php

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\{Layout, Validate, Title};
use Livewire\Volt\Component;
use App\Notifications\UserRegistered;
use Mary\Traits\Toast;

new #[Title('Register')] #[Layout('components.layouts.auth')] 
class extends Component {

    use Toast;

    #[Validate('required|string|max:255|unique:users')]
    public string $name = '';

    #[Validate('required|email|unique:users')]
    public string $email = '';

    #[Validate('required|confirmed')]
    public string $password = '';

    #[Validate('required')]
    public string $password_confirmation = '';

    #[Validate('sometimes|nullable')]
    public ?string $gender = null;

    public function register()
    {
        if ($this->gender) {
            abort(403);
        }

        $data = $this->validate();

        $user = $this->createUser($data);

        auth()->login($user);

        request()->session()->regenerate();

        $this->success(__('Registration successful!'), redirectTo: '/');
    }

    protected function createUser(array $data): User
    {
        $data['password'] = Hash::make($data['password']);
        return User::create($data);
    }

}; ?>

<div>
    <x-card class="flex items-center justify-center h-screen" title="{{ __('Register') }}" shadow separator
        progress-indicator>

        <x-form wire:submit="register" class="w-full sm:min-w-[30vw]">
            <x-input label="{{ __('Name') }}" wire:model="name" icon="o-user" inline required />
            <x-input label="{{ __('E-mail') }}" wire:model="email" icon="o-envelope" inline required />
            <x-input label="{{ __('Password') }}" wire:model="password" type="password" icon="o-key" inline required />
            <x-input label="{{ __('Confirm Password') }}" wire:model="password_confirmation" type="password"
                icon="o-key" inline required />
            <div style="display: none;">
                <x-input wire:model="gender" type="text" inline />
            </div>
            <x-slot:actions>
                <x-button label="{{ __('Already registered?') }}" class="btn-ghost" link="/login" />
                <x-button label="{{ __('Register') }}" type="submit" icon="o-paper-airplane" class="btn-primary"
                    spinner="login" />
            </x-slot:actions>
        </x-form>

    </x-card>
</div>

Explication des Parties du Composant Volt

    Classe PHP :
        Propriétés publiques : Les propriétés publiques sont accessibles dans le template Blade. Par exemple, $name est une propriété publique.
        Méthodes publiques : Les méthodes publiques peuvent être appelées depuis le template Blade. Par exemple, register est une méthode publique.

    Template Blade :
        Affichage des propriétés : Vous pouvez afficher les propriétés publiques de la classe en utilisant la syntaxe Blade ({{ $mavariable }}).
        Gestion des événements : Vous pouvez lier des événements (comme des clics de bouton) à des méthodes de la classe en utilisant la directive wire:click. Ici on utilise la directive wire:submit pour la soumission du formulaire.
        Liaison de données : Vous pouvez lier des données avec wire:model, dans le formulaire on a par exemple wire:model="name".

Il ne reste plus qu'à ajouter la route (routes/web) :

use Illuminate\Support\Facades\Route;

Route::middleware('guest')->group(function () {
	Volt::route('/register', 'auth.register');
});

Dans un framework web comme Laravel, un middleware est une sorte de filtre qui peut être appliqué à des routes ou à des groupes de routes. Il permet de vérifier certaines conditions avant de laisser une requête atteindre sa destination finale (comme une page web ou une API).

Route::middleware('guest')

Cette partie du code dit à Laravel d'appliquer un middleware appelé guest à toutes les routes qui suivent. Le middleware guest est généralement utilisé pour vérifier si l'utilisateur n'est pas authentifié (c'est-à-dire qu'il n'est pas connecté). Si l'utilisateur est déjà connecté, le middleware redirigera probablement l'utilisateur vers une autre page (comme le tableau de bord).

->group(function () { ... })

Cette partie du code crée un groupe de routes. Toutes les routes définies à l'intérieur de ce groupe seront soumises au middleware guest.

Volt::route('/register', 'auth.register');

À l'intérieur du groupe, il y a une route définie avec Volt::route. Cette route est /register et elle est associée à un composant Volt auth.register. Cela signifie que lorsque quelqu'un visite l'URL /register, la logique définie dans auth.register sera exécutée.

En résumé, ce code fait en sorte que la route /register soit accessible uniquement par les utilisateurs non authentifiés (les invités). Si un utilisateur déjà connecté essaie d'accéder à cette route, il sera redirigé ailleurs (probablement une page d'accueil).

Pourquoi Utiliser ce Middleware ?

Ce middleware est utile pour des pages comme l'inscription ou la connexion, où vous ne voulez pas que les utilisateurs déjà connectés puissent accéder. Par exemple, si un utilisateur est déjà connecté, il n'a pas besoin de voir la page d'inscription ou de connexion.

Si tout se passe bien avec l'url /register, vous devez obtenir le formulaire suivant :

Vous pouvez vérifier que tout fonctionne, en particulier la validation.

On va quand même ajouter des traductions pour avoir tout en français dans le fichier lang/fr.json :

"Password": "Mot de passe",
"Confirm Password": "Confirmation du mot de passe",
"Register": "Créer un compte",
"Already registered?": "Déjà enregistré ?",
"Name": "Nom",
"E-mail": "Courriel"

Et voilà le résultat :

Les attributs de Livewire

1. #[Title('Register')]

Cet attribut est utilisé pour définir le titre de la page. Lorsque vous utilisez #[Title('Register')], le titre de la page HTML sera automatiquement défini sur "Register". Cela peut être utile pour améliorer le référencement (SEO) et pour fournir des informations claires aux utilisateurs sur la page qu'ils visitent.

2. #[Layout('components.layouts.auth')]

Cet attribut est utilisé pour définir le layout (mise en page) du composant. Lorsque vous utilisez #[Layout('components.layouts.auth')], le composant utilisera le layout spécifié (dans ce cas, components.layouts.auth), au lieu d'utiliser le layout par défaut. Cela permet de réutiliser des layouts communs pour différentes pages, ce qui facilite la gestion de la mise en page de votre application.

3. #[Validate('required|string|max:255|unique:users')]

Cet attribut est utilisé pour définir des règles de validation pour une propriété. #[Validate('required|string|max:255|unique:users')] signifie que la propriété doit être une chaîne de caractères, ne doit pas être vide, ne doit pas dépasser 255 caractères, et doit être unique dans la table users.

4. #[Validate('required|email|unique:users')]

La propriété doit être une adresse e-mail valide, ne doit pas être vide, et doit être unique dans la table users.

5. #[Validate('required|confirmed')]

La propriété doit être confirmée par une autre propriété (généralement utilisée pour les mots de passe).

6. #[Validate('required')]

La propriété ne doit pas être vide.

7. #[Validate('sometimes|nullable')]

La propriété peut parfois être présente et peut être nulle.

Le pot de miel

Si vous êtes observateur, vous avez remarqué la présence insolite d'une propriété gender qui semble inutile. C'est un moyen appelé "pot de miel" pour tromper les robots.

Les robots automatisés (spambots) remplissent souvent tous les champs de formulaire qu'ils trouvent, sans se soucier de leur visibilité. En ajoutant un champ de saisie masqué (le pot de miel), vous pouvez détecter si un robot a rempli le formulaire. Si le champ gender contient une valeur lors de la soumission du formulaire, cela signifie probablement que le formulaire a été rempli par un robot, car un utilisateur humain ne verrait pas ce champ et ne le remplirait pas.

En utilisant cette technique, vous pouvez ajouter une couche de sécurité supplémentaire à vos formulaires. Si le champ gender est rempli, vous pouvez rejeter la soumission du formulaire et empêcher les spambots de soumettre des données indésirables. On le fait dans le composant avec ce code :

public function register()
{
    if ($this->gender) {
        abort(403);
    }

La connexion

Pour la connexion, on va créer un autre composant :

php artisan make:volt auth/login --class

Entrez ce code :

<?php

use Livewire\Attributes\{Layout, Validate, Title};
use Livewire\Volt\Component;

new #[Title('Login')] #[Layout('components.layouts.auth')] 
class extends Component {
    
    #[Validate('required|email')]
    public string $email = '';

    #[Validate('required')]
    public string $password = '';

    public function login()
    {
        $credentials = $this->validate();

        if (auth()->attempt($credentials)) {
            request()->session()->regenerate();

            return redirect()->intended('/');
        }

        $this->addError('email', __('The provided credentials do not match our records.'));
    }
}; ?>

<div>
    <x-card class="flex items-center justify-center h-screen" title="{{ __('Login') }}" shadow separator
        progress-indicator>
        <x-form wire:submit="login">
            <x-input label="{{ __('E-mail') }}" wire:model="email" icon="o-envelope" type="email" inline />
            <x-input label="{{ __('Password') }}" wire:model="password" type="password" icon="o-key" type="password"
                inline />
            <x-checkbox label="{{ __('Remember me') }}" wire:model="remember" />
            <x-slot:actions>
                <div class="flex flex-col space-y-2 flex-end sm:flex-row sm:space-y-0 sm:space-x-2">
                    <x-button label="{{ __('Login') }}" type="submit" icon="o-paper-airplane"
                        class="ml-2 btn-primary sm:order-1" />
                    <div class="flex flex-col space-y-2 flex-end sm:flex-row sm:space-y-0 sm:space-x-2">
                        <x-button label="{{ __('Forgot your password?') }}" class="btn-ghost" link="/forgot-password" />
                        <x-button label="{{ __('Create an account') }}" class="btn-ghost" link="/register" />
                    </div>
                </div>
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

On ajoute la route :

Route::middleware('guest')->group(function () {
	...
    Volt::route('/login', 'auth.login')->name('login');
});

On ajoute quelques traductions :

"The provided credentials do not match our records.": "Ces identifiants ne correspondent pas à nos enregistrements.",
"Forgot your password?": "Mot de passe oublié ?",
"Remember me": "Se rappeler de moi"

Et vous obtenez un joli formulaire :

On retrouve les mêmes principes et la même organisation pour le code, alors je n'insiste pas.

L'oubli du mot de passe

La mise en place d'un système de réinitialisation de mot de passe est un élément essentiel à prendre en considération lors de la conception d'un site web ou d'une application en ligne.

Cette fonctionnalité est primordiale pour diverses raisons :

Expérience utilisateur positive : Les utilisateurs peuvent oublier leurs mots de passe pour diverses raisons (absence prolongée, informations sensibles oubliées, etc.). En offrant une fonctionnalité de réinitialisation de mot de passe, vous améliorez l'expérience utilisateur en facilitant leur accès au site ou à l'application.

Sécurité : Les utilisateurs ont tendance à réutiliser les mêmes mots de passe ou à les noter, ce qui peut engendrer des risques de sécurité. En offrant une fonctionnalité de réinitialisation de mot de passe, vous encouragez les utilisateurs à changer régulièrement leurs mots de passe, ce qui renforce la sécurité de leurs comptes.

Conformité aux standards de l'industrie : De nos jours, la présence d'une fonctionnalité de réinitialisation de mot de passe est considérée comme un standard de l'industrie dans le développement de sites web et d'applications. En l'intégrant à votre plateforme, vous satisfaites aux attentes des utilisateurs et montrez votre professionnalisme.

Réduction des demandes de support : La présence d'une fonctionnalité de réinitialisation de mot de passe automatisée réduit significativement le nombre de demandes de support liées à l'oubli de mot de passe, ce qui vous permet de vous concentrer sur d'autres aspects.

On va commencer par créer un composant Blade pour les messages en session :

php artisan make:component session-status --view

@props(['status'])

@if ($status)
    <div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
        {{ $status }}
    </div>
@endif

Pour l'oubli du mot de passe, on crée un composant Volt : 

php artisan make:volt auth/forgot-password --class

Avec ce code :

<?php

use Illuminate\Support\Facades\Password;
use Livewire\Attributes\{ Layout, Title };
use Livewire\Volt\Component;

new #[Title('Password renewal')] #[Layout('components.layouts.auth')]
class extends Component {

	public string $email = '';

	public function sendPasswordResetLink(): void
	{
		$this->validate([
			'email' => ['required', 'string', 'email'],
		]);

		$status = Password::sendResetLink(
			$this->only('email')
		);

		if (Password::RESET_LINK_SENT != $status) {
			$this->addError('email', __($status));

			return;
		}

		$this->reset('email');

		session()->flash('status', __($status));
	}
}; ?>

<div>
    <x-card class="flex items-center justify-center h-screen" title="{{__('Password renewal')}}" subtitle="{{__('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.')}}" shadow separator  progress-indicator>
        <x-session-status class="mb-4" :status="session('status')" />
        <x-form wire:submit="sendPasswordResetLink">
            <x-input label="{{__('E-mail')}}" wire:model="email" icon="o-envelope" inline required />
            <x-slot:actions>
                <x-button label="{{ __('Email Password Reset Link') }}" type="submit" icon="o-paper-airplane" class="btn-primary" />
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

Et on ajoute la route, toujours dans le même groupe :

Route::middleware('guest')->group(function () {
	...
	Volt::route('/forgot-password', 'auth.forgot-password');
});

Il ne nous manque plus que les traductions :

"Password renewal": "Renouvellement du mot de passe",
"Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.": "Mot de passe oublié ? Pas de problème. Indiquez-nous simplement votre adresse courriel et nous vous enverrons par courriel un lien de réinitialisation du mot de passe pour en choisir un nouveau.",
"Email Password Reset Link": "Envoi du lien de renouvellement"

Si vous utilisez le formulaire, vous allez tomber sur une erreur à la soumission :

Route [password.reset] not defined.

Et effectivement, on n'a pas encore codé cette partie, alors faisons-le :

php artisan make:volt auth/reset-password --class

Avec ce code :

<?php

use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\{Hash, Password, Session};
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Livewire\Attributes\{Layout, Locked};
use Livewire\Volt\Component;

new
#[Layout('components.layouts.auth')]
class extends Component {
	#[Locked]
	public string $token = '';

	public string $email                 = '';
	public string $password              = '';
	public string $password_confirmation = '';

	public function mount(string $token): void
	{
		$this->token = $token;

		$this->email = request()->input('email');
	}

	public function resetPassword(): void
	{
		$this->validate([
			'token'    => ['required'],
			'email'    => ['required', 'string', 'email'],
			'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
		]);

		$status = Password::reset(
			$this->only('email', 'password', 'password_confirmation', 'token'),
			function ($user) {
				$user->forceFill([
					'password'       => Hash::make($this->password),
					'remember_token' => Str::random(60),
				])->save();

				event(new PasswordReset($user));
			}
		);

		if (Password::PASSWORD_RESET != $status) {
			$this->addError('email', __($status));

			return;
		}

		Session::flash('status', __($status));

		$this->redirectRoute('login', navigate: true);
	}
}; ?>

<div>
    <x-card class="flex items-center justify-center h-screen" title="{{__('Reset Password')}}" shadow separator progress-indicator>
        <x-session-status class="mb-4" :status="session('status')" />
        <x-form wire:submit="resetPassword">
            <x-input label="{{__('E-mail')}}" wire:model="email" icon="o-envelope" inline />
            <x-input label="{{__('Password')}}" wire:model="password" type="password" icon="o-key" inline />
            <x-input label="{{__('Confirm Password')}}" wire:model="password_confirmation" type="password" icon="o-key" inline required autocomplete="new-password" />
            <x-slot:actions>
               <x-button label="{{ __('Reset Password') }}" type="submit" icon="o-paper-airplane" class="btn-primary" />
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

On ajoute la route :

Route::middleware('guest')->group(function () {
	...
    Volt::route('/reset-password/{token}', 'auth.reset-password')->name('password.reset');
});

Maintenant l'envoi du lien fonctionne et on a un message qui apparaît :

Par défaut, les e-mails sont générés dans les logs, ce qui est pratique pour le développement en local. Voici le log que j'ai eu : 

# Bonjour !

Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.

Réinitialisation du mot de passe: http://moncms.oo/reset-password/80dd27b46baee89f614ee5405adcf1f686a18196fb8046a45a7c7c6cbb36a251?email=user%40example.com

Ce lien de réinitialisation du mot de passe expirera dans 60 minutes.

Si vous n'avez pas demandé de réinitialisation de mot de passe, vous pouvez ignorer ce message.

Cordialement,
Mon CMS

Si vous avez des difficultés à cliquer sur le bouton "Réinitialisation du mot de passe", copiez et collez l'URL ci-dessous
dans votre navigateur Web : [http://moncms.oo/reset-password/80dd27b46baee89f614ee5405adcf1f686a18196fb8046a45a7c7c6cbb36a251?email=user%40example.com](http://moncms.oo/reset-password/80dd27b46baee89f614ee5405adcf1f686a18196fb8046a45a7c7c6cbb36a251?email=user%40example.com)

© 2024 Mon CMS. Tous droits réservés.

En utilisant le lien on aboutit sur ce formulaire :

Après réinitialisation, on se retrouve sur la page de connexion.

Conclusion

Dans ce chapitre on a vu :

  • comment mettre facilement en place le frontend en utilisant MaryUI, Livewire et Volt
  • comment avec un composant créer le formulaire d'inscription
  • comment avec un composant créer le formulaire de connexion
  • comment ajouter le renouvellement du mot de passe

Pour vous simplifier la vie, vous pouvez charger le projet dans son état à l’issue de ce chapitre.



Par bestmomo

Aucun commentaire

Article précédent : Mon CMS - Les données
Article suivant : Mon CMS - La page d'accueil