mcServerWebsite/document/DOCUMENTATION_API.md
Ploush 303fc7bcb5 ajout trop de chose
rappel pour moi : a ne plus reproduire
2026-04-09 14:58:12 +02:00

825 lines
20 KiB
Markdown

# 📚 Documentation Complète API mcV3
## Table des matières
1. [Architecture générale](#architecture-générale)
2. [Authentification](#authentification)
3. [Endpoints API](#endpoints-api)
4. [Gestion des clés API](#gestion-des-clés-api)
5. [Rôles et permissions](#rôles-et-permissions)
6. [Codes HTTP et erreurs](#codes-http-et-erreurs)
7. [Intégrations](#intégrations)
8. [Configuration avancée](#configuration-avancée)
9. [FAQ et dépannage](#faq-et-dépannage)
---
## Architecture générale
### Vue d'ensemble
Votre API utilise une **authentification hybride** combinant deux modes:
1. **Token API (Bearer Token)** - Pour applications externes/services
2. **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é
1. **Firewall `/api`** - Accepte les requêtes `/api/*`
2. **ApiBearerTokenAuthenticator** - Vérifie les Bearer tokens
3. **http_basic** - Charge la session du context partagé
4. **Contrôles d'accès** - Vérifie `@IsGranted()` sur les routes
5. **ApiExceptionListener** - Convertit les erreurs en JSON
---
## Authentification
### Mode 1: Bearer Token (Clé API)
#### Génération du token
**Via CLI:**
```bash
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é):**
```http
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`:
```http
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
```http
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:
```bash
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é:
```json
{
"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:**
```bash
curl http://localhost:8000/api/health
```
**Réponse (200 OK):**
```json
{
"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:**
```bash
curl -X GET http://localhost:8000/api/profile \
-H "Authorization: Bearer your_token"
```
**Réponse (200 OK):**
```json
{
"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:**
```bash
curl -X GET http://localhost:8000/api/api-keys \
-H "Authorization: Bearer your_token"
```
**Réponse (200 OK):**
```json
{
"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):**
```json
{
"name": "Nouvelle application",
"expiresAt": "2027-04-03T23:59:59Z" // optionnel
}
```
**Exemple:**
```bash
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):**
```json
{
"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" manquant
- `401 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:**
```bash
curl -X DELETE http://localhost:8000/api/api-keys/1 \
-H "Authorization: Bearer your_token"
```
**Réponse (200 OK):**
```json
{
"message": "API key deleted"
}
```
**Erreurs possibles:**
- `403 Forbidden` - Essai de supprimer la clé d'un autre utilisateur
- `404 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:**
```bash
curl -X PATCH http://localhost:8000/api/api-keys/1/deactivate \
-H "Authorization: Bearer your_token"
```
**Réponse (200 OK):**
```json
{
"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
1. **Créer des clés par application** - Une clé par service/app
2. **Définir une expiration** - Rotation annuelle recommandée
3. **Révoquer régulièrement** - Supprimer les vieilles clés
4. **Ne jamais partager** - Chaque clé est personnelle
5. **Monitorer l'accès** - Vérifier `lastUsedAt`
### Exemple: Rotation de clés
```bash
# 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
```php
// 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:**
```json
{
"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:**
```json
{
"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:**
```json
{
"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
```javascript
// 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
```python
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
```php
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
```php
// 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
```php
#[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
```php
#[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:
1. Créer une nouvelle clé avec expiration souhaitée
2. Révoquer l'ancienne
3. 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:
1. Installer bundle CORS: `composer require nelmio/cors-bundle`
2. Configurer les domaines autorisés
### Q: Comment monitorer l'utilisation de l'API?
**R:** Vérifier:
- `lastUsedAt` dans 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:**
1. Lister les clés: `GET /api/api-keys`
2. Supprimer une par une: `DELETE /api/api-keys/{id}`
3. Créer de nouvelles clés
---
**Documentation à jour: 2026-04-03**