kani / laravel-nemesis
Nemesis: CORS par token et quota par domaine pour Laravel.
Requires
- php: >=8.1
- laravel/framework: ^12.0
README
Nemesis est un package Laravel de sécurité API et son rôle est de protéger vos APIs contre les abus et les utilisations non autorisées en combinant :
- 🔑 Gestion des tokens associés à des domaines spécifiques.
- 🌍 Contrôle CORS par token (chaque token est lié à un ou plusieurs domaines).
- 📊 Quota d'appels avec suivi en base de données.
- 🚨 Blocage automatique si un token dépasse sa limite d'utilisation.
Nemesis agit comme un gardien implacable de vos endpoints.
🚀 Installation
Ajoutez le package à votre projet Laravel :
composer require kani/laravel-nemesis
Publiez les fichiers de configuration et les migrations :
php artisan vendor:publish --provider="Kani\Nemesis\NemesisServiceProvider"
php artisan migrate
⚙️ CONFIGURATION IMPORTANTE POUR LES PROJETS LARAVEL
1. Installation de l'API Laravel
IMPERATIF : Pour éviter les problèmes CORS (Cross-Origin) lors des appels depuis un frontend web, vous devez installer le système d'API Laravel :
php artisan install:api
Cette commande installe Laravel Sanctum et crée le fichier routes/api.php
nécessaire pour les routes stateless.
2. Configuration des routes API
TOUTES LES ROUTES PROTÉGÉES PAR NEMESIS DOIVENT ÊTRE DÉFINIES DANS routes/api.php
:
// routes/api.php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; Route::middleware(['nemesis'])->group(function () { Route::get('/posts', function (Request $request) { return response()->json(['data' => 'Posts list']); }); Route::get('/profile', function (Request $request) { return response()->json(['data' => 'User profile']); }); });
3. Configuration du middleware dans bootstrap/app.php
Ajoutez l'alias du middleware Nemesis dans votre fichier bootstrap/app.php
:
// bootstrap/app.php use Kani\Nemesis\Http\Middleware\NemesisMiddleware; ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'nemesis' => NemesisMiddleware::class, // autres middlewares... ]); }) // Optionnel : changement du préfixe API ->withRouting( api: __DIR__.'/../routes/api.php', apiPrefix: 'api/admin', // ou conservez 'api' par défaut // ... )
⚙️ Configuration du package
Après publication, le fichier config/nemesis.php
est disponible :
return [ 'default_max_requests' => 1000, // nombre maximum d'appels par token 'reset_period' => 'daily', // peut être 'daily', 'weekly', 'monthly' 'block_response' => [ 'message' => 'Accès refusé : quota dépassé ou domaine non autorisé.', 'status' => 429, ], ];
💡 Astuce : default_max_requests
centralise les quotas par défaut, pour ne pas répéter la valeur dans toutes les commandes.
🗄️ Migration
La migration crée une table nemesis_tokens
avec les colonnes suivantes :
id
token
(string unique)allowed_origins
(json : liste des domaines autorisés)max_requests
(integer : limite d'appels)requests_count
(integer : nombre d'appels effectués)last_request_at
(datetime : date du dernier appel)created_at
,updated_at
(timestamps)
🛡️ Middleware
Utilisation du middleware
Le middleware Nemesis est maintenant automatiquement enregistré par le package. Vous pouvez l'utiliser directement avec son alias nemesis
:
// routes/api.php Route::middleware('nemesis')->group(function () { Route::get('/posts', [PostController::class, 'index']); Route::get('/profile', [UserController::class, 'show']); });
Transmission du token
Le token peut être transmis de deux manières :
1. Via l'en-tête Authorization (Bearer)
const API_TOKEN = 'crrxnjbAucrzMl8FvlRDQHwJSmvET05ncqcX3LuO'; fetch('http://localhost:8000/api/posts', { method: 'GET', headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json', }, })
2. Via le paramètre de query string
const API_TOKEN = 'crrxnjbAucrzMl8FvlRDQHwJSmvET05ncqcX3LuO'; fetch(`http://localhost:8000/api/posts?token=${API_TOKEN}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, })
Fonctionnement du middleware
- Si l'origine (
Origin
) est identique au domaine de l'application ou absente, la requête passe sans vérification du token. - Vérifie que le token existe et n'est pas bloqué.
- Vérifie que l'origine (domaine) est autorisée (
allowed_origins
) pour ce token. - Accepte le token soit via l'en-tête
Authorization: Bearer TOKEN
, soit via le paramètre query?token=TOKEN
. - Vérifie le quota et incrémente le compteur
requests_count
. - Bloque la requête si la limite
max_requests
est atteinte. - Répond avec les headers CORS appropriés, y compris la gestion des requêtes
OPTIONS
(preflight).
💡 Astuce : si votre frontend est sur le même domaine que l'API, vous n'avez pas besoin de token pour les requêtes internes.
Flux simplifié :
┌─────────────┐
│ Requête API │
└─────┬───────┘
│
▼
┌─────────────┐
│ Récupère │
│ token │
│ (header ou │
│ query) │
└─────┬───────┘
│
▼
┌─────────────┐
│ Vérif token │
│ existe et │
│ non bloqué │
└─────┬───────┘
│
▼
┌─────────────┐
│ Vérif orig. │
│ autorisée ? │
└─────┬───────┘
│
▼
┌─────────────┐
│ Compteur │
│ incrémenté │
└─────┬───────┘
│
▼
┌─────────────┐
│ Limite OK ? │
└─────┬───────┘
│
▼
┌─────────────┐
│ Réponse API │
│ + CORS │
└─────────────┘
🔧 Commandes Artisan Nemesis
📋 Liste des Commandes Disponibles
1️⃣ nemesis:create
- Créer un nouveau token API
php artisan nemesis:create [--origins=*] [--max=] [--name=]
Paramètres :
--origins
: (Optionnel, multiple) Domaines autorisés à utiliser ce token- Format :
--origins=https://site1.com --origins=https://site2.com
- Par défaut :
['*']
(tous les domaines autorisés)
- Format :
--max
: (Optionnel) Nombre maximum de requêtes autorisées- Par défaut : valeur définie dans
config/nemesis.php
(généralement 1000)
- Par défaut : valeur définie dans
--name
: (Optionnel) Nom descriptif pour identifier le token
Exemples :
# Créer un token avec des domaines spécifiques php artisan nemesis:create --origins=https://monsite.com --origins=https://api.monsite.com # Créer un token avec une limite personnalisée php artisan nemesis:create --max=5000 --origins=https://client-site.com # Créer un token avec un nom descriptif php artisan nemesis:create --name="Token pour application mobile" # Créer un token avec tous les paramètres php artisan nemesis:create --origins=https://production.com --max=10000 --name="Token production"
Exemple de sortie :
✅ Nemesis token created successfully!
Token: AbC123XyZdef456Uvw789Ghi012Jkl345Mno678Pqr901Stu234Vwx567Yza
Max Requests: 5000
Allowed Origins: ["https://monsite.com","https://api.monsite.com"]
Name: Token pour application mobile
⚠️ Important: Save this token securely as it cannot be retrieved later!
2️⃣ nemesis:reset
- Réinitialiser les quotas d'utilisation
php artisan nemesis:reset [--token=] [--force]
Paramètres :
--token
: (Optionnel) Réinitialiser uniquement un token spécifique--force
: (Optionnel) Forcer la réinitialisation sans confirmation
Fonctionnement :
- Réinitialise le compteur
requests_count
à 0 - Remet à null la date
last_request_at
- Affecte tous les tokens si aucun token spécifique n'est précisé
Exemples :
# Réinitialiser tous les tokens (avec confirmation) php artisan nemesis:reset # Réinitialiser tous les tokens sans confirmation php artisan nemesis:reset --force # Réinitialiser un token spécifique php artisan nemesis:reset --token=AbC123XyZdef456Uvw789Ghi012Jkl345Mno678Pqr901Stu234Vwx567Yza
Exemple de sortie :
Are you sure you want to reset all token quotas? (yes/no) [no]:
> yes
✅ Successfully reset quotas for 15 tokens.
3️⃣ nemesis:block
- Bloquer un token
php artisan nemesis:block {token} [--reason=]
Paramètres :
token
: (Requis) Le token à bloquer--reason
: (Optionnel) Raison du blocage pour documentation
Fonctionnement :
- Met la valeur de
max_requests
à 0 - Le token ne peut plus être utilisé pour des appels API
- Le blocage est réversible avec la commande
nemesis:unblock
Exemples :
# Bloquer un token php artisan nemesis:block AbC123XyZdef456Uvw789Ghi012Jkl345Mno678Pqr901Stu234Vwx567Yza # Bloquer un token avec une raison php artisan nemesis:block AbC123XyZdef456Uvw789Ghi012Jkl345Mno678Pqr901Stu234Vwx567Yza --reason="Abuse detected"
Exemple de sortie :
✅ Token AbC123XyZ... has been blocked successfully.
Reason: Abuse detected
4️⃣ nemesis:unblock
- Débloquer un token
php artisan nemesis:unblock {token} [--max=] [--reason=]
Paramètres :
token
: (Requis) Le token à débloquer--max
: (Optionnel) Nouvelle limite de requêtes- Par défaut : valeur définie dans
config/nemesis.php
- Par défaut : valeur définie dans
--reason
: (Optionnel) Raison du déblocage
Exemples :
# Débloquer un token avec la limite par défaut php artisan nemesis:unblock AbC123XyZdef456Uvw789Ghi012Jkl345Mno678Pqr901Stu234Vwx567Yza # Débloquer avec une limite personnalisée php artisan nemesis:unblock AbC123XyZdef456Uvw789Ghi012Jkl345Mno678Pqr901Stu234Vwx567Yza --max=2000 # Débloquer avec une raison php artisan nemesis:unblock AbC123XyZdef456Uvw789Ghi012Jkl345Mno678Pqr901Stu234Vwx567Yza --reason="Issue resolved"
Exemple de sortie :
✅ Token AbC123XyZ... has been unblocked successfully.
New max requests: 2000
Reason: Issue resolved
5️⃣ nemesis:list
- Lister tous les tokens (Nouvelle commande)
php artisan nemesis:list [--status=]
Paramètres :
--status
: (Optionnel) Filtrer par status:active
,blocked
,all
Exemples :
# Lister tous les tokens php artisan nemesis:list # Lister seulement les tokens actifs php artisan nemesis:list --status=active # Lister seulement les tokens bloqués php artisan nemesis:list --status=blocked
Exemple de sortie :
📋 Nemesis Tokens List (Showing 3 of 15 tokens)
┌──────────────┬────────────────────────────────────────────┬─────────────┬────────┬──────────────┐
│ Name │ Token (truncated) │ Status │ Usage │ Last Used │
├──────────────┼────────────────────────────────────────────┼─────────────┼────────┼──────────────┤
│ Mobile App │ AbC123XyZ... │ ✅ Active │ 250/1K │ 2 hours ago │
│ Production │ Def456Uvw... │ ✅ Active │ 980/10K│ 5 minutes ago│
│ Test Client │ Ghi012Jkl... │ 🚫 Blocked │ 0/0 │ Never │
└──────────────┴────────────────────────────────────────────┴─────────────┴────────┴──────────────┘
🎯 Bonnes Pratiques pour les Commandes
-
Sécurité des Tokens :
- Les tokens sont affichés une seule fois à la création
- Stockez-les dans un gestionnaire de mots de passe sécurisé
- Utilisez des variables d'environnement en production
-
Gestion des Quotas :
- Planifiez la réinitialisation régulière avec
php artisan nemesis:reset
- Utilisez
php artisan schedule:run
pour l'automatisation
- Planifiez la réinitialisation régulière avec
-
Surveillance :
- Utilisez régulièrement
nemesis:list
pour monitorer l'utilisation - Bloquez rapidement les tokens suspects
- Utilisez régulièrement
-
Documentation :
- Utilisez le paramètre
--name
pour identifier clairement chaque token - Documentez les raisons de blocage/déblocage avec
--reason
- Utilisez le paramètre
⚙️ Intégration avec la Planification Laravel
Ajoutez à votre app/Console/Kernel.php
pour automatiser les tâches :
protected function schedule(Schedule $schedule) { // Réinitialiser les quotas tous les jours à minuit $schedule->command('nemesis:reset --force')->daily(); // Lister l'état des tokens chaque lundi $schedule->command('nemesis:list --status=active')->weeklyOn(1, '8:00'); }
Ces commandes offrent une gestion complète de vos tokens API Nemesis, permettant un contrôle précis de l'accès et des quotas d'utilisation.
📌 Exemple concret
Protégeons un endpoint api/posts
dans routes/api.php
:
// routes/api.php Route::middleware(['nemesis'])->get('/posts', [PostController::class, 'index']);
Requête avec token valide (header)
GET /api/posts HTTP/1.1 Host: api.monsite.com Authorization: Bearer VOTRE_TOKEN Origin: https://monsite.com
✅ Résultat : accès autorisé.
Requête avec token valide (query param)
GET /api/posts?token=VOTRE_TOKEN HTTP/1.1 Host: api.monsite.com Origin: https://monsite.com
✅ Résultat : accès autorisé.
Requête depuis un autre domaine
GET /api/posts?token=VOTRE_TOKEN HTTP/1.1 Host: api.monsite.com Origin: https://sitepirate.com
❌ Résultat : 429 Accès refusé : quota dépassé ou domaine non autorisé.
🛠️ Bonnes pratiques
- ✅ IMPERATIF : Définissez vos routes protégées dans
routes/api.php
- ✅ Utilisez un quota adapté pour chaque client.
- 🔄 Activez un reset automatique des quotas.
- 🔐 Ne communiquez jamais vos tokens côté client sans contrôle.
- 📊 Surveillez les logs Nemesis pour détecter les abus.
🔒 Sécurité
- Les tokens sont hachés en base de données.
- Les tokens ne peuvent être utilisés que depuis les origines autorisées.
- Les tentatives échouées sont loguées pour suivi.
🚨 Dépannage
Erreur CORS persistante
Solution : Vérifiez que :
- Vous avez bien exécuté
php artisan install:api
- Vos routes sont bien définies dans
routes/api.php
- Le middleware est bien aliasé dans
bootstrap/app.php
Erreur lors de la désinstallation
Class "Kani\Nemesis\NemesisServiceProvider" not found
Solution :
# Supprimer les caches rm -f bootstrap/cache/*.php php artisan config:clear # Supprimer le provider sed -i '/Kani\\Nemesis\\NemesisServiceProvider/d' config/app.php # Supprimer config publié rm -f config/nemesis.php # Vider tous les caches Laravel php artisan optimize:clear # Désinstaller le package composer remove kani/laravel-nemesis
Pour Windows
del /Q bootstrap\cache\*.php
php artisan optimize:clear
composer remove kani/laravel-nemesis
👤 Auteur
Développé par André Kani — Inspiré de la justice implacable de Némésis.
📜 Licence
MIT. Libre d'utilisation et de modification.