Aller au contenu

Le back-office : l'authentification du kit, inscription fermée

Temps 1 · séance 3 · ~35 min

Les slides du chapitre (PDF)

  • Repérer ce que le kit fournit déjà pour se connecter et gérer son compte.
  • Créer l'utilisateur unique du portfolio avec le seeder.
  • Fermer l'inscription et réparer les fichiers qui en dépendaient.
  • Protéger les adresses /admin avec le middleware auth.
  • Lire l'utilisateur connecté dans la prop partagée auth.user.

1. Ce que le kit sait déjà faire

Votre portfolio a deux parties. Les pages publiques montrent vos projets à tout le monde. Le back-office, sous /admin, sert à les encoder, et vous seul y entrez. Pour cela, le site doit savoir qui envoie chaque requête. Vérifier l'identité d'une personne, par exemple avec une adresse e-mail et un mot de passe, s'appelle l'authentification.

Vous n'avez rien à écrire pour l'authentification : le kit l'a installée au chapitre 1.04. Côté serveur, c'est Fortify, un paquet de Laravel qui fournit les routes et les contrôleurs. Côté navigateur, ce sont les pages Vue des dossiers resources/js/pages/auth/ et resources/js/pages/settings/.

Ce que le kit fournit Adresse Ce que ça fait
Connexion /login vérifie l'adresse e-mail et le mot de passe, puis ouvre une session
Mot de passe oublié /forgot-password envoie par e-mail un lien pour choisir un nouveau mot de passe
Profil /settings/profile modifie votre nom et votre adresse e-mail
Deux facteurs /settings/security demande, en plus du mot de passe, un code temporaire donné par une application de votre téléphone
Passkeys /settings/security enregistre une clé d'accès dans votre téléphone ou votre ordinateur, qui remplace le mot de passe

Fortify ajoute ses routes à celles de routes/web.php. Vous les voyez avec route:list, ici limité à la connexion :

php artisan route:list --path=login
 GET|HEAD login .. login › Laravel\Fortify › AuthenticatedSessionController@create
 POST login .. login.store › Laravel\Fortify › AuthenticatedSessionController@store
 POST passkeys/login .. passkey.login › Laravel\Passkeys › PasskeyLoginController@store
 GET|HEAD passkeys/login/options .. passkey.login-options › Laravel\Passkeys › PasskeyLoginController@index

 Showing [4] routes

Les contrôleurs de ces routes vivent dans le dossier vendor/, et vous ne les modifiez pas. Vous réglez Fortify par un fichier de configuration, config/fortify.php, que vous ouvrirez à la section 3.

2. Un seul utilisateur : vous

Le portfolio n'a qu'un auteur. Votre diagramme de classes du chapitre 1.05 le disait déjà : une classe User, un seul utilisateur. Le seeder crée cet utilisateur.

Depuis l'installation, database/seeders/DatabaseSeeder.php crée un utilisateur de test, Test User, avec l'adresse test@example.com. Remplacez son nom et son adresse par les vôtres :

User::factory()->create([
    'name' => 'Nora Benali',
    'email' => 'admin@example.com',
]);

$this->call(ProjectSeeder::class);

Écrivez votre nom à la place de celui de l'exemple. L'adresse sert d'identifiant de connexion, et une adresse inventée comme admin@example.com suffit en local.

Le mot de passe vient de UserFactory, la fabrique du kit. Elle donne à chaque utilisateur le mot de passe password, chiffré avant d'être enregistré :

'password' => static::$password ??= Hash::make('password'),

La fabrique remplit aussi la colonne email_verified_at. Ce détail compte, car le modèle User du kit implémente l'interface MustVerifyEmail. Un utilisateur dont l'adresse n'est pas vérifiée serait renvoyé vers une page qui lui demande de cliquer sur un lien reçu par e-mail. Votre utilisateur seedé passe directement.

Relancez les migrations et les seeders, comme au chapitre 1.06 :

php artisan migrate:fresh --seed

