Laravel 8

Créer un blog – l’authentification

Nous avons dans le précédent article terminé la gestion des commentaires. Nous avons donc l’essentiel du frontend de notre blog mais nous sommes encore loin d’en avoir terminé ! Dans le présent article nous allons changer les vues de l’authentification pour les adapter à notre thème. Ca va être un peu laborieux parce qu’il nous faut pas mal de vues, mais on va s’arranger pour rester suffisamment DRY pour que ça se passe bien !

Vous pouvez télécharger le code final de cet article ici.

Un petit point

Avec Breeze on a installé un dossier avec toutes les vues de l’authentification :

On va conserver ces vues, à part confirm-password et verify-email qui ne nous serviront pas, et juste en changer le code.

On a aussi installé de nombreux composants :

Là on va être sans pitié et on va tous les supprimer (attention à ne pas supprimer le dossier front où on a déjà créé des composants).

On a aussi ces vues :

Ce sont les layouts, la page d’accueil et le dashboard. Là aussi on va se débarrasser de tout ça !

Après ces différentes purges vous devriez n’avoir plus que ces vues :

Vérifiez quand même que votre blog fonctionne encore !

On va aussi supprimer des assets générés pour Breeze (app.css et app.js) :

Cinq composants

On va créer 5 composants pour mutualiser du code et simplifier les vues :

  • une alerte pour le statut
  • une alerte pour la liste des erreurs de validation
  • un input pour l’email
  • un input pour le mot de passe
  • le bouton de soumission

Pour session-status :

@props(['status'])

@if ($status)
    <div class="alert-box alert-box--info">
        <p>{{ $status }}</p>
        <span class="alert-box__close"></span>
    </div>
@endif

Pour validation-errors :

@props(['errors'])

@if ($errors->any())    
    <div class="alert-box alert-box--error">
        <div style="padding-bottom:1rem">@lang('Whoops! Something went wrong.')</div>
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
        <span class="alert-box__close"></span>
    </div> 
@endif

Pour input-email :

@props(['email' => ''])

<div>
  <label for="email">@lang('Email')</label>  
  <input 
      id="email" 
      class="h-full-width" 
      type="email" 
      name="email" 
      placeholder="@lang('Your email')" 
      value="{{ old('email', $email) }}" 
      required 
      autofocus>
</div>

Pour input-password :

<div>
    <label for="password">@lang('Password')</label> 
    <input 
        id="password" 
        class="h-full-width" 
        type="password" 
        name="password" 
        placeholder="@lang('Your password')" 
        required>
</div>

Et enfin pour submit :

@props(['title'])

<input class="btn--primary h-full-width" type="submit" value="@lang($title)">

La connexion

On va commencer par la page de connexion.

On va changer ainsi le code :

@extends('front.layout')

@section('main')
    <div class="row row-x-center s-styles">
        <div class="column large-6 tab-12">

            <!-- Session Status -->
            <x-auth.session-status :status="session('status')" />

            <!-- Validation Errors -->
            <x-auth.validation-errors :errors="$errors" />

            <h3 class="h-add-bottom">@lang('Login')</h3>
            <form class="h-add-bottom" method="POST" action="{{ route('login') }}">
                @csrf                

                <!-- Email Address -->
                <x-auth.input-email />

                <!-- Password -->
                <x-auth.input-password />

                <!-- Remember Me -->
                <label class="h-add-bottom">
                    <input 
                        id="remember_me" 
                        type="checkbox" 
                        name="remember_me" 
                        {{ old('remember_me') ? 'checked' : '' }}> 
                    <span class="label-text">@lang('Remember me')</span>
                </label>

                <x-auth.submit title="Login" />
                
                <label class="h-add-bottom">
                  <a href="{{ route('password.request') }}">
                      @lang('Forgot Your Password?')
                  </a>                  
                  <a href="{{ route('register') }}" style="float: right;">
                      @lang('Not registered?')
                  </a>
                </label>

            </form>
        </div>
    </div>

@endsection

Avec l’url monblog.ext/login on obtient maintenant cet aspect :

C’est pas mal mais un chose me gêne : le formulaire est trop collé au footer. On va un peu espacer ça.

On ajoute un règle de style dans public.styles.css :

.s-styles {
  padding-bottom: var(--vspace-4);
}

J’ai déjà ajouté cette classe dans la vue. Du coup c’est plus aéré :

Normalement on doit avoir une alerte en cas de problème :

Et en cas de d’authentification réussie vous tombez maintenant sur une erreur :

Ce qui est logique puisqu’on a supprimé la vue. On voudrait à présent une redirection sur l’accueil du blog. Ca se passe dans RouteServiceProvider, il suffit de changer la valeur de HOME :

