20 KiB
📚 Documentation Complète API mcV3
Table des matières
- Architecture générale
- Authentification
- Endpoints API
- Gestion des clés API
- Rôles et permissions
- Codes HTTP et erreurs
- Intégrations
- Configuration avancée
- FAQ et dépannage
Architecture générale
Vue d'ensemble
Votre API utilise une authentification hybride combinant deux modes:
- Token API (Bearer Token) - Pour applications externes/services
- Session utilisateur - Pour utilisateurs connectés au site web
┌─────────────────────────────────────────────┐
│ Requête /api/* │
└────────────────┬────────────────────────────┘
│
┌───────┴──────────┐
│ │
┌────▼─────┐ ┌───────▼──────┐
│ Bearer │ │ Session │
│ Token? │ │ Active? │
└────┬─────┘ └───────┬──────┘
│ │
┌────▼──────────────────▼────┐
│ ApiTokenHandler OR │
│ Session Storage │
└────┬──────────────────────┘
│
┌────▼──────────────────────┐
│ Load User + Roles │
└────┬──────────────────────┘
│
┌────▼──────────────────────┐
│ Verify @IsGranted() │
└────┬──────────────────────┘
│
✅ 200 OK ou ❌ 401/403
Flux de sécurité
- Firewall
/api- Accepte les requêtes/api/* - ApiBearerTokenAuthenticator - Vérifie les Bearer tokens
- http_basic - Charge la session du context partagé
- Contrôles d'accès - Vérifie
@IsGranted()sur les routes - ApiExceptionListener - Convertit les erreurs en JSON
Authentification
Mode 1: Bearer Token (Clé API)
Génération du token
Via CLI:
php bin/console app:api-key:create pseudo_utilisateur "Nom de l'application"
Le token est généré automatiquement (64 caractères hexadécimaux, cryptographiquement sécurisé).
Via API (utilisateur connecté):
POST /api/api-keys
Content-Type: application/json
Authorization: Bearer existing_token
{
"name": "Application mobile",
"expiresAt": "2027-04-03T23:59:59Z" // optionnel
}
Utilisation du token
Le token doit être envoyé dans l'en-tête Authorization:
GET /api/profile
Authorization: Bearer abc123def456...
Caractéristiques
- ✅ Unique par clé API
- ✅ Peut avoir une date d'expiration
- ✅ Peut être désactivé/révoqué
- ✅ Stateless (pas de session serveur)
- ✅ Audit (tracking du dernier accès)
Mode 2: Session utilisateur
Établir une session
POST /login
Content-Type: application/x-www-form-urlencoded
_username=john&_password=secret
Un cookie PHPSESSID est créé et stocké.
Utiliser la session
Tous les cookies sont envoyés automatiquement par le navigateur. Via cURL:
curl -b cookies.txt -X POST http://localhost:8000/login \
-d "_username=john&_password=secret"
curl -b cookies.txt http://localhost:8000/api/profile
Caractéristiques
- ✅ Basée sur les cookies
- ✅ Automatique dans le navigateur
- ✅ Partage avec le firewall
main - ✅ Timeout configurable
- ✅ Plus sécurisée pour les applications web
Détection du mode d'authentification
La réponse /api/profile indique le mode utilisé:
{
"id": 1,
"pseudo": "john",
"roles": ["ROLE_USER"],
"authenticatedVia": "api_key" // ou "session"
}
Endpoints API
GET /api/health
Authentification: ❌ Non requise
Vérifier l'état de l'API.
Exemple:
curl http://localhost:8000/api/health
Réponse (200 OK):
{
"status": "ok",
"timestamp": "2026-04-03T10:30:00+00:00"
}
GET /api/profile
Authentification: ✅ Requise (Bearer token ou session)
Récupérer le profil de l'utilisateur authentifié.
Exemple avec Bearer token:
curl -X GET http://localhost:8000/api/profile \
-H "Authorization: Bearer your_token"
Réponse (200 OK):
{
"id": 1,
"pseudo": "john",
"roles": ["ROLE_USER", "ROLE_ADMIN"],
"authenticatedVia": "api_key"
}
Erreurs possibles:
401 Unauthorized- Token invalide/expiré ou pas de session
GET /api/api-keys
Authentification: ✅ Requise (Bearer token ou session)
Lister les clés API.
- ROLE_USER: Voit uniquement SES propres clés
- ROLE_ADMIN: Voit TOUTES les clés du système
Exemple:
curl -X GET http://localhost:8000/api/api-keys \
-H "Authorization: Bearer your_token"
Réponse (200 OK):
{
"apiKeys": [
{
"id": 1,
"name": "Mobile App",
"token": "a1b2c3d4e5...", // masqué pour sécurité
"createdAt": "2026-04-01T10:00:00+00:00",
"lastUsedAt": "2026-04-03T15:30:00+00:00",
"expiresAt": "2026-12-31T23:59:59+00:00",
"isActive": true
}
]
}
POST /api/api-keys
Authentification: ✅ Requise (Bearer token ou session)
Créer une nouvelle clé API.
Body (JSON):
{
"name": "Nouvelle application",
"expiresAt": "2027-04-03T23:59:59Z" // optionnel
}
Exemple:
curl -X POST http://localhost:8000/api/api-keys \
-H "Authorization: Bearer your_token" \
-H "Content-Type: application/json" \
-d '{
"name": "API Desktop",
"expiresAt": "2026-12-31T23:59:59Z"
}'
Réponse (201 Created):
{
"id": 5,
"name": "API Desktop",
"token": "abc123def456...xyz", // Complet à la création uniquement!
"createdAt": "2026-04-03T16:45:00+00:00",
"message": "Copier le token en lieu sûr, il ne sera plus visible ultérieurement"
}
⚠️ IMPORTANT: Le token complet n'est affiché qu'à la création. Stockez-le immédiatement.
Erreurs possibles:
400 Bad Request- Paramètre "name" manquant401 Unauthorized- Authentification invalide
DELETE /api/api-keys/{id}
Authentification: ✅ Requise (Bearer token ou session)
Supprimer une clé API.
- ROLE_USER: Peut supprimer uniquement ses propres clés
- ROLE_ADMIN: Peut supprimer n'importe quelle clé
Exemple:
curl -X DELETE http://localhost:8000/api/api-keys/1 \
-H "Authorization: Bearer your_token"
Réponse (200 OK):
{
"message": "API key deleted"
}
Erreurs possibles:
403 Forbidden- Essai de supprimer la clé d'un autre utilisateur404 Not Found- Clé inexistante
PATCH /api/api-keys/{id}/deactivate
Authentification: ✅ Requise (Bearer token ou session)
Désactiver une clé API sans la supprimer.
Exemple:
curl -X PATCH http://localhost:8000/api/api-keys/1/deactivate \
-H "Authorization: Bearer your_token"
Réponse (200 OK):
{
"message": "API key deactivated"
}
Gestion des clés API
Cycle de vie d'une clé
Création
│
├─→ Actif et utilisable
│
├─→ Peut être désactivé (sans suppression)
│
├─→ Peut avoir une expiration automatique
│
└─→ Peut être supprimé définitivement
Bonnes pratiques
- Créer des clés par application - Une clé par service/app
- Définir une expiration - Rotation annuelle recommandée
- Révoquer régulièrement - Supprimer les vieilles clés
- Ne jamais partager - Chaque clé est personnelle
- Monitorer l'accès - Vérifier
lastUsedAt
Exemple: Rotation de clés
# 1. Créer une nouvelle clé
curl -X POST http://localhost:8000/api/api-keys \
-H "Authorization: Bearer old_token" \
-H "Content-Type: application/json" \
-d '{"name": "Mobile App v2"}'
# 2. Mettre à jour l'application avec la nouvelle clé
# ... déployer avec new_token ...
# 3. Désactiver l'ancienne clé
curl -X PATCH http://localhost:8000/api/api-keys/1/deactivate \
-H "Authorization: Bearer new_token"
# 4. (Optionnel) Supprimer après confirmation
curl -X DELETE http://localhost:8000/api/api-keys/1 \
-H "Authorization: Bearer new_token"
Rôles et permissions
Hiérarchie de rôles
ROLE_ADMIN
└── ROLE_USER
ROLE_MODERATOR
└── ROLE_USER
ROLE_USER (base)
Permissions par rôle
| Action | ROLE_USER | ROLE_ADMIN |
|---|---|---|
| Voir son profil | ✅ | ✅ |
| Voir ses clés | ✅ | ✅ |
| Voir les clés d'autres | ❌ | ✅ |
| Créer ses clés | ✅ | ✅ |
| Supprimer ses clés | ✅ | ✅ |
| Supprimer les clés d'autres | ❌ | ✅ |
| Accéder à /api/* | ✅ | ✅ |
| Accéder à /administration | ❌ | ✅ |
Implémentation dans le code
// Protéger une route par rôle
#[Route('/api/mon-endpoint')]
#[IsGranted('ROLE_USER')]
public function monEndpoint(): JsonResponse { }
// Logique conditionnelle selon le rôle
if ($this->isGranted('ROLE_ADMIN')) {
// Admin seulement
return $this->json($allData);
}
return $this->json($userData);
Codes HTTP et erreurs
Codes de succès
| Code | Signification | Utilisation |
|---|---|---|
| 200 | OK | Requête réussie (GET, POST, PATCH, DELETE) |
| 201 | Created | Ressource créée (POST) |
| 204 | No Content | Succès sans contenu (rare) |
Codes d'erreur client (4xx)
400 Bad Request
Requête invalide (paramètres manquants, JSON malformé).
Exemple:
{
"error": "Bad Request",
"message": "name is required"
}
Solutions:
- Vérifier le format JSON
- Ajouter les paramètres requis
401 Unauthorized
Non authentifié ou authentification invalide.
Cas courants:
- Token manquant:
curl http://localhost:8000/api/profile - Token invalide:
Authorization: Bearer wrong_token - Token expiré: Token dont la date d'expiration est passée
- Session expirée: Cookies invalides/écoulés
Réponse:
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token or be logged in."
}
Solutions:
- Ajouter le header
Authorization: Bearer YOUR_TOKEN - Renouveler le token expiré
- Se reconnecter (pour session)
403 Forbidden
Authentifié mais permissions insuffisantes.
Cas courants:
- ROLE_USER essayant de supprimer la clé d'un autre
- Accès à un endpoint réservé aux admins
Réponse:
{
"error": "Forbidden",
"message": "You do not have permission to access this resource"
}
Solutions:
- Utiliser un compte admin si nécessaire
- Vérifier les droits requis
404 Not Found
Ressource inexistante.
Cas courants:
- Clé API inexistante:
DELETE /api/api-keys/99999 - Endpoint invalide:
GET /api/nonexistent
Solutions:
- Vérifier l'ID de la ressource
- Vérifier le chemin de la route
Codes d'erreur serveur (5xx)
500 Internal Server Error
Erreur serveur générique.
Cause: Bug ou exception non gérée.
Solutions:
- Vérifier les logs:
tail -f var/log/dev.log - Contacter le développeur
Intégrations
JavaScript / Fetch API
// Configuration
const API_BASE_URL = 'http://localhost:8000';
const API_TOKEN = 'your_api_token';
// Helper pour les appels API
async function apiCall(endpoint, options = {}) {
const url = `${API_BASE_URL}${endpoint}`;
const headers = {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json',
...options.headers,
};
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
const error = await response.json();
throw new Error(`${response.status}: ${error.message}`);
}
return response.json();
}
// Utilisation
async function getProfile() {
try {
const profile = await apiCall('/api/profile');
console.log('Profil:', profile);
} catch (error) {
console.error('Erreur:', error.message);
}
}
async function createApiKey(name) {
try {
const key = await apiCall('/api/api-keys', {
method: 'POST',
body: JSON.stringify({ name }),
});
console.log('Clé créée:', key.token);
} catch (error) {
console.error('Erreur:', error.message);
}
}
// Appels
getProfile();
createApiKey('Mon App');
Python / Requests
import requests
import json
class ApiClient:
def __init__(self, base_url, api_token):
self.base_url = base_url
self.api_token = api_token
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {api_token}',
'Content-Type': 'application/json',
})
def request(self, method, endpoint, data=None):
url = f"{self.base_url}{endpoint}"
try:
if method == 'GET':
response = self.session.get(url)
elif method == 'POST':
response = self.session.post(url, json=data)
elif method == 'DELETE':
response = self.session.delete(url)
elif method == 'PATCH':
response = self.session.patch(url, json=data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Erreur: {e}")
return None
def get_profile(self):
return self.request('GET', '/api/profile')
def list_keys(self):
return self.request('GET', '/api/api-keys')
def create_key(self, name, expires_at=None):
data = {'name': name}
if expires_at:
data['expiresAt'] = expires_at
return self.request('POST', '/api/api-keys', data)
# Utilisation
client = ApiClient('http://localhost:8000', 'your_api_token')
profile = client.get_profile()
print(f"Profil: {profile}")
keys = client.list_keys()
print(f"Clés: {keys}")
new_key = client.create_key('Python App')
print(f"Nouvelle clé: {new_key['token']}")
PHP / cURL
class ApiClient {
private $baseUrl;
private $apiToken;
public function __construct($baseUrl, $apiToken) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->apiToken = $apiToken;
}
private function request($method, $endpoint, $data = null) {
$url = $this->baseUrl . $endpoint;
$options = [
'http' => [
'method' => $method,
'header' => [
"Authorization: Bearer {$this->apiToken}",
'Content-Type: application/json',
],
'timeout' => 10,
],
];
if ($data && in_array($method, ['POST', 'PATCH'])) {
$options['http']['content'] = json_encode($data);
}
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);
if ($response === false) {
return ['error' => 'Erreur de connexion'];
}
return json_decode($response, true);
}
public function getProfile() {
return $this->request('GET', '/api/profile');
}
public function listKeys() {
return $this->request('GET', '/api/api-keys');
}
public function createKey($name, $expiresAt = null) {
$data = ['name' => $name];
if ($expiresAt) {
$data['expiresAt'] = $expiresAt;
}
return $this->request('POST', '/api/api-keys', $data);
}
}
// Utilisation
$client = new ApiClient('http://localhost:8000', 'your_api_token');
$profile = $client->getProfile();
var_dump($profile);
$keys = $client->listKeys();
var_dump($keys);
$newKey = $client->createKey('PHP App');
echo "Nouvelle clé: " . $newKey['token'];
Configuration avancée
Ajouter des endpoints personnalisés
// Dans src/Controller/ApiController.php
#[Route('/api/custom-endpoint')]
#[IsGranted('ROLE_USER')]
public function customEndpoint(): JsonResponse
{
$user = $this->getUser();
return $this->json([
'message' => 'Données personnalisées',
'user' => $user->getPseudo(),
]);
}
Filtrer par rôle dans un endpoint
#[Route('/api/admin-data')]
#[IsGranted('ROLE_USER')]
public function adminData(): JsonResponse
{
if (!$this->isGranted('ROLE_ADMIN')) {
return $this->json(
['error' => 'Accès admin requis'],
JsonResponse::HTTP_FORBIDDEN
);
}
return $this->json([
'adminData' => 'Secret data',
]);
}
Valider les requêtes
#[Route('/api/create-item', methods: ['POST'])]
#[IsGranted('ROLE_USER')]
public function createItem(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
// Valider les paramètres requis
if (empty($data['name'])) {
return $this->json(
['error' => 'name is required'],
JsonResponse::HTTP_BAD_REQUEST
);
}
// Traiter les données...
return $this->json(['id' => 1, 'name' => $data['name']]);
}
FAQ et dépannage
Q: Erreur "Invalid or expired API token"
R: Le token est invalide ou expiré.
- Vérifier que le token est correct
- Créer une nouvelle clé:
php bin/console app:api-key:create - Vérifier l'expiration
Q: Comment mettre à jour ma clé?
R: Les clés ne peuvent pas être mises à jour. Solutions:
- Créer une nouvelle clé avec une date d'expiration plus lointaine
- Révoquer l'ancienne clé
Q: Puis-je utiliser la même clé pour plusieurs applications?
R: Techniquement oui, mais déconseillé pour des raisons de sécurité:
- Une compromission affecte toutes les applications
- Impossible de révoquer une seule application
- Créer une clé par application
Q: Comment changer l'expiration d'une clé?
R: Ce n'est pas possible directement. Options:
- Créer une nouvelle clé avec expiration souhaitée
- Révoquer l'ancienne
- Mettre à jour l'application
Q: L'API fonctionne-t-elle avec HTTPS?
R: Oui, recommandé en production. Assurez-vous que:
- Les certificats sont valides
- Les URLs sont en HTTPS
- Les cookies ont le flag
Secure
Q: Puis-je accéder à l'API depuis un autre domaine (CORS)?
R: CORS n'est pas configuré par défaut. Pour l'activer:
- Installer bundle CORS:
composer require nelmio/cors-bundle - Configurer les domaines autorisés
Q: Comment monitorer l'utilisation de l'API?
R: Vérifier:
lastUsedAtdans GET/api/api-keys- Les logs:
var/log/dev.log - Implémenter un logging personnalisé
Q: Erreur "403 Forbidden"
R: Vous n'avez pas les permissions. Vérifier:
- Votre rôle (GET
/api/profile) - Les droits requis (@IsGranted)
- Si vous essayez de modifier les données d'un autre
Q: Comment réinitialiser toutes mes clés?
R:
- Lister les clés:
GET /api/api-keys - Supprimer une par une:
DELETE /api/api-keys/{id} - Créer de nouvelles clés
Documentation à jour: 2026-04-03