Ouvrez portfolio.test/login et connectez-vous avec votre adresse et password. Avant la mise en ligne, vous choisirez un vrai mot de passe dans les réglages du compte.

3. Fermer l'inscription

Le kit laisse n'importe qui créer un compte à l'adresse /register. Sur votre portfolio, un visiteur inscrit entrerait dans votre back-office. L'inscription doit donc disparaître.

Dans config/fortify.php, le tableau features liste les fonctions actives de Fortify. Mettez la première ligne en commentaire :

'features' => [
    // Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
    // … twoFactorAuthentication et passkeys inchangés
],

Fortify ne déclare plus les routes register et register.store, et route:list ne trouve plus rien à cette adresse :

php artisan route:list --path=register
 ERROR Your application doesn't have any routes matching the given criteria.

portfolio.test/register répond maintenant par une erreur 404. Comme toute adresse inconnue depuis le chapitre 1.08, elle s'affiche avec votre page ErrorPage, dans votre layout public. Voici la réponse, coupée :

HTTP/1.0 404 Not Found
…
{"component":"ErrorPage","props":{"errors":{},"name":"Nora Benali","auth":{"user":null},"sidebarOpen":true,"status":404},"url":"\/register",…}

Trois fichiers importent encore register

Wayfinder ne génère plus de fonction register, puisque la route n'existe plus. Le serveur de Vite ne le remarque pas tout de suite, car il surveille routes/ et pas config/. Arrêtez composer run dev avec Ctrl+C, puis relancez-le, pour que Wayfinder régénère ses fichiers.

Une ligne mise en commentaire dans config/fortify.php retire la route /register ; Wayfinder ne génère alors plus la fonction register ; les trois fichiers Register.vue, Welcome.vue et Login.vue, qui l'importent encore, font échouer npm run build

Trois fichiers du kit importent encore la fonction disparue. npm run build, la compilation du site pour la mise en ligne, les signale l'un après l'autre. La première erreur vise la page d'inscription :

[UNLOADABLE_DEPENDENCY] Could not load ../../../../../resources/js/routes/register
    ╭─[ resources/js/pages/auth/Register.vue?vue&type=script&setup=true&lang.ts:18:23 ]
 18 │ import { store } from "@/routes/register";
    │                       ─────────┬─────────
    │                                ╰─────────── No such file or directory (os error 2)

Les deux suivantes visent l'accueil du kit et la page de connexion :

[MISSING_EXPORT] "register" is not exported by "resources/js/routes/index.ts".
   ╭─[ resources/js/pages/Welcome.vue?vue&type=script&setup=true&lang.ts:8:10 ]
 8 │ import { register } from "@/routes";
[MISSING_EXPORT] "register" is not exported by "resources/js/routes/index.ts".
    ╭─[ resources/js/pages/auth/Login.vue?vue&type=script&setup=true&lang.ts:22:10 ]
 22 │ import { register } from "@/routes";

Faites les trois corrections.

  1. Supprimez resources/js/pages/auth/Register.vue. Plus aucune route ne l'affiche.
  2. Supprimez resources/js/pages/Welcome.vue, que votre page Home a remplacée au chapitre 1.04. Depuis le chapitre 1.07, le résolveur de resources/js/app.ts ne la nomme plus.
  3. Dans resources/js/pages/auth/Login.vue, retirez la ligne import { register } from '@/routes'; et le lien « Sign up » en bas du formulaire :
        <div class="text-center text-sm text-muted-foreground">
            Don't have an account?
            <TextLink :href="register()" :tabindex="5">Sign up</TextLink>
        </div>

Relancez npm run build dans un autre terminal. La compilation se termine cette fois par une ligne ✓ built in, suivie de sa durée.

Les tests de l'inscription

Le kit contient deux tests de l'inscription, dans tests/Feature/Auth/RegistrationTest.php. Chacun commence par $this->skipUnlessFortifyHas(Features::registration());, qui l'ignore quand l'inscription est fermée :

