Laravel 8

Cours Laravel 8 – la sécurité – l’authentification

L’authentification constitue une tâche fréquente. En effet il y a souvent des parties d’un site qui ne doivent être accessibles qu’à certains utilisateurs, ne serait-ce que l’administration. La solution proposée par Laravel est d’une grande simplicité parce que tout est déjà préparé comme nous allons le voir dans ce chapitre.

Au fil de l’avolution de Laravel ce domaine a connu plusieurs fois des changements. Ainsi avec Laravel 6 est apparu une nouveauté. Précédemment il y avait la commande php artisan make:auth pour générer les vues. Cette commande a disparu au profit d’un package à installer (laravel/ui). Mais les changements continuent parce que désormais on nous dit d’utiliser Jetstream. Je vais donc orienter mon cours sur ce nouveau package.

Toutefois il faut savoir que le package laravel/ui va continuer à être mis à jour (alors qu’on nous avait annoncé au départ son obsolescence) et pourra donc être utilisé avec Laravel 8. Pour savoir comment s’y prendre vous pouvez suivre le cours de Laravel 6 avec la partie concernant l’authentification parce que rien n’a changé depuis.

En résumé pour l’authentification on a ces possibilités :

  • Utiliser laravel/ui comme on le faisait pour les versions antérieures (se référer alors à mon cours sur Laravel 6).
  • Utiliser Jetstream, c’est l’objet du présent article, mais à ce niveau on a deux possibilités :
    • Livewire (c’est le choix que j’ai fait pour cet article)
    • Inertia (ça concerne surtout ceux qui veulent s’orienter sur une SPA)
  • Utiliser Fortify en créant soi-même les vues et accessoirement le traitement de fonctionnalités complémentaires (je traite cette possibilité ici)

Je sais que le débutant peut trouver cette situation un peu confuse mais c’est comme ça ! L’option la plus simple est certainement d’installer laravel/ui. Le code est simple et lisible et facilement accessible et modifiable. Jetstream s’adresse à ceux qui sont friands de nouvelles technologies. En effet Livewire et Inertia sont de nouveaux venus et on ne sait pas vraiment quel sera leur avenir, pour le moment ils ont un certain succès.

Quelques principes

Comment fonctionne une authentification ? En général l’utilisateur utilise un navigateur, renseigne son nom et son mot de passe dans un formulaire, ces informations sont reçues par le serveur, vérifiées, et si tout est bon on mémorise cette authentification en session. Cette session possède un identifiant qui est transmis à chaque requête pour éviter d’avoir à s’authentifier à chaque fois. Lorsqu’une requête HTTP arrive avec un identifiant de session l’application considère l’utilisateur correspondant comme authentifié. Ce principe ne s’applique évidemment plus dans le cas d’une API parce qu’on ne dispose pas de session et aucune persistance et on utilise alors un token pour chaque requête.

Laravel est équipé de tout ce qui est nécessaire pour cette authentification au travers des façades Auth et Session. La gestion du cookie est automatisée. D’autre part on dispose de nombreuses méthodes pratiques, par exemple pour retrouver facilement les renseignements sur l’utilisateur authentifié.

On peut très bien utiliser cette infrastructure pour construire sois-même un système complet d’authentification mais c’est assez laborieux. Il est beaucoup plus simple d’utiliser les packages complémentaires Jetstream et Laravel Fortify.

Laravel Fortify gère les outils d’authentification de Laravel sans se soucier de la partie frontend. Autrement dit lorsqu’on utilise Fortify on a aucune vue. Si on veut quelque chose de complet il faut aussi utiliser Jetstream qui est un ensemble de vues qui utilisent l’intendance de Fortify. Mais dans ce cas on se voit imposer un certain nombre de technologies : Tailwind CSS, Laravel Livewire, et/ou Inertia.js. Alors vous avez le choix, soit vous passez par ces technologie modernes et un peu dépaysantes, soit vous vous contentez de Fortify et vous constituez vos vues avec la librairie CSS que vous aimez.

Dans cet article je vais présenter l’authentification en utilisant Jetstream.

Ce n’est pas la partie la plus simple de Laravel, d’autant que la version 8 introduit de nombreuses nouveautés.

La base de données

Par défaut la partie persistance de l’authentification (c’est à dire la manière de retrouver les renseignements des utilisateurs) avec Laravel se fait en base de données avec Eloquent et part du principe qu’il y a un modèle App\Models\User (dans le dossier app).

