Aller au contenu principal
QualiForma
CatalogueDevenir formateurQualiopiIADéveloppeurs
Connexion
  • Catalogue
  • Devenir formateur
  • Qualiopi
  • IA
  • Développeurs
  • Connexion
  • Vue d'ensemble

Démarrage

  • Quickstart
  • Authentification

Référence

  • API Reference (Scalar)

Endpoints

  • Tous les endpoints

Cœur LMS

  • Tenants
  • Utilisateurs
  • Formations
  • Inscriptions
  • Sessions live

Conformité Qualiopi

  • Dashboard Qualiopi
  • Conformité
  • Émargement
  • Questionnaires
  • Réclamations
  • Plans d'amélioration
  • Parcours adaptatifs
  • Compétences formateurs
  • BPF
  • Médiateurs

Paiements & Facturation

  • Paiements
  • Facturation Factur-X
  • Webhooks

Design System

  • Vue d'ensemble
  • Couleurs
  • Typographie
  • Espacement
  • Elevation
  • Motion
  • Radius
  • Composants
  • · Formulaires
  • · Feedback
  • · Navigation
  • · Progression
  • · Données
Swagger UI (s'ouvre dans un nouvel onglet)
  1. Développeurs
  2. Authentification

Authentification

JWT Bearer tokens, refresh automatique, multi-tenancy via header et OIDC headless.

Flow d'authentification

  1. 1POST /auth/login → reçoit accessToken + refreshToken
  2. 2Chaque requête : Authorization: Bearer {accessToken} + X-Tenant-ID: {slug}
  3. 3Token expiré (401) → POST /auth/refresh → nouveaux tokens
  4. 4Refresh expiré → redirection vers /auth/login

Durée de vie des tokens

Caractéristiques des tokens d'accès et de rafraîchissement
TokenDuréeStockage recommandéRotation
Access Token1 heureMémoire / variableÀ chaque refresh
Refresh Token7 joursHttpOnly cookie ou SecureStoreÀ chaque utilisation

Rafraîchir un token

Rotation automatique : chaque refresh renvoie un nouveau pair de tokens. L'ancien refresh token est immédiatement invalidé.

POST /auth/refresh · Python
import requests

# Rafraîchir l'access token sans re-login
refresh_response = requests.post(
    'https://api.qualiforma.site/api/v1/auth/refresh',
    headers={
        'X-Tenant-ID': 'votre-tenant',
        'Content-Type': 'application/json'
    },
    json={'refreshToken': 'eyJhbGci...ancien-refresh-token...'}
)
data = refresh_response.json()['data']
new_access_token = data['accessToken']
new_refresh_token = data['refreshToken']  # rotation à chaque refresh
print("Token rafraîchi avec succès")
POST /auth/refresh · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/auth/refresh \
  -H 'X-Tenant-ID: votre-tenant' \
  -H 'Content-Type: application/json' \
  -d '{"refreshToken": "eyJhbGci...votre-refresh-token..."}'

# Réponse :
# {
#   "data": {
#     "accessToken": "eyJhbGci...nouveau-token...",
#     "refreshToken": "eyJhbGci...nouveau-refresh...",
#     "expiresIn": 3600
#   }
# }
POST /auth/refresh · TypeScript
async function refreshTokens(
  refreshToken: string,
  tenantId: string
): Promise<{ accessToken: string; refreshToken: string; expiresIn: number }> {
  const response = await fetch('https://api.qualiforma.site/api/v1/auth/refresh', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Tenant-ID': tenantId,
    },
    body: JSON.stringify({ refreshToken }),
  });

  if (!response.ok) {
    // 401 → refresh token expiré, rediriger vers login
    throw new Error('Session expirée, veuillez vous reconnecter');
  }

  const { data } = await response.json();
  return data;
}

// Intercepteur axios / fetch wrapper classique
async function apiCall(url: string, options: RequestInit, tokens: Tokens) {
  let response = await fetch(url, {
    ...options,
    headers: { ...options.headers, Authorization: `Bearer ${tokens.accessToken}` },
  });

  if (response.status === 401) {
    // Tentative de rafraîchissement automatique
    const refreshed = await refreshTokens(tokens.refreshToken, tokens.tenantId);
    saveTokens(refreshed); // stocker les nouveaux tokens
    response = await fetch(url, {
      ...options,
      headers: { ...options.headers, Authorization: `Bearer ${refreshed.accessToken}` },
    });
  }

  return response;
}
POST /auth/refresh · PHP
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

function refreshTokens(Client $client, string $refreshToken, string $tenantId): array
{
    try {
        $response = $client->post('auth/refresh', [
            'headers' => [
                'X-Tenant-ID' => $tenantId,
                'Content-Type' => 'application/json',
            ],
            'json' => ['refreshToken' => $refreshToken],
        ]);
        $body = json_decode($response->getBody()->getContents(), true);
        return $body['data'];
    } catch (ClientException $e) {
        if ($e->getResponse()->getStatusCode() === 401) {
            throw new \RuntimeException('Session expiree, veuillez vous reconnecter');
        }
        throw $e;
    }
}