public const HOME = '/';

Et ça doit être bon maintenant !

Mais ce qui serait encore mieux c’est de disposer d’un lien vers la page de connexion dans notre barre de navigation. Il faudrait également prévoir la possibilité de se déconnecter. On va compléter le code dans front.layout :

<ul class="s-header__nav">
    ...
    @guest                        
        <li {{ currentRoute('login') }}>
            <a href="{{ route('login') }}">@lang('Login')</a>
        </li>
    @else
        <li>                                
            <form action="{{ route('logout') }}" method="POST" hidden>
                @csrf                                
            </form>
            <a 
                href="{{ route('logout') }}"
                onclick="event.preventDefault(); this.previousElementSibling.submit();">
                @lang('Logout')
            </a>
        </li>
    @endguest
</ul>

On a maintenant un lien pour la connexion :

Et quand on est connecté un lien pour se déconnecter :

L’enregistrement

On va s’intéresser à présent à la vue pour l’enregistrement :

@extends('front.layout')

@section('main')
    <div class="row row-x-center s-styles">
        <div class="column large-6 tab-12">

            <!-- Validation Errors -->
            <x-auth.validation-errors :errors="$errors" />

            <h3 class="h-add-bottom">@lang('Register')</h3>
            <form class="h-add-bottom" method="POST" action="{{ route('register') }}">
                @csrf
                
                <!-- Name -->
                <div>
                  <label for="name">@lang('Name')</label>  
                  <input 
                      id="name" 
                      class="h-full-width" 
                      type="text" 
                      name="name" 
                      placeholder="@lang('Your name')" 
                      value="{{ old('name') }}" 
                      required 
                      autofocus>
                </div>

                <!-- Email Address -->
                <x-auth.input-email />

                <!-- Password -->
                <x-auth.input-password />

                <!-- Confirm Password -->
                <div>
                  <label for="password_confirmation">@lang('Confirm Password')</label> 
                  <input 
                      id="password_confirmation" 
                      class="h-full-width" 
                      type="password" 
                      name="password_confirmation" 
                      placeholder="@lang('Confirm your Password')" 
                      required>
                </div>                

                <x-auth.submit title="Register" />
                
            </form>
        </div>
    </div>

@endsection

On profite évidemment largement des composants qu’on a créés précédemment. On se retrouve avec un formulaire dans le même style :

 

La validation devrait fonctionner :

On va également compléter la barre de navigation. mais dans un premier temps on ajoute une commande à Blade (dans AppServiceProvider) :

use Illuminate\Support\Facades\{ Blade, View };

...

public function boot()
{
    ...

    Blade::if('request', function ($url) {
        return request()->is($url);
    });
}

On se sert de cette commande pour compléter le menu :

@guest
    @request('register')
        <li class="current">
            <a href="{{ request()->url() }}">@lang('Register')</a>
        </li>
    @endrequest
    <li {{ currentRoute('login') }}>
        <a href="{{ route('login') }}">@lang('Login')</a>
    </li>
@else

J’ai choisi de ne pas faire apparaître systématiquement ce lien d’enregistrement mais juste lorsqu’on affiche le formulaire qui est accessible à partir du formulaire de connexion :

Dès qu’on affiche la page d’enregistrement le lien apparaît dans le menu :

Le renouvellement du mot de passe

La demande

Pour le renouvellement on va remplacer le code de la vue forgot-password :

@extends('front.layout')

@section('main')
    <div class="row row-x-center s-styles">
        <div class="column large-6 tab-12">

            <!-- Session Status -->
            <x-auth.session-status :status="session('status')" />

            <!-- Validation Errors -->
            <x-auth.validation-errors :errors="$errors" />

            <p class="h-add-bottom">@lang('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.')</p>
            <form class="h-add-bottom" method="POST" action="{{ route('password.email') }}">
                @csrf                

                <!-- Email Address -->
                <x-auth.input-email />

                <x-auth.submit title="Email Password Reset Link" />
               
            </form>
        </div>
    </div>

@endsection

On va envoyer un email. Pour vos tests le plus efficace est d’utiliser un service comme Mailtrap. Il suffit de renseigner le fichier .env pour que ça fonctionne :

MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=xxx
MAIL_PASSWORD=xxx
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=admin@monblog.fr
MAIL_FROM_NAME="${APP_NAME}"

On présente un message pour confirmer l’envoi :

Par défaut l’email se présente ainsi :

Pour le moment tout est en anglais mais mettra tout en français à la fin. Comme j’ai bien prévu dans le code la syntaxe adéquate il nous suffira de créer un fichier de traduction global en plus des fichiers de base (validation, pagination, mots de passe…).