Lors de l’installation on a vu dans les chapitres précédents qu’il existe déjà des migrations :

Repartez d’une installation vierge et faites la migration avec Artisan :

Vous devriez normalement obtenir ces tables :

Je rappelle que la table migrations sert seulement d’intendance pour les migrations et que vous ne devez pas y toucher.

  • table users : par défaut Laravel considère que cette table existe et il s’en sert comme référence pour l’authentification.
  • table password_resets : cette table va nous servir pour la réinitialisation des mots de passe.

Les middlewares

Je vous ai déjà parlé des middlewares qui servent à effectuer un traitement à l’arrivée (ou au départ) des requêtes HTTP. On a vu le middleware de groupe web qui intervient pour toutes les requêtes qui arrivent. Dans ce chapitre on va voir deux autres middlewares qui vont nous permettre de savoir si un utilisateur est authentifié ou pas pour agir en conséquence.

Middleware auth

Le middleware auth permet de n’autoriser l’accès qu’aux utilisateurs authentifiés. Ce middleware est déjà présent et déclaré dans app\Http\Kernel.php :

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    ...
];

On voit que la classe se trouve dans l’application (ce qui n’était pas le cas avant la version 6) :

On peut utiliser ce middleware directement sur une route :

Route::get('comptes', function() {
    // Réservé aux utilisateurs authentifiés
})->middleware('auth');

Ou un groupe de routes :

Route::middleware('auth')->group(function () {
    Route::get('/', function ()    {
        // Réservé aux utilisateurs authentifiés
    });
    Route::get('comptes', function () {
        // Réservé aux utilisateurs authentifiés
    });
});

Le groupement de routes avec la méthode group permet de mutualiser des actions et de simplifier le code.

Ou dans le constructeur d’un contrôleur :

public function __construct()
{
    $this->middleware('auth');
}

Dans ce cas on peut désigner les méthodes concernées avec only ou non concernées avec except :

public function __construct()
{
    $this->middleware('auth')->only(['create', 'update']);
}

Middleware guest

Ce middleware est exactement l’inverse du précédent : il permet de n’autoriser l’accès qu’aux utilisateurs non authentifiés. Ce middleware est aussi déjà présent et déclaré dans app\Http\Kernel.php :

protected $routeMiddleware = [
    ...
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    ...
];

La classe se trouve aussi dans l’application :

De la même manière que auth, le middleware guest, comme d’ailleurs tous les middlewares, peut s’utiliser sur une route, un groupe de routes ou dans le constructeur d’un contrôleur, avec la même syntaxe.

Les routes de l’authentification

Dans l’installation de base vous ne trouvez aucune route pour l’authentification. Pour les créer (et ça ne créera pas seulement les routes) il faut déjà installer un package :

composer require laravel/jetstream

Ensuite on a le choix entre Livewire et Inertia selon les goûts ou habitudes. Pour ce cours je vais utiliser Livewire qui a actuellement un grand succès dans la communauté.

php artisan jetstream:install livewire
npm install
npm run dev
php artisan migrate

Ça prend un peu de temps parce qu’il y a pas mal de chose à faire. Je parlerai de cet aspect dans un article ultérieur.

Pendant ce temps regardez ce qui a été ajouté dans le fichier routes/web.php :

Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
    return view('dashboard');
})->name('dashboard');

Voyons quelles sont les routes générées (vous devez maintenant bien connaître la commande correspondante d’Artisan, là j’utilise l’option -c pour avoir une liste compacte) :

Vous voyez qu’on a produit pas mal de routes mais elles ne servent pas toutes pour l’authentification !

Une autre chose intéressante aussi est la création d’un dossier pour les actions de Fortify et Jetstream :

En ce qui concerne la base de données vous pouvez avoir un aperçu précis dans DrawSQL.

Nous allons voir bientôt tout ça en action.

Les vues de l’authentification

Il y a également eu la génération de nombreuses vues :

On va aussi voir à quoi servent toutes ces vues. Elles ne sont pas toutes utilisées par l’authentification.

L’enregistrement d’un utilisateur

Commençons par le commencement avec l’enregistrement d’un utilisateur. Si vous allez sur la page d’accueil vous allez remarquer la présence en haut à droite de la page de deux liens :

