
Dans cette partie du développement de notre boutique, nous allons intégrer les formulaires d'authentification. Bien que Laravel propose des kits d'authentification prêts à l'emploi, je préfère adopter une approche sur mesure. Cette décision nous permet de créer un système d'authentification parfaitement adapté à nos besoins spécifiques, tout en offrant une flexibilité maximale.
Vous pouvez trouver le code dans ce dépôt Github.
Créer un compte
Une règle de validation pour les mots de passe
La première des choses à prévoir est évidemment un formulaire pour s'enregistrer sur le site. On va sécuriser convenablement les mots de passe proposés en imposant une majuscule, une minuscule, un nombre et un caractère particulier, ce qui est une approche classique. Il n'existe pas de règle toute prête dans Laravel pour vérifier ça. Alors, on crée une règle personnalisée :
php artisan make:rule StrongPassword
Avec ce code :
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!preg_match('/[A-Z]/', $value) ||
!preg_match('/[a-z]/', $value) ||
!preg_match('/[0-9]/', $value) ||
!preg_match('/[^A-Za-z0-9]/', $value)) {
$fail(trans('The :attribute must contain at least one uppercase letter, one lowercase letter, one number, and one special character.'));
}
}
On ajoute la traduction du message :
"The :attribute must contain at least one uppercase letter, one lowercase letter, one number, and one special character.": "Le :attribute doit contenir au moins une lettre majuscule, une lettre minuscule, un chiffre et un caractère special."
La génération d'un mot de passe sécurisé
Pour le confort de l'utilisateur, on va proposer la génération d'un mot de passe sécurisé. Comme cette fonctionnalité sera utilisée dans plusieurs classes on crée un trait :
On en profite pour placer également les propriétés communes dans ce trait dont voilà le code :
<?php
namespace App\Traits;
trait ManageProfile
{
public string $firstname = '';
public string $name = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
public bool $newsletter = false;
public function generatePassword(int $length = 16): void
{
$characters = array_merge(
range('A', 'Z'),
range('a', 'z'),
range('0', '9'),
str_split('!@#$%^&*()_+-=[]{}|;:,.<>?')
);
$password = '';
$max = count($characters) - 1;
$password .= chr(random_int(65, 90));
$password .= chr(random_int(97, 122));
$password .= chr(random_int(48, 57));
$password .= $characters[array_rand(array_slice($characters, 62))];
while (strlen($password) < $length) {
$password .= $characters[random_int(0, $max)];
}
$this->password = str_shuffle($password);
$this->password_confirmation = $this->password;
}
}
Un email de confirmation
Il faut aussi prévoir l'envoi d'un email à l'utilisateur pour confirmer que l'inscription s'est bien déroulée.
La classe
On crée la classe qui va gérer l'envoi de l'email :
php artisan make:mail UserRegistered
Avec ce code :
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
use App\Models\Shop;
class UserRegistered extends Mailable
{
use Queueable, SerializesModels;
Public Shop $shop;
public function __construct()
{
$this->shop = Shop::firstOrFail();
}
public function envelope(): Envelope
{
return new Envelope(
from: new Address($this->shop->email, $this->shop->name),
subject: trans('You have been registered'),
);
}
public function content(): Content
{
return new Content(
view: 'mails.registered',
);
}
public function attachments(): array
{
return [];
}
}
On transmet les données de la boutique pour les renseignements essentiels à donner au nouvel inscrit.
On a une traduction à prévoir :
"You have been registered": "Vous avez bien été enregistré"
La vue
On doit créer la vue associée à cet email :
Comme le code est assez volumineux, je vous propose d'aller chercher le fichier dans le dépôt. Pour créer ce fichier, je me suis appuyé sur l'excellent site Beefree. Dans la version gratuite, on peut créer dix emails, et faire six exportations chaque mois. On peut ainsi créer le design et, après exportation, apporter les modifications nécessaires pour adapter l'email à nos données.
On a encore besoin de traductions pour cet email :
"Thank you for creating an account": "Merci d'avoir créé un compte",
"Your access code is": "Votre code d'accès est",
"Safety tips": "Conseils de sécurité",
"Your account information must remain confidential.": "Vos informations de compte doivent rester confidentielles.",
"Never give them to anyone.": "Ne les communiquez à personne.",
"Change your password regularly.": "Changez votre mot de passe régulièrement.",
"If you believe that someone is using your account illegally, please notify us immediately.": "Si vous pensez qu'une personne utilise votre compte indûment, veuillez nous notifier immédiatement.",
"You can now order on": "Vous pouvez maintenant commander sur",
"Questions?": "Des questions ?",
"our shop": "notre boutique",
"You can": "Vous pouvez",
"send us a message": "nous envoyer un message",
"Call us": "Appelez-nous",
"Hello": "Bonjour",
D'autre part, pour vos essais, vous aurez besoin d'une plateforme pour recevoir les emails. J'aime bien utiliser Mailtrap. Avec un compte gratuit, on a droit à 1000 emails par mois, ce qui est assez généreux. Les fonctionnalités sont nombreuses et faciles à utiliser. pour que ça fonctionne, vous devez entrer les bons paramètres dans le fichier .env.
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=445ccd3e72a7b2
MAIL_PASSWORD=******************
Vous trouvez votre identifiant et votre mot de passe dans votre boîte de réception sur le site. Ainsi, tous les emails qui partent de votre projet arrivent dans cette boîte de réception.
Le composant pour la création d'un compte
On crée le composant Volt pour la création d'un compte :
php artisan make:volt auth/register --class
Et voilà le code :
<?php
use App\Models\User;
use App\Traits\ManageProfile;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
use App\Notifications\NewUser;
use App\Rules\StrongPassword;
use App\Mail\UserRegistered;
use Illuminate\Support\Facades\Mail;
new #[Title('Register')]
class extends Component {
use ManageProfile;
public ?string $gender = null;
public function register()
{
if ($this->gender) {
abort(403);
}
$data = $this->validate([
'firstname' => 'required|string|max:255',
'name' => 'required|string|max:255',
'newsletter' => 'nullable',
'email' => 'required|email|unique:users',
'password' => ['required','string','min:8','confirmed', new StrongPassword,],
'password_confirmation' => 'required',
]);
$data['password'] = Hash::make($data['password']);
$user = User::create($data);
auth()->login($user);
request()->session()->regenerate();
Mail::to(auth()->user())->send(new UserRegistered());
session()->flash('registered', __('Your account has been successfully created. An email has been sent to you with all the details.'));
return redirect('/');
}
}; ?>
<div>
<x-card
class="flex justify-center items-center mt-6"
title="{{ __('Register') }}"
shadow
separator
progress-indicator
>
<x-form wire:submit="register" x-data="{ rgpd: false }" class="w-full sm:min-w-[50vw]">
<x-input
label="{{ __('Your firstName') }}"
wire:model="firstname" icon="o-user"
required
/>
<x-input
label="{{ __('Your name') }}"
wire:model="name"
icon="o-user"
required
/>
<x-input
label="{{ __('Your e-mail') }}"
wire:model="email"
icon="o-envelope"
required
/>
<x-input
label="{{ __('Your password') }}"
wire:model="password"
icon="o-key"
hint="{{ __('Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character') }}"
required
/>
<x-input
label="{{ __('Confirm Password') }}"
wire:model="password_confirmation"
icon="o-key"
required
/>
<x-button
label="{{ __('Generate a secure password') }}"
wire:click="generatePassword()"
icon="m-wrench"
class="btn-outline btn-sm"
required
/>
<hr>
<x-checkbox
label="{{ __('I would like to receive your newsletter') }}"
wire:model="newsletter"
/>
<x-checkbox
label="{!! __('I accept the terms and conditions of the privacy policy') !!}"
x-model="rgpd"
/>
<div style="display: none;">
<x-input
wire:model="gender"
type="text"
inline
/>
</div>
<p class="text-xs text-gray-500"><span class="text-red-600">*</span> @lang('Required information')</p>
<x-slot:actions>
<x-button
label="{{ __('Already registered?') }}"
class="btn-ghost"
link="/login"
/>
<x-button
x-show="rgpd"
label="{{ __('Register') }}"
type="submit"
icon="o-paper-airplane"
class="btn-primary"
spinner="login"
/>
</x-slot:actions>
</x-form>
</x-card>
</div>
Avec quelques traductions :
"Your account has been successfully created. An email has been sent to you with all the details.": "Votre compte a bien été crée. Un email vous a ete envoyé avec toutes les informations.",
"Your e-mail": "Votre courriel",
"Your password": "Votre mot de passe",
"Your firstName": "Votre prénom",
"Your name": "Votre nom",
"Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character": "Le mot de passe doit contenir au moins 8 caractères et au moins une lettre majuscule, une lettre minuscule, un chiffre et un caractère special.",
"Confirm Password": "Confirmez le mot de passe",
"Generate a secure password": "Créer un mot de passe securisé",
"I would like to receive your newsletter": "Je souhaite recevoir votre newsletter",
"I accept the terms and conditions of the privacy policy": "J'accepte les conditions de la politique de confidentialité",
"Already registered?": "Vous avez déjà un compte ?",
"Required information": "Information requise",
On ajoute la route :
use Illuminate\Support\Facades\Route;
...
Route::middleware('guest')->group(function () {
Volt::route('/register', 'auth.register');
});
Et maintenant avec l'url .../register, on arrive sur le formulaire :
Un message pour rassurer
Le nouvel inscrit va recevoir un email de confirmation, mais il est judicieux lors du renvoi sur la page d'accueil, même s'il se retrouve déjà connecté dans la boutique, de lui afficher un message. Dans le composant d'inscription, on a prévu un message en session :
session()->flash('registered', __('Your account has been successfully created. An email has been sent to you with all the details.'));
Il faut l'utiliser dans notre composant index de l'accueil :
<div class="container mx-auto">
@if (session('registered'))
<x-alert
title="{!! session('registered') !!}"
icon="s-rocket-launch"
class="mb-4 alert-info"
dismissible
/>
@endif
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);
}
Fonctionnement
Il ne nous reste plus qu'à vérifier que tout fonctionne correctement. Les validations :
L'enregistrement effectif dans la base :
Le message sur la page d'accueil :
Et la cohérence de l'email reçu :
La connexion
À présent qu'on peut s'inscrire dans la boutique, on doit prévoir la possibilité de se connecter. On crée un nouveau composant :
php artisan make:volt auth/login --class
Entrez ce code :
<?php
use Livewire\Attributes\{Validate, Title};
use Livewire\Volt\Component;
use Illuminate\Support\Str;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
new
#[Title('Login')]
class extends Component {
#[Validate('required|email')]
public string $email = '';
#[Validate('required')]
public string $password = '';
#[Validate('boolean')]
public bool $remember = false;
public function login()
{
$this->validate();
$this->authenticate();
Session::regenerate();
$this->redirectIntended(default: url('/'), navigate: true);
}
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only(['email', 'password']), $this->remember)) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
protected function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout(request()));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
protected function throttleKey(): string
{
return Str::transliterate(Str::lower($this->email).'|'.request()->ip());
}
}; ?>
<div>
<x-card
class="flex justify-center items-center mt-6"
title="{{ __('Log in') }}"
shadow
separator
progress-indicator
>
<x-form wire:submit="login" >
<x-input
label="{{ __('Your e-mail') }}"
wire:model="email"
icon="o-envelope"
type="email"
required
/>
<x-input
label="{{ __('Your password') }}"
wire:model="password"
type="password"
icon="o-key"
required
/>
<x-checkbox
label="{{ __('Remain identified for a few days') }}"
wire:model="remember"
/>
<p class="text-xs text-gray-500"><span class="text-red-600">*</span> @lang('Required information')</p>
<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 my account') }}"
class="btn-ghost"
link="/register"
/>
</div>
</div>
</x-slot:actions>
</x-form>
</x-card>
</div>
On ajoute les traductions :
"Log in": "Identifiez-vous",
"Remain identified for a few days": "Rester identifié quelques jours",
"Forgot your password?": "Mot de passe oublié ?",
"Remember me": "Se rappeler de moi",
"Create my account": "Créer mon compte",
Et la route :
Route::middleware('guest')->group(function () {
...
Volt::route('/login', 'auth.login')->name('login');
});
On obtient ce joli formulaire :
Vérifiez la validation :
Et évidemment aussi que la connexion et la déconnexion fonctionnent bien.
Conclusion
Dans ce chapitre, on a mis en place les fondamentaux de l'authentification avec l'inscription à la boutique et la connexion. Dans le prochain chapitre, on s'occupera de l'oubli du mot de passe.
Par bestmomo
Aucun commentaire