Exposer une API à un frontend découplé demande une discipline de sécurité constante. Les frameworks comme Laravel, Django et NestJS fournissent de nombreuses briques, mais aucune configuration par défaut ne remplace une architecture correctement pensée.
Voici les règles que j’applique en priorité.
1. Authentification
Une API doit clairement distinguer authentification et autorisation.
L’authentification répond à :
Qui êtes-vous ?
L’autorisation répond à :
Avez-vous le droit de faire cette opération ?
Un utilisateur authentifié ne doit donc pas automatiquement pouvoir accéder à toutes les ressources.
JWT ou cookies HTTP-Only ?
Les deux approches peuvent être pertinentes selon l’architecture.
Pour une application web contrôlée par le même écosystème, les cookies HttpOnly, Secure et correctement configurés peuvent être très pratiques.
Pour des architectures API consommées par plusieurs clients, les tokens peuvent être appropriés.
Dans tous les cas, je cherche surtout à éviter une gestion fragile des secrets côté navigateur.
2. CORS strict
Le CORS n’est pas un mécanisme d’authentification.
Il contrôle quelles origines peuvent effectuer certaines requêtes depuis un navigateur.
Une configuration de production doit éviter le réflexe :
Access-Control-Allow-Origin: *
lorsque l’API manipule des données privées ou utilise des credentials.
Je préfère déclarer explicitement les origines autorisées.
3. Validation des entrées
Tout payload externe doit être considéré comme non fiable.
Je valide :
- types ;
- tailles ;
- formats ;
- enums ;
- relations ;
- permissions.
Exemple conceptuel :
const CreateUser = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
});
La validation doit avoir lieu côté serveur même si le frontend possède déjà un schéma.
4. Rate limiting
Les endpoints sensibles sont particulièrement importants :
- login ;
- reset password ;
- OTP ;
- recherche coûteuse ;
- upload ;
- génération de ressources ;
- webhooks publics.
Le rate limiting limite les abus et protège les ressources du serveur.
5. SQL et ORM
Un ORM ne signifie pas qu’une application est automatiquement protégée.
Je préfère utiliser les mécanismes paramétrés des ORM et éviter de concaténer des entrées utilisateur dans des requêtes SQL.
Pour les requêtes SQL brutes, les paramètres doivent être séparés des données.
6. Autorisation au niveau des ressources
Une API vulnérable peut avoir une authentification correcte mais une autorisation incorrecte.
Par exemple :
GET /api/orders/8421
Le serveur ne doit pas seulement vérifier que l’utilisateur est connecté. Il doit vérifier qu’il peut accéder à la commande 8421.
C’est une distinction fondamentale.
7. Secrets et logs
Je ne mets jamais :
- clés API ;
- mots de passe ;
- tokens ;
- secrets JWT ;
dans Git.
Et je fais attention aux logs. Un log de debug trop détaillé peut devenir une fuite de données.
8. Défense en profondeur
Ma stratégie n’est jamais :
« J’ai JWT donc mon API est sécurisée. »
Je combine :
Authentication
↓
Authorization
↓
Validation
↓
Rate limiting
↓
Database constraints
↓
Monitoring
Chaque couche réduit l’impact d’une erreur dans une autre.
9. Production
Avant de considérer une API comme prête, je vérifie notamment :
- HTTPS ;
- headers de sécurité ;
- CORS ;
- expiration des sessions/tokens ;
- permissions ;
- validation serveur ;
- limites de taille ;
- gestion des erreurs ;
- sauvegardes ;
- monitoring.
La sécurité n’est pas une fonctionnalité que l’on ajoute à la fin. Elle doit faire partie de l’architecture dès le départ.
