Développement Web

Authentification avec Laravel Sanctum : tokens API et SPA — tutoriel pas à pas

8 min de lecture

📍 Guide de référence de cette série : Laravel 11 et PHP 8.4 : installer l’environnement et maîtriser la nouvelle architecture

L’authentification est la couche de sécurité la plus critique de toute application web. Laravel 11 propose Laravel Sanctum pour les APIs et les SPAs — une solution légère qui émet des tokens API portables ou s’appuie sur les cookies de session pour les applications JavaScript single-page. Ce tutoriel couvre les deux modes d’utilisation de Sanctum : les tokens API pour les clients mobiles et les services tiers, et l’authentification SPA pour les frontends Vue.js, React ou Nuxt servis sur le même domaine ou un sous-domaine.

Prérequis

Étape 1 — Installer Sanctum et préparer la base

Dans Laravel 11, Sanctum est installé via la commande install:api qui configure en même temps le routing API. Si vous avez déjà exécuté cette commande lors de la création de votre API, Sanctum est déjà installé. Dans le cas contraire, lancez-la maintenant :

php artisan install:api

Cette commande installe le package laravel/sanctum via Composer, publie le fichier de configuration config/sanctum.php, publie et exécute la migration qui crée la table personal_access_tokens. Cette table stocke tous les tokens émis : leur nom, le hash SHA-256 de leur valeur, les abilities (permissions) associées, et la date d’expiration optionnelle. Vérifiez que la migration a bien créé la table :

php artisan migrate:status

Vous devez voir create_personal_access_tokens_table avec le statut Ran. Si ce n’est pas le cas, relancez php artisan migrate.

Étape 2 — Configurer le modèle User avec HasApiTokens

Pour qu’un modèle puisse émettre et valider des tokens Sanctum, il doit utiliser le trait HasApiTokens. Ce trait ajoute les méthodes createToken(), tokens(), tokenCan() et currentAccessToken() au modèle. Ouvrez app/Models/User.php et ajoutez le trait :

<?php
// app/Models/User.php

namespace AppModels;

use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateFoundationAuthUser as Authenticatable;
use IlluminateNotificationsNotifiable;
use LaravelSanctumHasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

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

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'password'          => 'hashed', // Hash automatique via cast (Laravel 10+)
    ];
}

Le cast 'password' => 'hashed' est une fonctionnalité introduite dans Laravel 10 qui hash automatiquement le mot de passe lors de l’affectation — plus besoin de bcrypt() ou Hash::make() manuel dans les contrôleurs. Assurez-vous que ce cast est présent dans votre modèle User.

Étape 3 — Créer le contrôleur d’authentification

Le flux d’authentification par token Sanctum comporte trois opérations : l’inscription (register), la connexion (login) et la déconnexion (logout). Créez un contrôleur dédié :

php artisan make:controller Auth/AuthController
<?php
// app/Http/Controllers/Auth/AuthController.php

namespace AppHttpControllersAuth;

use AppHttpControllersController;
use AppModelsUser;
use IlluminateHttpJsonResponse;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use IlluminateValidationValidationException;

class AuthController extends Controller
{
    // POST /api/register
    public function register(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name'     => ['required', 'string', 'max:255'],
            'email'    => ['required', 'email', 'unique:users,email'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);

        $user  = User::create($validated);
        $token = $user->createToken('api-token')->plainTextToken;

        return response()->json([
            'user'  => $user,
            'token' => $token,
        ], 201);
    }

    // POST /api/login
    public function login(Request $request): JsonResponse
    {
        $request->validate([
            'email'       => ['required', 'email'],
            'password'    => ['required', 'string'],
            'device_name' => ['required', 'string', 'max:100'],
        ]);

        if (! Auth::attempt($request->only('email', 'password'))) {
            throw ValidationException::withMessages([
                'email' => ['Les identifiants fournis sont incorrects.'],
            ]);
        }

        $user  = Auth::user();
        $token = $user->createToken($request->device_name)->plainTextToken;

        return response()->json(['token' => $token]);
    }

    // POST /api/logout
    public function logout(Request $request): JsonResponse
    {
        // Révoquer uniquement le token courant (pas tous les tokens)
        $request->user()->currentAccessToken()->delete();

        return response()->json(['message' => 'Déconnecté avec succès.']);
    }
}

La méthode login demande un champ device_name — c’est la bonne pratique Sanctum pour nommer les tokens par appareil ("iPhone 15 de Moussa", "Chrome Windows", etc.). Cela permet à l’utilisateur de révoquer les tokens par appareil depuis son espace personnel. Auth::attempt() vérifie les identifiants ; s’ils sont invalides, on lance une ValidationException qui Laravel transforme automatiquement en réponse JSON 422 avec le champ d’erreur.

Étape 4 — Déclarer les routes d’authentification

Ouvrez routes/api.php et ajoutez les routes publiques (inscription, connexion) et la route protégée (déconnexion). La route /user générée par défaut par Sanctum peut servir à vérifier le token courant :