On trouve ce code dans la page d’accueil :

@if (Route::has('login'))
    <div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
        @auth
            <a href="{{ url('/dashboard') }}" class="text-sm text-gray-700 underline">Dashboard</a>
        @else
            <a href="{{ route('login') }}" class="text-sm text-gray-700 underline">Login</a>

            @if (Route::has('register'))
                <a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 underline">Register</a>
            @endif
        @endif
    </div>
@endif

On en profite pour voir les opérateurs conditionnels de Blade.

Avec un @if on vérifie si dans les routes (Route) on a (has) une route login et si c’est le cas on vérifie si l’utilisateur est authentifié avec @auth, si c’est le cas on affiche un lien vers le dashboard et si ce n’est pas le cas (@else) on affiche les deux liens de l’authentification.

Si on clique sur Register on appelle une méthode de Fortify :

Remarquez au passage la déclaration du middleware guest dans les routes de Fortify (il faut aller voir ça dans le dossier vendor) :

Route::get('/register', [RegisteredUserController::class, 'create'])
    ->middleware(['guest'])
    ->name('register');

En effet si on est authentifié c’est qu’on n’a pas besoin de s’enregistrer !

La vue register

On va donc chercher cette vue :

Elle a cet aspect :

Cette vue utilise le composant resources/views/layouts/guest.blade.php. Je n’ai pas encore parlé des composants de Blade dans ce cours, c’est une autre manière d’organiser le code des vues, la documentation est ici. Jetstream fait un usage intensif de ces composants. Je ne vais pas entrer dans le détail du code.

La validation

La validation pour l’enregistrement est présente dans l’action app/Actions/Fortify/CreateNewUser :

public function create(array $input)
{
    Validator::make($input, [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => $this->passwordRules(),
    ])->validate();

    ...
}

De cette manière elle est facile à modifier si vous devez changer des règles ou ajouter un champ.

On peut vérifier que ça fonctionne :

Je rappelle qu’on peut ajouter la langue française avec ce complément.

La création de l’utilisateur

La création de l’utilisateur se fait aussi dans app/Actions/Fortify/CreateNewUser :

public function create(array $input)
{
    ...

    return User::create([
        'name' => $input['name'],
        'email' => $input['email'],
        'password' => Hash::make($input['password']),
    ]);
}

On utilise la méthode create d’Eloquent comme on l’a déjà vu au chapitre précédent. Vous trouvez dans le modèle App\Models\User.php la propriété $fillable renseignée :

protected $fillable = [
    'name', 'email', 'password',
];

Donc là encore il est facile d’apporter des modifications si nécessaire.

Une fois que l’utilisateur est créé dans la base il est automatiquement connecté et est redirigé sur le dashboard :

La connexion d’un utilisateur

Pour se connecter, un utilisateur doit cliquer sur le lien login de la page d’accueil :

Si vous venez juste d’enregistrer un utilisateur il faudra le déconnecter avant d’avoir accès au formulaire de login.

Si on clique sur Login on appelle une méthode de Fortify :

Remarquez aussi la déclaration du middleware guest dans les routes de Fortify (il faut aller voir ça dans le dossier vendor) :

Route::get('/login', [AuthenticatedSessionController::class, 'create'])
    ->middleware(['guest'])
    ->name('login');

La vue login

On va donc chercher cette vue :

Elle a cet aspect :

Cette vue utilise aussi le composant resources/views/layouts/guest.blade.php.

La validation

La validation pour la connexion est présente dans la requête de formulaire fortify\src\Http\Requests\LoginRequest :

public function rules()
{
    return [
        Fortify::username() => 'required|string',
        'password' => 'required|string',
    ];
}

Dans le formulaire de connexion il y a une case à cocher « se rappeler de moi » (remember me) :

Si on coche cette case on reste connecté indéfiniment jusqu’à ce qu’on se déconnecte intentionnellement. Pour que ça fonctionne il faut une colonne remember_token dans la table users :

Il se trouve que cette colonne est prévue dans la migration de base pour la table.

La redirection

Lorsqu’on installe Fortify on a la création d’un fichier de configuration :

On y trouve un configuration de la redirection après la connexion :

'home' => RouteServiceProvider::HOME,

Dans le provider on a le chemin de redirection :

public const HOME = '/dashboard';

 

Par défaut on redirige sur le dashboard. Donc pour changer la redirection par défaut il suffit de créer cette propriété.

