Laravel 11

Albums – Authentification

Dans cette partie de notre travail sur l’application de galerie photos, nous allons nous concentrer sur l’amélioration de l’apparence et de l’intégration des formulaires d’authentification. Bien que Laravel propose des kits d’authentification prêts à l’emploi tels que Breeze et Jetstream, nous allons cette fois-ci opter pour une approche plus personnalisée en nous basant uniquement sur les fonctionnalités dont nous avons vraiment besoin., ce qui nous permettra d’ajuster le système d’authentification à nos exigences spécifiques.

Nous exploiterons les capacités de MaryUI et Volt pour concevoir des formulaires d’authentification élégants et fonctionnels, intégrés de manière homogène à l’ensemble de l’application. Grâce à la flexibilité de ces outils, nous créerons une expérience utilisateur robuste pour la connexion et l’inscription, tout en maintenant une apparence cohérente avec notre projet.

Cette approche personnalisée avec Laravel, MaryUI et Volt vous aidera à comprendre les concepts fondamentaux de gestion des utilisateurs et des sessions dans Laravel. À la fin de ce chapitre, votre application de galerie photos disposera d’un système d’authentification personnalisé et efficace, offrant une expérience utilisateur harmonieuse et sécurisée.

Les langues

On va faire une application en français, mais en conservant la possibilité d’utiliser n’importe quelle langue. À la base, on va tout prévoir en anglais et o va utiliser les possibilités de Laravel pour rendre l’application multilingue. Dans un premier temps, on installe le plugin dédié :

composer require --dev laravel-lang/common

Dans le fichier .env on change la locale :

APP_LOCALE=fr

Puis on installe :

php artisan lang:update

Si tout s’est bien passé, vous avez ajouté le français :

L’inscription

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

On va le coder ainsi :

<?php

use App\Models\User;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Rule;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
use Illuminate\Support\Facades\Hash;
 
new
#[Title('Register')]
class extends Component {
 
    #[Rule('required|string|max:255|unique:users')]
    public string $name = '';
 
    #[Rule('required|email|unique:users')]
    public string $email = '';
 
    #[Rule('required|confirmed')]
    public string $password = '';
 
    #[Rule('required')]
    public string $password_confirmation = '';
 
    public function register()
    {
        $data = $this->validate();
 
        $data['password'] = Hash::make($data['password']);
 
        $user = User::create($data);
 
        auth()->login($user);
 
        request()->session()->regenerate();
 
        return redirect('/');
    }
}; ?>

<div>
    <x-card class="h-screen flex items-center justify-center" title="{{__('Register')}}" shadow separator>
 
        <x-form wire:submit="register">
            <x-input label="{{__('Name')}}" wire:model="name" icon="o-user" inline />
            <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 />
    
            <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>

Et évidemment prévoir la route avec un groupe équipé du middleware guest :

use Illuminate\Support\Facades\Route;

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

Maintenant avec la route …/register, on tombe sur le formulaire d’inscription :

On se rend compte qu’il nous manque des traductions ici. On va arranger ça dans le fichier fr.json :

"Password": "Mot de passe",
"Confirm Password": "Confirmation du mot de passe",
"Create an account": "Créer un compte",
"Already registered?": "Déjà enregistré ?",
"Name": "Nom",

Voilà qui est mieux :

Vous pouvez vérifier que ça fonctionne. Comme on n’utilisera pas la vérification de l’E-mail, vous pouvez enlever cette ligne dans la migration de la table users :

$table->timestamp('email_verified_at')->nullable();

Vous pouvez ensuite relancer les migrations pour purger la base :

php artisan migrate:fresh

Quand quelqu’un s’enregistre, on le retrouve dans la base :

En ce qui concerne la validation, Livewire peut utiliser la façon classique de Laravel mais aussi d’utiliser des attributs pour chaque propriété comme je l’ai fait ici. Vérifiez que ça fonctionne :

Au niveau du code HTML, vous pouvez remarquer l’élégance et la concision de la syntaxe de MaryUI.

La connexion

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

php artisan make:volt auth/login --class

Avec ce code :

<?php

use Livewire\Attributes\Layout;
use Livewire\Attributes\Rule;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;

new 
#[Title('Login')]
class extends Component {
    #[Rule('required|email')]
    public string $email = '';
 
    #[Rule('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="h-screen flex items-center justify-center" title="{{__('Login')}}" shadow separator>
 
        <x-form wire:submit="login">
            <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-checkbox label="{{ __('Remember me') }}" wire:model="remember"/>