<?php
// routes/api.php

use AppHttpControllersAuthAuthController;
use IlluminateHttpRequest;
use IlluminateSupportFacadesRoute;

// Routes publiques — accessibles sans token
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

// Routes protégées — requièrent un token Sanctum valide
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/user', fn (Request $request) => $request->user());
});

Étape 5 — Tester les endpoints d’authentification

Démarrez le serveur de développement (php artisan serve) et testez le flux complet avec curl. Remplacez les valeurs par vos propres données :

# 1. Inscription
curl -s -X POST http://localhost:8000/api/register   -H "Content-Type: application/json"   -H "Accept: application/json"   -d '{"name":"Mamadou Diallo","email":"mamadou@example.com","password":"secret12","password_confirmation":"secret12"}' | jq .

# Réponse attendue : {"user":{...}, "token":"1|AbCdEfGhIj..."}

# 2. Connexion et récupération du token
TOKEN=$(curl -s -X POST http://localhost:8000/api/login   -H "Content-Type: application/json"   -H "Accept: application/json"   -d '{"email":"mamadou@example.com","password":"secret12","device_name":"curl-test"}' | jq -r '.token')

echo "Token: $TOKEN"

# 3. Accéder à une route protégée
curl -s http://localhost:8000/api/user   -H "Authorization: Bearer $TOKEN"   -H "Accept: application/json" | jq .

Le signal de succès : la troisième commande retourne le JSON de l’utilisateur connecté ({"id":1,"name":"Mamadou Diallo","email":"..."}). Si vous obtenez {"message":"Unauthenticated."}, vérifiez que le header Authorization: Bearer TOKEN est bien envoyé et que le middleware auth:sanctum est appliqué à la route.

Étape 6 — Token abilities : permissions granulaires

Sanctum permet d’associer des abilities (permissions) à chaque token émis. Cela permet de créer des tokens avec des droits limités — utile pour les intégrations tierces ou les tokens d’accès à usage unique. Voici comment émettre un token avec abilities et vérifier ces permissions dans un contrôleur :

<?php
// Émettre un token avec abilities spécifiques
$token = $user->createToken('mobile-app', ['articles:read', 'articles:create'])->plainTextToken;

// Vérifier les abilities dans un contrôleur ou middleware
public function store(Request $request): JsonResponse
{
    if (! $request->user()->tokenCan('articles:create')) {
        return response()->json(['error' => 'Permission insuffisante.'], 403);
    }
    // ...
}

// Émettre un token avec expiration (ex: 24h)
$token = $user->createToken(
    'temp-access',
    ['reports:read'],
    now()->addHours(24)
)->plainTextToken;

// Planifier la suppression des tokens expirés (routes/console.php)
Schedule::command('sanctum:prune-expired --hours=24')->daily();

La commande sanctum:prune-expired supprime de la table personal_access_tokens les tokens dont la date d’expiration est dépassée. Sans cette commande planifiée, la table grossit indéfiniment. Ajoutez-la dans routes/console.php avec une fréquence quotidienne.

Erreurs fréquentes

Erreur Cause Solution
Unauthenticated. malgré un token valide Header Accept: application/json absent → Laravel retourne une redirection HTML au lieu d’un JSON 401 Toujours envoyer Accept: application/json dans les requêtes API
Token présent mais abilities incorrectes Token créé sans abilities ou avec un nom incorrect Utiliser tokenCan('ability-name') et vérifier les abilities lors de l’émission
Table personal_access_tokens absente php artisan install:api non exécuté ou migration non lancée Lancer php artisan migrate après install:api
Mot de passe non hashé en base Cast 'password' => 'hashed' absent du modèle User Ajouter le cast ou utiliser Hash::make() explicitement

FAQ

Quelle différence entre Sanctum et Passport ?
Passport implémente le protocole OAuth2 complet (authorization code, client credentials, etc.) — nécessaire pour des intégrations tiers complexes comme « Se connecter avec MonApp ». Sanctum est plus simple et couvre 90% des besoins : tokens API et SPA sur le même domaine. Choisissez Passport uniquement si vous avez besoin d’OAuth2 complet.

Comment révoquer tous les tokens d’un utilisateur ?
$user->tokens()->delete() révoque tous les tokens. Pour révoquer seulement le token courant : $request->user()->currentAccessToken()->delete().

Peut-on limiter le nombre de tokens actifs par utilisateur ?
Pas nativement dans Sanctum. Il faut implémenter cette logique manuellement : avant d’émettre un nouveau token, compter les tokens existants et en supprimer les plus anciens si la limite est atteinte.

Tutoriels frères dans cette série

Pour aller plus loin

Service ITSkillsCenter

Site ou application web sur mesure

Conception Pro + Nom de domaine 1 an + Hébergement 1 an + Formation + Support 6 mois. Accès et code livrés. À partir de 350 000 FCFA.

Demander un devis
Publicité