En fait ce que j’ai écrit ci-dessus est incomplet, il faut préciser quelque chose. On peut arriver sur le formulaire de connexion parce qu’on clique sur le lien correspondant, mais on peut aussi y arriver par l’action du middleware auth. En effet si on veut atteindre une page pour laquelle il faut être authentifié le middleware auth va nous bloquer et nous rediriger sur le formulaire de connexion. Dans ce cas ce qui serait bien serait, à l’issue de la connexion, d’aller directement sur la page qu’on désirait atteindre au départ.Sans entrer dans les détails la route désirée est mémorisée dans la session.

La déconnexion d’un utilisateur

L’utilisateur dispose d’un lien pour se déconnecter :

Si on clique sur Logout on appelle une méthode de Fortify :

Remarquez que la méthode est POST. Du coup la vue (navigation-dropdown) utilise un formulaire et une soumission en Javascript :

<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
    @csrf

    <x-jet-dropdown-link href="{{ route('logout') }}"
                        onclick="event.preventDefault();
                                    this.closest('form').submit();">
        {{ __('Logout') }}
    </x-jet-dropdown-link>
</form>

C’est la méthode destroy du contrôleur AuthenticatedSessionController de Fortify qui accomplit la déconnexion :

public function destroy(Request $request): LogoutResponse
{
    $this->guard->logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return app(LogoutResponse::class);
}

Si on veut changer des choses

Tout ce qu’on a vu fonctionne très bien mais évidemment il arrive souvent qu’on veuille changer des choses, faisons un petit point…

Changer l’email de connexion

Par défaut on attend un email pour se connecter, mais comment faire si on préfre utiliser par exemple le nom ? Il suffit d’aller dans le fichier de configuration de Fortify (config/fortify.php) et de modifier cette ligne de code :

'username' => 'email',

Le règles du mot de passe

Par défaut le mot de passe doit avoir au minimum 8 caractères et c’est tout ce qu’on exige de lui. Si on a envie de rendre les mots de passe plus sûr on peut ajouter d’autres contraintes. Ouvrez ce fichier d’action :

Vous y trouvez les règles de validation du mot de passe :

protected function passwordRules()
{
    return ['required', 'string', new Password, 'confirmed'];
}

C’est sur l’objet Password qu’on peut intervenir :

(new Password)->length(10)

// Au moins une majuscule
(new Password)->requireUppercase()

// Au moins un chiffre
(new Password)->requireNumeric()

// Au moins un caractère spécial
(new Password)->requireSpecialCharacter()

La vérification de l’email

Par défaut Jetstream n’impose pas la vérification de l’email mais on peut très facilement activer cette fonctionnalité. Regardez dans le fichier de configuration de Fortify (config/fortify.php) :

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

On trouve toutes les fonctionnalités de Jetstream. Pour le moment on n’a vu que la première, les utilisateurs peuvent s’enregistrer, se connecter et se déconnecter. Mais on voit qu’il y a d’autres possibilités, en particulier la vérification de l’email qui est par défaut commentée. Il suffit de la décommenter pour que ça fonctionne. Mais aussi que le modèle User implémente l’interface MustVerifyEmail :

class User extends Authenticatable implements MustVerifyEmail

Maintenant lorsqu’un utilisateur s’enregistre il a ce message :

Et il reçoit cet email :

Lorqu’il utilise le lien d’activation il est automatiquement connecté.

Changer la langue

On a déjà vu qu’on peut ajouter le français à Laravel en utilisant Laravel-Lang. mais ça n’intervient qu’au niveau de la validation et de quelques autres éléments. Par contre ça ne change rien dans les textes des vues et des emails. Je parlerai plus loin dans ce cours de la localisation et de la manière d’intervenir dans les vues.

En résumé

  • L’authentification est totalement et simplement prise en charge par Laravel.
  • Les migrations pour l’authentification sont déjà dans l’installation de base de Laravel.
  • L’ajout du package Jetstream permet de générer les routes et les vues pour l’authentification.
  • L’installation de Jetstream entraine l’installation de Fortify qui gère le backend de l’authentification.
  • Les middlewares auth et guest permettent d’interdire les accès aux routes qui concernent uniquement les utilisateurs authentifiés ou non authentifiés.
Print Friendly, PDF & Email

4 commentaires

Leave a Reply