php artisan test tests/Feature/Auth/RegistrationTest.php
   WARN  Tests\Feature\Auth\RegistrationTest
  - registration screen can be rendered → Fortify feature [registration] is not enabled.
  - new users can register → Fortify feature [registration] is not enabled.

  Tests:    2 skipped (0 assertions)

Les deux tests sont ignorés, et aucun n'échoue. Vous pouvez garder le fichier.

4. Le groupe /admin

Toutes les pages du back-office commencent par /admin et demandent une connexion. Laravel règle ces deux points pour un groupe de routes entier.

Le middleware auth

Un middleware est une classe que Laravel fait passer à la requête avant le contrôleur. Il peut la laisser continuer, ou répondre lui-même à la place du contrôleur. Le middleware auth regarde si la session contient un utilisateur connecté. Si ce n'est pas le cas, il répond par une redirection vers /login. Le middleware verified vérifie en plus que l'adresse e-mail est vérifiée. La route /dashboard du kit utilise déjà les deux.

La même requête GET /admin/projets, deux fois. Pour un visiteur, le middleware auth ne trouve pas d'utilisateur dans la session et répond lui-même 302 vers /login : le contrôleur n'est pas appelé. Pour vous, connecté, le middleware laisse passer la requête jusqu'au contrôleur, qui renvoie la page Projets

Ajoutez le groupe à la fin de routes/web.php, avec use App\Http\Controllers\Admin; en tête du fichier :

Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () {
    Route::resource('projets', Admin\ProjectController::class)
        ->except('show')
        ->names('projects')
        ->parameters(['projets' => 'project']);
});

Lisez la première ligne de gauche à droite.

  1. middleware(['auth', 'verified']) fait passer chaque route du groupe par les deux middlewares.
  2. prefix('admin') ajoute admin/ devant chaque adresse du groupe.
  3. name('admin.') ajoute admin. devant chaque nom de route.
  4. group(function () { … }) contient les routes concernées.

La première ligne du groupe en trois morceaux : middleware, avec auth et verified, décide qui peut entrer, prefix('admin') donne le début de l'adresse, name('admin.') donne le début du nom. Pour la liste des projets : l'adresse admin/projets et le nom admin.projects.index

Vous connaissez Route::resource() depuis 5XCOS : il déclare d'un coup les routes d'une ressource. except('show') retire la fiche, car la fiche publique existe déjà. names('projects') garde un nom anglais aux routes, alors que l'adresse est en français. parameters(['projets' => 'project']) nomme le paramètre {project}, comme l'argument Project $project des futures méthodes du contrôleur. Sans cette ligne, le paramètre s'appellerait {projet}, et la liaison de modèle ne se ferait pas.

Deux adresses de la ressource restent en anglais, create et edit. Comme au chapitre 16 de 5XCOS, Route::resourceVerbs() les traduit pour toutes les ressources du site. Dans app/Providers/AppServiceProvider.php, importez Illuminate\Support\Facades\Route, puis ajoutez cette ligne dans boot() :

Route::resourceVerbs(['create' => 'nouveau', 'edit' => 'modifier']);

Créez le contrôleur dans un sous-dossier Admin, pour le distinguer du ProjectController des pages publiques :

php artisan make:controller Admin/ProjectController

Les six routes de la ressource apparaissent dans route:list :

php artisan route:list --path=admin
 GET|HEAD admin/projets .. admin.projects.index › Admin\ProjectController@index
 POST admin/projets .. admin.projects.store › Admin\ProjectController@store
 GET|HEAD admin/projets/nouveau .. admin.projects.create › Admin\ProjectController@create
 PUT|PATCH admin/projets/{project} .. admin.projects.update › Admin\ProjectController@update
 DELETE admin/projets/{project} .. admin.projects.destroy › Admin\ProjectController@destroy
 GET|HEAD admin/projets/{project}/modifier .. admin.projects.edit › Admin\ProjectController@edit

Aujourd'hui, le contrôleur n'a que index(). Les méthodes create() et store() arrivent au chapitre 1.12, puis edit(), update() et destroy() au chapitre 1.13.

