Module 9 – Authentification et autorisation
Niveau 5.2 – Laravel Inertia (React & Vue)
Objectif
À la fin de ce module vous saurez :
- Protéger les routes Laravel avec le middleware auth (et éventuellement guest) pour les pages Inertia.
- Créer des pages de connexion et d’inscription (formulaires Inertia) qui envoient les données à Laravel et gèrent les erreurs de validation.
- Exposer l’utilisateur connecté et des permissions via les shared data (déjà vu au module 7) et les utiliser dans les layouts (menu, déconnexion).
- Gérer la déconnexion (lien ou bouton qui envoie POST vers la route logout).
Nous supposons que Laravel utilise le système d’auth classique (session, Auth facade). Nous donnons des exemples complets : routes, contrôleurs, pages React et Vue pour Login et Register, et layout avec user + logout.
Rappel : auth Laravel + Inertia
Avec Inertia, il n’y a pas de token JWT à gérer côté front : Laravel utilise la session (cookie). Lorsque l’utilisateur se connecte (formulaire POST vers /login), Laravel valide les identifiants, crée la session, et redirige (ex. vers /dashboard). Les requêtes Inertia suivantes envoient automatiquement le cookie de session ; Laravel reconnaît l’utilisateur. Les routes protégées utilisent le middleware auth : si l’utilisateur n’est pas connecté, Laravel renvoie une redirect vers la page de login (ou une 401), et Inertia suit cette redirect. Côté front, vous n’avez rien de spécial à faire pour « envoyer le token » : les cookies sont gérés par le navigateur.
Routes et middleware
Exemple de découpage :
- Routes publiques :
/,/login,/register(pour les invités uniquement : middleware guest). - Routes protégées :
/dashboard,/users, etc. (middleware auth).
// routes/web.php
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\UserController;
// Invités uniquement
Route::middleware('guest')->group(function () {
Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
Route::post('login', [LoginController::class, 'login']);
Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name('register');
Route::post('register', [RegisterController::class, 'register']);
});
// Déconnexion (authentifié)
Route::post('logout', [LoginController::class, 'logout'])->name('logout')->middleware('auth');
// Zone authentifiée
Route::middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
Route::get('/users', [UserController::class, 'index'])->name('users.index');
// ...
});
- guest : si l’utilisateur est déjà connecté et tente d’accéder à /login ou /register, Laravel le redirige vers /dashboard (ou une URL configurée).
- auth : si l’utilisateur n’est pas connecté et tente d’accéder à /dashboard ou /users, Laravel redirige vers /login.
Côté Laravel – Login (contrôleur + validation)
app/Http/Controllers/Auth/LoginController.php (exemple avec formulaire email + password) :
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
class LoginController extends Controller
{
public function showLoginForm()
{
return Inertia::render('Auth/Login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
], [
'email.required' => 'L’email est obligatoire.',
'password.required' => 'Le mot de passe est obligatoire.',
]);
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate();
return redirect()->intended(route('dashboard'));
}
return back()->withErrors([
'email' => 'Identifiants incorrects.',
])->onlyInput('email');
}
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login');
}
}
- showLoginForm : affiche la page Inertia Auth/Login (formulaire).
- login : valide email et password, tente Auth::attempt(). En cas de succès : regenerate la session (sécurité), redirect()->intended() (vers la page demandée avant la redirection vers login, ou dashboard par défaut). En cas d’échec : back()->withErrors() ; Inertia recevra ces erreurs dans useForm().errors.
- logout : déconnexion, invalidation de la session, redirect vers login.
Page Login – React
resources/js/Pages/Auth/Login.jsx :
import React from 'react';
import { useForm } from '@inertiajs/react';
export default function Login() {
const { data, setData, post, processing, errors } = useForm({
email: '',
password: '',
remember: false,
});
const handleSubmit = (e) => {
e.preventDefault();
post(route('login'));
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="w-full max-w-md p-8 bg-white rounded-lg shadow">
<h1 className="text-xl font-semibold mb-6">Connexion</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email
</label>
<input
id="email"
type="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
autoComplete="email"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email}</p>
)}
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Mot de passe
</label>
<input
id="password"
type="password"
value={data.password}
onChange={(e) => setData('password', e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
autoComplete="current-password"
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600">{errors.password}</p>
)}
</div>
<div className="flex items-center">
<input
id="remember"
type="checkbox"
checked={data.remember}
onChange={(e) => setData('remember', e.target.checked)}
className="rounded border-gray-300"
/>
<label htmlFor="remember" className="ml-2 text-sm text-gray-600">
Se souvenir de moi
</label>
</div>
<button
type="submit"
disabled={processing}
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
>
{processing ? 'Connexion...' : 'Se connecter'}
</button>
</form>
</div>
</div>
);
}
- post(route('login')) envoie les champs data vers la route POST /login. Si la validation échoue ou si attempt échoue, Laravel redirige back avec errors ; Inertia les expose et on les affiche sous les champs.
Page Login – Vue
resources/js/Pages/Auth/Login.vue :
<template>
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="w-full max-w-md p-8 bg-white rounded-lg shadow">
<h1 class="text-xl font-semibold mb-6">Connexion</h1>
<form @submit.prevent="form.post(route('login'))" class="space-y-4">
<div>
<label for="email" class="block text-sm font-medium text-gray-700">
Email
</label>
<input
id="email"
v-model="form.email"
type="email"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
autocomplete="email"
/>
<p v-if="form.errors.email" class="mt-1 text-sm text-red-600">
{{ form.errors.email }}
</p>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">
Mot de passe
</label>
<input
id="password"
v-model="form.password"
type="password"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
autocomplete="current-password"
/>
<p v-if="form.errors.password" class="mt-1 text-sm text-red-600">
{{ form.errors.password }}
</p>
</div>
<div class="flex items-center">
<input
id="remember"
v-model="form.remember"
type="checkbox"
class="rounded border-gray-300"
/>
<label for="remember" class="ml-2 text-sm text-gray-600">
Se souvenir de moi
</label>
</div>
<button
type="submit"
:disabled="form.processing"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50"
>
{{ form.processing ? 'Connexion...' : 'Se connecter' }}
</button>
</form>
</div>
</div>
</template>
<script setup>
import { useForm } from '@inertiajs/vue3';
const form = useForm({
email: '',
password: '',
remember: false,
});
</script>
Inscription (Register)
Même principe : une page Auth/Register avec useForm (name, email, password, password_confirmation), soumission en POST vers route('register'). Côté Laravel : validation (email unique, password confirmé, règles de mot de passe), création du compte (User::create + hash du mot de passe), puis Auth::login($user) et redirect()->route('dashboard'). Nous ne dupliquons pas tout le code ici ; la structure est identique à Login (formulaire Inertia + contrôleur Laravel qui valide et redirige).
Déconnexion (Link avec method="post")
La déconnexion est en général une route POST (pour invalider le token CSRF et la session). Avec Inertia, utilisez Link avec method="post" et as="button" (pour un bouton) :
React :
<Link href={route('logout')} method="post" as="button">
Déconnexion
</Link>
Vue :
<Link :href="route('logout')" method="post" as="button">
Déconnexion
</Link>
Inertia enverra une requête POST vers /logout ; Laravel déconnecte et redirige vers login.
Autorisation (policies, can)
Pour masquer des liens ou des boutons selon les droits (ex. « Modifier » seulement pour les admins), exposez les permissions dans share() (voir module 7), par exemple auth.can.manageUsers. Côté React/Vue, affichez le lien seulement si props.auth.can.manageUsers est true. Pour protéger réellement les actions, utilisez les policies Laravel dans les contrôleurs ($this->authorize('update', $user)) ; le front ne fait que de l’UX, le backend refuse l’accès si non autorisé.
À retenir
- Routes : guest pour login/register, auth pour dashboard et zone privée. logout en POST, middleware auth.
- Login : page Inertia avec useForm (email, password, remember) ; post(route('login')) ; afficher errors en cas d’échec.
- Logout : Link avec method="post" et href vers route('logout').
- Utilisateur et permissions : partagés via share() ; lecture avec usePage().props.auth dans le layout.
- Pas de JWT : session Laravel + cookies ; Inertia envoie les cookies automatiquement.
Dans le prochain module, nous verrons Eloquent, les listes paginées et les filtres (recherche, tri) avec Inertia.