Laravel 8

Cours Laravel 8 – la sécurité – se passer de Jetstream

On a vu dans les précédents articles comment Jetstream peut nous simplifier la vie au niveau de la gestion de l’authentification et aussi tout ce qu’il nous apporte en prime (gestion du profil, des équipes…). Mais évidemment il y a un revers à la médaille. On a pas forcément besoin de tout ce que Jetstream nous propose, mais on a vu qu’il est facile de désactiver des fonctionnalités au niveau de la configuration. Un autre aspect qui peut se révéler assez gênant c’est que Jetstream nous impose des technologies comme Tailwind ou Livewire que l’on n’a pas forcément envie d’utiliser. Dans cet article je vous montre comment utiliser Fortify sans l’aide de Jetstream.

Si toutes ces manipulations vous rebutent vous pouvez toujours vous orienter sur le package laravel/ui.

Un petit point

Il faut déjà bien comprendre qui s’occupe de quoi, voici une petite illustration :

Laravel

A la base Laravel est équipés de Guards, c’est à dire de systèmes qui définissent comment un utilisateur doit être authentifié, par exemple avec des sessions (seesion guard). Il est également équipé de providers qui permettent de retrouver un utilisateur à partir d’un lieu de stockage de l’information, en général la base de données.

Laravel est donc équipé de services accessible au travers des façades Auth et Session. Il sait donc gérer une authentification avec la création de cookies. Il a également des méthodes pour retrouver un utilisateur à partir de ses données (credencials).

On peut donc créer un système d’authentification en accédant directement à ces systèmes, comme c’était d’ailleurs le cas avec les versions précédentes de Laravel. On peut par exemple créer un contrôleur pour la connexion :

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials)) {
            return redirect()->intended('dashboard');
        }
    }
}

Fortify

Alors que nous apporte Fortify ? Eh bien il nous facilite justement l’utilisation de l’infrastructure de base de Laravel. Il a été créé pour le fonctionnement de Jetstream mais il peut aussi être utilisé indépendamment de lui comme je me propose de le faire justement dans cet article.

Installation

On va partir d’un Laravel 8 tout neuf et installer Fortify :

composer require laravel/fortify

Ensuite on publie ses ressources :

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

Ça a pour effet d’ajouter ces actions :

Et également ce provider :

Il faut déclarer ce provider dans config/app.php :

App\Providers\FortifyServiceProvider::class,

On peut ensuite lancer les migrations :

php artisan migrate

A ce niveau Fortify se contente d’ajouter deux colonnes dans la tables users pour l’authentification à deux facteurs :

Un élément important à connaître est le fichier de configuration de Fortify :

On y trouve en particulier les fonctionnalités disponibles :

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    // Features::emailVerification(),
    Features::updateProfileInformation(),
    Features::updatePasswords(),
    Features::twoFactorAuthentication([
        'confirmPassword' => true,
    ]),
],

On peut les activer ou les désactiver selon les besoins. Pour notre exemple on va conserver seulement les 3 premières options :

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
],

Pour les vues on ne va pas se compliquer la vie et utiliser celle du package laravel/ui qui sont conçues pour Bootstrap 4 mais évidemment ça serait pareil pour n’importe quel framework frontend. On va les récupérer là :

Ce sont des stub et on va devoir modifier les noms. On va donc copier tout ça dans le projet :

N’oubliez pas la vue home.

Pour encore simplifier on ne va pas générer les librairies avec npm mais utiliser des CDN dans le layout :

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">

    <!-- Styles -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">

                    </ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                            </li>
                            @if (Route::has('register'))
                                <li class="nav-item">
                                    <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                                </li>
                            @endif
                        @else
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }}
                                </a>

                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            @yield('content')
        </main>
    </div>

    <!-- Scripts -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script><br />    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>

</body>
</html>

Pour finir on va ajouter on va ajouter la route pour afficher la page d’acceuil (home) de l’utilisateur connecté :

Route::get('home', function () {
  return view('home');
})->middleware('auth');

L’enregistrement

Voyons en premier l’enregistrement des utilisateurs. Il suffit d’indiquer à Fortify où trouver la vue, donc dans le provider FortifyServiceProvider :

public function boot()
{
    ...

    Fortify::registerView(function () {
        return view('auth.register');
    });
}

Maintenant avec lurl …/register on retrouve le formulaire d’enregistrement :

La redirection est celle déclarée dans le fichier config/fortify.php :

'home' => RouteServiceProvider::HOME,

Par défaut dans le provider on a :

public const HOME = '/home';

La connexion

Pour la connexion c’est le même principe :

public function boot()
{
    ...

    Fortify::loginView(function () {
        return view('auth.login');
    });
}

Avec ce formulaire :

Pour la redirection c’est la même chose.

L’oubli du mot de passe

C’est encore le même principe :

public function boot()
{
    ...

    Fortify::requestPasswordResetLinkView(function () {
        return view('auth.passwords.email');
    });

    Fortify::resetPasswordView(function ($request) {
        return view('auth.passwords.reset', ['token' => $request->token]);
    });
}

Avec ce formulaire pour la demande :

On a la confirmation que l’email est parti :

On le reçoit bien :

Et on obtient le formulaire de réinitialisation du mot de passe :

Lorsqu’on renseigne un nouveau mot de passe on retourne sur le formulaire de connexion.

Vérification de l’email

Pour la vérification de l’email ça se passe encore dans le provider :

public function boot()
{
    ...

    Fortify::verifyEmailView(function () {
        return view('auth.verify');
    });
}

Dans la vue auth.verify on va changer le nom de la route pour la réexpédition de l’email :

<form class="d-inline" method="POST" action="{{ route('verification.send') }}">

Dans le modèle User on doit implémenter le trait :

class User extends Authenticatable implements MustVerifyEmail

Et enfin on ajoute une route avec le middleware pour voir si ça marche :

Route::get('test', function () {
  return 'Vue de test';
})->middleware(['verified']);

Et pour l’url …/test :

Et on reçoit bien l’email :

La confirmation du mot de passe

Pour terminer voyons la confirmation du mot de passe. Reprenons notre route de test mais cette fois en vérifiant que l’utilisateur est authentifié et qu’en plus il doit confirmer son mot de passe :

Route::get('test', function () {
  return 'Vue de test';
})->middleware(['auth', 'password.confirm']);

On renseigne encore le provider de Fortify :

public function boot()
{
    ...

    Fortify::confirmPasswordView(function () {
        return view('auth.passwords.confirm');
    });
}

Et pour l’url …/test on a le formulaire qui arrive :

Conclusion

On a vu dans cet article qu’on peut mettre en place une authentification sans l’aide de Jetstream en utilisant le frontend qu’on veut. Fortify nous facilite grandement la tâche en établissant la liaison avec le framework. Mais évidemment on ne dispose que de l’authentification de base :

  • enregistrement, connexion,
  • oubli du mot de passe,
  • vérification de l’email,
  • confirmation du mot de passe

Sans toutes les fioritures de Jetstream. Toutefois Fortify contient d’autres possibilités que je développerai sans doute dans un prochain article.

Print Friendly, PDF & Email

Leave a Reply