La liste côté administration

Écrivez index() dans app/Http/Controllers/Admin/ProjectController.php, avec use App\Models\Project;, use Inertia\Inertia; et use Inertia\Response; en tête :

public function index(): Response
{
    return Inertia::render('admin/projects/Index', [
        'projects' => Project::orderBy('position')->get(['id', 'title', 'slug']),
    ]);
}

get(['id', 'title', 'slug']) ne lit que trois colonnes, car la règle des props explicites du chapitre 1.07 vaut aussi derrière une connexion. La page affichera le titre et l'adresse publique. id servira aux boutons « Modifier » et « Supprimer » du chapitre 1.13.

Créez resources/js/pages/admin/projects/Index.vue, encore brut :

<script setup>
import { Head } from '@inertiajs/vue3';

defineProps({
    projects: Array,
});
</script>

<template>
    <Head title="Projets" />

    <div class="p-4">
        <h1 class="text-xl font-semibold">Projets</h1>

        <ul>
            <li v-for="project in projects" :key="project.id">
                {{ project.title }}
            </li>
        </ul>
    </div>
</template>

Le chapitre 1.11 remplacera la liste par un tableau.

Le layout du back-office

Le layout ne demande aucune ligne de plus. Depuis le chapitre 1.07, le résolveur de resources/js/app.ts donne AppLayout aux pages dont le nom commence par admin/ :

case name === 'Dashboard' || name.startsWith('admin/'):
    return AppLayout;

AppLayout est le layout du back-office du kit : une barre latérale à gauche, un fil d'Ariane en haut, votre nom en bas à gauche. Ajoutez un lien « Projets » à sa barre latérale, dans resources/js/components/AppSidebar.vue. Ce composant du kit est écrit en TypeScript. Ajoutez deux imports et une entrée au tableau mainNavItems :

<script setup lang="ts">
import { BookOpen, FolderGit2, FolderKanban, LayoutGrid } from '@lucide/vue';
// …
import { index as adminProjectsIndex } from '@/routes/admin/projects';

const mainNavItems: NavItem[] = [
    {
        title: 'Dashboard',
        href: dashboard(),
        icon: LayoutGrid,
    },
    {
        title: 'Projets',
        href: adminProjectsIndex(),
        icon: FolderKanban,
    },
];
</script>

FolderKanban est une icône de la bibliothèque Lucide, déjà installée par le kit. Le nom index est renommé à l'import, comme projectsIndex dans PublicLayout.vue.

Arriver sur la liste après la connexion

Après la connexion, Fortify redirige vers l'adresse de la clé home de config/fortify.php. Elle vaut /dashboard, le tableau de bord vide du kit. Remplacez-la :

'home' => '/admin/projets',

Cinq tests du kit attendent encore l'ancienne redirection, dans tests/Feature/Auth/ : un dans AuthenticationTest.php, trois dans EmailVerificationTest.php et un dans VerificationNotificationTest.php. Dans chacun, remplacez route('dashboard', absolute: false) par route('admin.projects.index', absolute: false). Un sixième test contient la même ligne, dans RegistrationTest.php : il est ignoré depuis la fermeture de l'inscription, vous pouvez le laisser. Vous lancerez toute la suite de tests au chapitre 1.17.

Ce que voit un visiteur

Déconnectez-vous, puis ouvrez portfolio.test/admin/projets. Le navigateur arrive sur la page de connexion. Voici ce que le serveur a répondu à la première requête, coupé à l'en-tête utile :

HTTP/1.1 302 Found
…
Location: http://…/login

Le middleware auth n'a pas trouvé d'utilisateur dans la session. Il a répondu par une redirection 302 vers /login, sans appeler le contrôleur. Une fois connecté, vous arrivez sur /admin/projets, avec vos trois projets dans le layout du back-office.

5. La prop partagée auth.user

