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

20 KiB

📚 Documentation Complète API mcV3

Table des matières

  1. Architecture générale
  2. Authentification
  3. Endpoints API
  4. Gestion des clés API
  5. Rôles et permissions
  6. Codes HTTP et erreurs
  7. Intégrations
  8. Configuration avancée
  9. 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:

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" 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:

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 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:

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

  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

# 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:

  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