Le renouvellement

Pour le renouvellement on va changer le code de la vue reset-password :

@extends('front.layout')

@section('main')
    <div class="row row-x-center s-styles">
        <div class="column large-6 tab-12">

            <!-- Validation Errors -->
            <x-auth.validation-errors :errors="$errors" />

            <h3 class="h-add-bottom">@lang('Password reset')</h3>
            <form class="h-add-bottom" method="POST" action="{{ route('password.update') }}">
                @csrf
                
                <!-- Password Reset Token -->
                <input type="hidden" name="token" value="{{ $request->route('token') }}">

                <!-- Email Address -->
                <x-auth.input-email :email="$request->email" />

                <!-- Password -->
                <x-auth.input-password />

                <!-- Confirm Password -->
                <div>
                  <label for="password_confirmation">@lang('Confirm Password')</label> 
                  <input 
                      id="password_confirmation" 
                      class="h-full-width" 
                      type="password" 
                      name="password_confirmation" 
                      placeholder="@lang('Confirm your Password')" 
                      required>
                </div>
                
                <x-auth.submit title="Reset Password" />
               
            </form>
        </div>
    </div>

@endsection

Dans le formulaire l’email est déjà renseigné :

Là aussi la validation doit fonctionner :

Si le mot de passe est bien renouvelé on renvoie sur la page de connexion avec un message :

Le renouvellent fonctionne bien mais je trouve dommage de ne pas avoir une indication de cette opération dans le menu. On va donc le compléter (front.layout) :

@guest
    ...                       
    @request('forgot-password')
        <li class="current">
            <a href="{{ request()->url() }}">@lang('Password')</a>
        </li>
    @endrequest
    @request('reset-password/*')
        <li class="current">
            <a href="{{ request()->url() }}">@lang('Password')</a>
        </li>
    @endrequest
@else

Ainsi on aura un repère dans le menu pendant cette opération :

Conclusion

Voilà on est à jour pour la partie authentification. Le frontend est maintenant bien avancé. Il nous reste peu de chose à code : le formulaire de contact, la partie CMS avec les pages et aussi les liens sociaux. Il nous faudra compléter notre base de données pour gérer tout ça.

Print Friendly, PDF & Email

14 commentaires

    • bestmomo

      Bonjour,

      Par défaut quand on installe Breeze on a un layout layouts.guest.

      Pour le blog on supprime ce layout et on utilise front.layout pour toutes les vues de l’authentification.

      Tu dois avoir l’erreur parce que tu as supprimé layouts.guest et pas encore créé les nouvelles vues.

  • slozano54

    Bonjour,
    Merci pour ce tuto très instructif.
    Je rencontre un problème avec la vue reset-passord.

    Laravel m’indique cette erreur :
    Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
    The GET method is not supported for this route. Supported methods: POST.

    J’ai donc vérifié que je n’avais rien oublié dans le code du fichier routes.auth.php

    Il y a bien une route get avec la méthode create et une route post avec la méthode store.
    J’ai comparé avec votre fichier dans l’archive jointe à l’article mais sans succès.

    Je suis allé voir dans app.Providers mais sans succès non plus.

    Une idée de ce à côté de quoi je suis passé ?

    Merci d’avance pour votre réponse.

  • Madz

    Bonjour, BESTMOMO, merci infiniment de ton travail et ta passion, j’aurai une question je début laravel, j’ai donc choisis Breeze comme starter pour l’authentification.
    Mais pour la customisation de ce dernier je ne m’y retrouve pas assez.
    J’aimerais personnalisé le forgot et reset /mot de passe.

    Pour les Login : username a la place du mail,
    un user a plusieurs adresses mail, (donc dans une table emails où l’adresse mail porte l’user_id)
    quand la personne oubli le mot de passe, je lui demande son username + une de ses adresses mail,
    et la je rencontre des difficultés pour personnalisé/override le Password::broker() pour qu’il arrête de chercher dans la table user un email.

    Merci d’avance BESTMoMo, je sais pas si j’étais assez clair.

    • bestmomo

      Salut,
      Dans le contrôleur PasswordResetLinkController tu as ce code :
      $request->validate([
      'email' => 'required|email',
      ]);
      $status = Password::sendResetLink(
      $request->only('email')
      );

      Par défaut le credential est constitué de l’email, mais là tu peux mettre le nom à la place. Mais je pense que ton souci vient après, avec le système de notification de Laravel. Je pense qu’il faut surcharger la méthode sendResetLink de PasswordBroker pour éviter le fonctionnement normal de la notification qui va forcément planter.

Laisser un commentaire