Le layout du back-office affiche votre nom en bas de la barre latérale. Il le lit dans auth.user, une prop partagée que le kit déclare dans app/Http/Middleware/HandleInertiaRequests.php :

public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'name' => config('app.name'),
        'auth' => [
            'user' => $request->user(),
        ],
        'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
    ];
}

$request->user() renvoie l'utilisateur connecté, ou null pour un visiteur. Chaque page reçoit cette prop, publique ou non. Une fois connecté, voici ce qu'elle contient dans la réponse de /admin/projets :

"auth": {
    "user": {
        "id": 1,
        "name": "Nora Benali",
        "email": "admin@example.com",
        "email_verified_at": "2026-10-25T12:35:36.000000Z",
        "created_at": "2026-10-25T12:35:36.000000Z",
        "updated_at": "2026-10-25T12:35:36.000000Z",
        "two_factor_confirmed_at": null
    }
}

Le kit envoie ici le modèle User entier, à l'inverse de la règle du chapitre 1.07. Seules les colonnes listées dans l'attribut #[Hidden] du modèle sont retirées :

#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]

Le mot de passe chiffré et les secrets des deux facteurs ne partent donc jamais vers le navigateur. Les colonnes qui restent sont vos propres données, envoyées à vous seul quand vous êtes connecté. Pour un visiteur, la prop vaut "auth":{"user":null}. Vous pouvez laisser le kit tel quel.

Le modèle User côté Laravel et la prop auth.user côté navigateur : id, name, email et created_at traversent ; password, two_factor_secret, two_factor_recovery_codes et remember_token sont arrêtés par l'attribut Hidden et restent sur le serveur

Dans une page en JavaScript, usePage() donne accès à toutes les props de la page, partagées comprises :

<script setup>
import { usePage } from '@inertiajs/vue3';

const page = usePage();
</script>

<template>
    <p>Connecté en tant que {{ page.props.auth.user.name }}</p>
</template>

Dans une page du back-office, auth.user ne vaut jamais null, puisque le middleware auth a refusé les visiteurs. Sur une page publique, testez d'abord sa valeur avec v-if="page.props.auth.user".

6. Pratique : le back-office protégé (10 min)

  1. Votre nom et votre adresse dans DatabaseSeeder, puis php artisan migrate:fresh --seed.
  2. L'inscription fermée dans config/fortify.php, Register.vue et Welcome.vue supprimées, le lien « Sign up » retiré de Login.vue.
  3. Le groupe /admin dans routes/web.php, Route::resourceVerbs() dans AppServiceProvider, et Admin\ProjectController avec index().
  4. La page admin/projects/Index.vue, le lien « Projets » dans la barre latérale, et home réglé sur /admin/projets.

Vérifiez dans le navigateur.

  • Sans être connecté, portfolio.test/admin/projets mène à la page de connexion.
  • portfolio.test/register affiche votre page 404, et la page de connexion n'a plus de lien « Sign up ».
  • Après la connexion, vous arrivez sur /admin/projets, avec vos trois projets, et votre nom en bas de la barre latérale.
  • npm run build se termine sans erreur.

Ce que votre portfolio contient maintenant

Un back-office sous /admin, fermé aux visiteurs, où vous seul entrez avec l'utilisateur créé par le seeder. Personne ne peut plus s'inscrire. La page /admin/projets liste vos projets dans le layout d'administration du kit.

À retenir

  • Fortify fournit la connexion, le mot de passe oublié, le profil, les deux facteurs et les passkeys, réglés par config/fortify.php.
  • Mettre Features::registration() en commentaire retire /register, et les fichiers qui importaient register doivent être corrigés.
  • Le middleware auth renvoie un visiteur vers /login avant que le contrôleur ne soit appelé.
  • prefix('admin') et name('admin.') donnent le même début d'adresse et le même début de nom à toutes les routes d'un groupe, et Route::resourceVerbs() traduit create et edit en nouveau et modifier.
  • La prop partagée auth.user contient l'utilisateur connecté ou null, et le kit y envoie le modèle entier moins les colonnes de #[Hidden].