// Wrapper qui retry une fois sur 401 (token expire)
function apiCall(Client $client, string $method, string $url, array $options, array &$tokens): \Psr\Http\Message\ResponseInterface
{
    $options['headers']['Authorization'] = 'Bearer ' . $tokens['accessToken'];

    try {
        return $client->request($method, $url, $options);
    } catch (ClientException $e) {
        if ($e->getResponse()->getStatusCode() !== 401) {
            throw $e;
        }
        // Tentative de rafraichissement automatique
        $refreshed = refreshTokens($client, $tokens['refreshToken'], $tokens['tenantId']);
        $tokens['accessToken'] = $refreshed['accessToken'];
        $tokens['refreshToken'] = $refreshed['refreshToken'];

        $options['headers']['Authorization'] = 'Bearer ' . $refreshed['accessToken'];
        return $client->request($method, $url, $options);
    }
}

Payload JWT

Exemple décodé

JWT payload · JSON
{
  "sub": "usr_abc123",
  "email": "admin@example.com",
  "tenantId": "ten_xyz789",
  "tenantSlug": "votre-tenant",
  "iat": 1700000000,
  "exp": 1700003600
}

Champs disponibles

ChampTypeDescription
substringID utilisateur (usr_...)
emailstringEmail de l'utilisateur
tenantIdstringID interne du tenant (ten_...)
tenantSlugstringSlug du tenant (URL-friendly)
iatnumberIssued At — timestamp Unix d'émission
expnumberExpiration — timestamp Unix (iat + 3600)

Multi-tenancy

Chaque organisation QualiForma est un tenant isolé. Le header X-Tenant-ID est obligatoire sur toutes les requêtes, y compris /auth/login.

X-Tenant-ID: votre-tenant-slug

Le slug tenant est la partie URL-friendly du nom de votre organisation. Retrouvez-le dans Administration → Paramètres → Informations du tenant.

Rôles et permissions

Rôles RBAC et niveaux d'accès
RôleAccès
ADMINAccès complet au tenant — gestion utilisateurs, formations, config
CREATORCréer et gérer ses propres formations, inscrire des apprenants
LEARNERConsulter le catalogue, suivre ses formations, télécharger ses documents
TRAINERAnimer des sessions, gérer les présences
FUNDERConsulter les inscriptions et documents de son périmètre

Vérification du rôle

Les endpoints vérifient toujours les permissions côté serveur. La vérification côté client est optionnelle pour l'UX.

Vérifier le rôle avant appel · Python
import requests

# Vérifier le rôle avant d'appeler un endpoint protégé
me = requests.get(
    'https://api.qualiforma.site/api/v1/auth/me',
    headers={
        'Authorization': f'Bearer {access_token}',
        'X-Tenant-ID': 'votre-tenant'
    }
).json()['data']

role = me['role']
if role not in ['ADMIN', 'CREATOR']:
    raise PermissionError(f"Rôle {role} insuffisant pour cette opération")

# Continuer — l'endpoint vérifie aussi côté serveur
response = requests.post(
    'https://api.qualiforma.site/api/v1/courses',
    headers={
        'Authorization': f'Bearer {access_token}',
        'X-Tenant-ID': 'votre-tenant',
        'Content-Type': 'application/json'
    },
    json={'title': 'Ma nouvelle formation', 'description': '...'}
)
Vérifier le rôle avant appel · PHP
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    // Verifier le role avant d'appeler un endpoint protege
    $response = $client->get('auth/me', [
        'headers' => [
            'Authorization' => 'Bearer ' . $accessToken,
            'X-Tenant-ID' => 'votre-tenant',
        ],
    ]);
    $me = json_decode($response->getBody()->getContents(), true)['data'];

    if (!in_array($me['role'], ['ADMIN', 'CREATOR'], true)) {
        throw new \RuntimeException("Role {$me['role']} insuffisant pour cette operation");
    }

    // Continuer - l'endpoint verifie aussi cote serveur
    $client->post('courses', [
        'headers' => [
            'Authorization' => 'Bearer ' . $accessToken,
            'X-Tenant-ID' => 'votre-tenant',
            'Content-Type' => 'application/json',
        ],
        'json' => [
            'title' => 'Ma nouvelle formation',
            'description' => '...',
        ],
    ]);
} catch (GuzzleException $e) {
    fwrite(STDERR, "Erreur: " . $e->getMessage() . PHP_EOL);
}

OIDC Headless

Pour les organisations avec un SSO existant (Azure AD, Okta, etc.), QualiForma supporte l'authentification OIDC headless par tenant. Le flux consiste à échanger un token OIDC contre des tokens QualiForma via POST /auth/oidc/exchange. Contactez le support pour la configuration.

QualiForma

La plateforme de formation professionnelle certifiee Qualiopi.

Plateforme

  • Catalogue
  • Espace formateur
  • Étude de besoin

Societe

  • A propos
  • Contact

Ressources

  • Développeurs
  • Référence API
  • Webhooks

Legal

  • CGV
  • Mentions legales
  • Confidentialite

Catalogue par catégorie

  • Management
  • Digital
  • Communication
  • Langues
  • Sécurité
  • Gestion

Comparatifs

  • QualiForma vs Didask
  • QualiForma vs Edusign
  • QualiForma vs Klaxoon
  • QualiForma vs 360Learning

Glossaire Qualiopi

  • I01 — Conditions d'information
  • I05 — Adaptation des prestations
  • I11 — Évaluations en cours
  • I22 — Compétences des intervenants
  • I30 — Recueil des appréciations
  • Voir les 32 indicateurs →

© 2026 QualiForma. Tous droits reserves.

Certifie Qualiopi