            <x-slot:actions>
                <x-button label="{{__('Forgot your password?')}}" class="btn-ghost" link="/forgot-password" />
                <x-button label="{{__('Create an account')}}" class="btn-ghost" link="/register" />
                <x-button label="{{__('Login')}}" type="submit" icon="o-paper-airplane" class="btn-primary" />
            </x-slot:actions>
        </x-form>

    </x-card>
</div>

Et la route avec évidemment encore le middleware guest :

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

Maintenant avec …/login, on a le formulaire :

Là il nous manque encore 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"

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 de votre entreprise.

En conclusion, la prévision d’une fonctionnalité de réinitialisation de mot de passe est un élément crucial lors du développement d’une plateforme en ligne. En mettant en place un système efficace pour répondre aux cas d’oubli de mot de passe, vous améliorez l’expérience utilisateur, renforcez la sécurité, répondez aux standards de l’industrie, et allégez la charge de support liée aux problèmes de connexion.

On va commence rpar créer un composant Blade pour les messages en session :

php artisan make:component session-status --view

Avec ce simple code :

@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 va créer un composant Volt :

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

Avec ce code :

<?php

use Livewire\Volt\Component;
use Illuminate\Support\Facades\Password;

new class extends Component {

    public string $email = '';

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

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

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

            return;
        }

        $this->reset('email');

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

<div>
    <x-card class="h-screen flex items-center justify-center" 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>
        <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 />
            
            <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 la route :

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

Encore quelques 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 e-mail et nous vous enverrons par e-mail un lien de réinitialisation du mot de passe pour en choisir un nouveau.",
"Email Password Reset Link": "Envoi du lien de renouvellement du mot de passe"

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;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Volt\Component;

new 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()->string('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 ($status != Password::PASSWORD_RESET) {
            $this->addError('email', __($status));

            return;
        }

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

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

<div>
    <x-card class="h-screen flex items-center justify-center" title="{{__('Reset Password')}}" shadow separator>
        <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>

Et 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 log, 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://albums.oo/reset-password/3af064ef749faa37bb59372ad89883b6f4e86ca8dc3c4c5744f0e91ff7bd318b?email=durand%40chezlui.fr

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,
Laravel

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://albums.oo/reset-password/3af064ef749faa37bb59372ad89883b6f4e86ca8dc3c4c5744f0e91ff7bd318b?email=durand%40chezlui.fr](http://albums.oo/reset-password/3af064ef749faa37bb59372ad89883b6f4e86ca8dc3c4c5744f0e91ff7bd318b?email=durand%40chezlui.fr)

© 2024 Laravel. Tous droits réservés.

On aboutit sur ce formulaire :

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

Le menu et la déconnexion

Maintenant qu’on a mis en place l’inscription, la connexion et l’oubli du mot de passe il nous reste à prévoir leur accès par le menu latéral et aussi la possobilité de se déconnecter facilement.

On a déjà un début de mise en place lorsque quelqu’un est connecté :

Mais le bouton de déconnexion ne fonctionne pas parce qu’il pointe sur l’url /logout qui est sans route correspondante. Changez ainsi le code dans le composant de navigation :

@if($user = auth()->user())
    <x-menu-separator />
        <x-list-item :item="$user" value="name" sub-value="email" no-separator no-hover class="-mx-2 !-my-2 rounded">
            <x-slot:actions>
                <x-button icon="o-power" wire:click="logout" class="btn-circle btn-ghost btn-xs" tooltip-left="{{__('Logout')}}" no-wire-navigate />
            </x-slot:actions>
        </x-list-item>
    <x-menu-separator />
@else
    <x-menu-item title="{{__('Login')}}" icon="o-user" link="{{ route('login') }}" />                   
@endif

On a plusieurs changements :

  • le bouton pour la déconnexion active maintenant une action logout et non plus une url
  • le texte du tooltip a été changé
  • un nouvel item de menu a été ajouté pour pointer sur la connexion

On va donc coder l’action logout dans la partie PHP :

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

new class extends Component 
{
    public function logout(): void
    {
        Auth::guard('web')->logout();

        Session::invalidate();
        Session::regenerateToken();

        $this->redirect('/');
    }
}; ?>

Vous pouvez vérifier que ça fonctionne. Lorsque personne n’est connecté on a l’item de menu pour la connexion :

Et si quelqu’un est connecté on a son nom, son e-mail et un bouton pour la déconnexion :

Conclusion

Dans ce chapitre on a vu :

  • comment mettre facilement en place le français pour notre site tout en le conservant multilingue
  • 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
  • comment synchroniser le menu avec ces possibilités tout en ajoutant la déconnexion

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

 

Print Friendly, PDF & Email

Un commentaire

Laisser un commentaire