# Spécifications Techniques Complètes pour le Déploiement d'Applications

**Version :** 2.0  
**Date :** Décembre 2025  
**Auteur :** Équipe Infrastructure  
**Objectif :** Document de référence exhaustif pour déployer de nouvelles applications dans le portail d'authentification centralisé.

---

## Table des Matières

1. [Vue d'ensemble Générale](#vue-densemble)
2. [Architecture Système](#architecture)
3. [Préfixe et Structure des Routes API](#routes-api)
4. [Gestion des Sessions](#sessions)
5. [Contrôles et Sécurité des Accès (RBAC)](#rbac)
6. [Gestion des Proxies](#proxies)
7. [Stratégie CORS](#cors)
8. [Base de Données](#base-donnees)
9. [Authentification et Autorisation](#auth-autho)
10. [Authentification à Deux Facteurs (2FA)](#2fa)
11. [Communication API Sécurisée](#communication-api)
12. [Gestion des Erreurs](#erreurs)
13. [Logging et Monitoring](#logging)
14. [Déploiement d'une Nouvelle Application](#deploiement)
15. [Checklist de Conformité](#checklist)

---

## 1. Vue d'ensemble Générale {#vue-densemble}

Le portail d'authentification est une solution centralisée conçue pour :

- **Authentifier** les utilisateurs une seule fois et réutiliser leur identité pour toutes les applications du portefeuille
- **Gérer les droits d'accès** via un système RBAC (Role-Based Access Control) flexible
- **Sécuriser les communications** entre le frontend, le portail et les applications intégrées
- **Fournir une expérience SSO** transparente et fluide

### Principes Fondamentaux

- **Sécurité par défaut** : HTTPS obligatoire, HTTPS/TLS strict
- **Isolation des données** : Chaque application reste maître de ses données métier
- **Audit complet** : Tous les accès et modifications sont loggés
- **Flexibilité** : Les rôles et permissions sont entièrement configurables

---

## 2. Architecture Système {#architecture}

### 2.1 Composants Principaux

```txt
┌─────────────────────────────────────────────────────────────────┐
│                        UTILISATEUR FINAL                        │
└────────────────────────┬────────────────────────────────────────┘
                         │ (Navigateur)
                         ↓
┌─────────────────────────────────────────────────────────────────┐
│                    PORTAIL D'AUTHENTIFICATION                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Frontend SPA (Vue/React)                               │    │
│  │  - Affichage du profil utilisateur                      │    │
│  │  - Portail des applications                             │    │
│  │  - Gestion du 2FA                                       │    │
│  └──────────────────┬──────────────────────────────────────┘    │
│                    │ (Fetch vers /access-point)                 │
│  ┌────────────────────────────────────────────────────-────┐    │
│  │  Backend API REST (/access-point)                       │    │
│  │  - Authentification                                     │    │
│  │  - Gestion des sessions                                 │    │
│  │  - Gestion des droits (RBAC)                            │    │
│  │  - Endpoints d'administration                           │    │
│  └──────────────────┬──────────────────────────────────────┘    │
└─────────────────────┼───────────────────────────────────-───────┘
                      │ (PDO, Prepared Statements)
                      ↓
        ┌─────────────────────────────┐
        │  MySQL/MariaDB (Base de     │
        │  Données Centralisée)       │
        └─────────────────────────────┘
         
         │ (Forwarding vers d'autres services)
         ↓
┌───────────────────────────────────────────────────────────────────┐
│                    APPLICATIONS INTÉGRÉES                         │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐ │
│  │  App 1 (Compta)  │  │  App 2 (RH)      │  │  App 3 (Stock)   │ │
│  │  Backend/BDD     │  │  Backend/BDD     │  │  Backend/BDD     │ │
│  └──────────────────┘  └──────────────────┘  └──────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
```

### 2.2 Flux d'Authentification Standard

1. **Authentification** : L'utilisateur se connecte via le portail (`/access-point/login`)
2. **Session Créée** : Un cookie `PHPSESSID` sécurisé est émis (HttpOnly, Secure, SameSite=Strict)
3. **Accès au Portail** : L'utilisateur accède au portail personnel
4. **Accès aux Applications** : Le portail récupère la liste des applications autorisées via `/access-point/applications`
5. **Redirection vers l'App** : L'utilisateur clique sur une application → le portail le redirige vers l'app avec identification
6. **Application Vérifie** : L'application valide que l'utilisateur est authentifié auprès du portail
7. **Session Partagée** : L'application peut consulter les données utilisateur via le portail (endpoint protégé)

---

## 3. Préfixe et Structure des Routes API {#routes-api}

### 3.1 Principes d'Architecture

**Préfixe Personnalisé :** `/access-point`

Pour des raisons de sécurité, le préfixe `/access-point` remplace les préfixes génériques comme `/api` ou `/auth`. Cela rend les endpoints moins facilement découvrables par des scans automatisés.

### 3.2 URL de Base

https://famille.labiau.fr/access-point

### 3.3 Routes Publiques (Sans Authentification)

#### Health Check
GET /access-point/health
Réponse : { "status": "available", "timestamp": "2025-12-06T10:11:00Z" }
HTTP 200 OK

#### Authentification
POST /access-point/register
POST /access-point/login
POST /access-point/password/forgot
POST /access-point/password/reset
GET /access-point/verify-email?token={token}
HTTP 201 / 200 / 202 / 400 / 401 / 409 ...

### 3.4 Routes Protégées (Authentification Requise)

#### Utilisateur Connecté
POST /access-point/logout
GET /access-point/applications
POST /access-point/login/2fa
GET /access-point/profile
HTTP 200 / 401 / 403 ...

#### Administration (Rôle `admin` requis)
GET /access-point/admin/users
GET /access-point/admin/roles
PUT /access-point/admin/users/{id}/roles
HTTP 200 / 401 / 403 / 404 ...

### 3.5 Codes de Réponse HTTP Standardisés

| Code | Signification | Utilisation |
|------|--------------|-----------|
| **200 OK** | Succès | Appels réussis, modification acceptée |
| **201 Created** | Créé | Inscription réussie |
| **202 Accepted** | Accepté | 2FA requis après login correct |
| **400 Bad Request** | Erreur cliente | Données invalides, token expiré |
| **401 Unauthorized** | Non authentifié | Cookie manquant, session expirée |
| **403 Forbidden** | Interdit | Rôle insuffisant (e.g., pas admin) |
| **404 Not Found** | Introuvable | Utilisateur/ressource inexistant |
| **409 Conflict** | Conflit | Email/username déjà existant |
| **422 Unprocessable Entity** | Non traitable | Validation échouée (plus détaillé que 400) |
| **500 Server Error** | Erreur serveur | Bug backend, contactez support |
| **503 Service Unavailable** | Service indisponible | BDD hors ligne, maintenance |

---

## 4. Gestion des Sessions {#sessions}

### 4.1 Mécanisme de Session PHP

Les sessions utilisent **cookies HTTP sécurisés** pour maintenir l'état authentifié.

### 4.2 Configuration Requise des Cookies

// php.ini ou configuration runtime
session.use_strict_mode = 1;              // Valide le PHPSESSID
session.cookie_secure = 1;                // HTTPS only
session.cookie_httponly = 1;              // Pas accessible via JS
session.cookie_samesite = "Strict";       // Protection CSRF
session.gc_maxlifetime = 1800;            // 30 minutes d'inactivité
session.name = "PHPSESSID";
session.use_trans_sid = 0;                // Pas d'ID en URL

### 4.3 Cycle de Vie de la Session

1. **Création** : Après `/login` réussi, `session_start()` crée une session
2. **Régénération** : `session_regenerate_id()` après authentication pour prévenir les attaques de fixation
3. **Stockage** : Les données utilisateur sont stockées dans `$_SESSION['user_id']`, `$_SESSION['username']`, etc.
4. **Expiration** : Si inactivité > 30 min, la session est supprimée automatiquement
5. **Destruction** : `/logout` appelle `session_destroy()`

### 4.4 Vérification de la Session dans une Application

// Vérifier que l'utilisateur est connecté
if (!isset($_SESSION['user_id'])) {
    header('HTTP/1.1 401 Unauthorized');
    exit(json_encode(['error' => 'Authentification requise']));
}

// Récupérer l'ID utilisateur
$userId = $_SESSION['user_id'];
$username = $_SESSION['username'];

### 4.5 Transmission de la Session

**Important :** Les applications ne peuvent **pas** créer directement les sessions. Elles doivent :

1. Vérifier que `PHPSESSID` existe (reçu du portail)
2. Accepter et valider le contenu de `$_SESSION` initié par le portail
3. Consulter les endpoints protégés du portail pour vérifier les permissions

---

## 5. Contrôles et Sécurité des Accès (RBAC) {#rbac}

### 5.1 Système RBAC (Role-Based Access Control)

Le portail gère un système flexible de **rôles** et **permissions**.

### 5.2 Architecture RBAC

Utilisateur
    ↓
Possède N rôles
    ↓
┌─────────────────────────────────────┐
│ Rôle 1: admin                       │
│ - Peut accéder à /admin/users       │
│ - Peut accéder à /admin/roles       │
│ - Peut modifier les rôles           │
└─────────────────────────────────────┘
│ Rôle 2: app_compta_viewer           │
│ - Peut accéder à l'app Comptabilité │
│ - Lecture seule sur les comptes     │
└─────────────────────────────────────┘
│ Rôle 3: app_rh_editor               │
│ - Peut accéder à l'app RH           │
│ - Lecture/Écriture sur les emplois  │
└─────────────────────────────────────┘

### 5.3 Rôles Prédéfinis

| Rôle | Description | Permissions |
|------|-------------|-----------|
| `admin` | Administrateur système | Accès à tous les endpoints `/admin` |
| `app_*_viewer` | Lecteur d'application | Accès lecture à une application spécifique |
| `app_*_editor` | Éditeur d'application | Accès lecture/écriture à une application |
| `support` | Support technique | Gestion des tickets, audit logs |

**Convention de nommage :** `app_{nom_app}_{permission_level}`

### 5.4 Vérification des Rôles dans une Application

// Vérifier qu'l'utilisateur a un rôle spécifique
function hasRole($roleRequired) {
    global $authenticatedUser; // Obtenu du portail
    
    return in_array($roleRequired, $authenticatedUser['roles']);
}

// Protéger un endpoint
if (!hasRole('app_compta_editor')) {
    header('HTTP/1.1 403 Forbidden');
    exit(json_encode(['error' => 'Accès refusé']));
}

### 5.5 Endpoint pour Récupérer les Rôles

GET /access-point/profile
Réponse (200 OK) :
{
    "user_id": 1,
    "username": "jean_dupont",
    "email": "jean@labiau.fr",
    "roles": [
        { "id": 2, "name": "app_compta_viewer" },
        { "id": 3, "name": "app_rh_editor" }
    ],
    "is_active": true
}

---

## 6. Gestion des Proxies {#proxies}

### 6.1 Stratégie de Proxy

Les proxies permettent aux applications d'appeler le portail sans exposer directement le domaine du portail.

### 6.2 Types de Proxies

#### A. Proxy d'Authentification

**Usage :** L'application reçoit une demande de vérification d'authentification

Application → Portail
GET /access-point/profile (avec PHPSESSID cookie)
Réponse : Données utilisateur + rôles

#### B. Proxy d'Autorisation

**Usage :** L'application demande si un utilisateur a un rôle

Application → Portail
POST /access-point/verify-permission
Body: { "required_role": "app_compta_viewer" }
Réponse : { "allowed": true/false }

#### C. Proxy de Traçabilité

**Usage :** L'application enregistre les actions de l'utilisateur auprès du portail

Application → Portail
POST /access-point/audit-log
Body: { "action": "accessed_invoice", "resource_id": 123, "timestamp": "..." }
Réponse : { "logged": true }

### 6.3 Implémentation d'un Proxy

// Dans la classe ProxyService de l'application
class ProxyService {
    private $portalUrl = 'https://famille.labiau.fr';
    
    public function verifyAuthentication() {
        $response = $this->callPortal('/access-point/profile');
        return $response['user_id'] ?? null;
    }
    
    public function verifyPermission($role) {
        $response = $this->callPortal('/access-point/verify-permission', [
            'required_role' => $role
        ]);
        return $response['allowed'] ?? false;
    }
    
    private function callPortal($endpoint, $data = null) {
        $options = [
            'http' => [
                'header'  => "Content-Type: application/json\r\nCookie: PHPSESSID={$_COOKIE['PHPSESSID']}",
                'method'  => $data ? 'POST' : 'GET',
                'content' => $data ? json_encode($data) : null,
            ],
            'ssl' => [
                'verify_peer'      => true,
                'verify_peer_name' => true,
            ]
        ];
        
        $context = stream_context_create($options);
        $result = @file_get_contents($this->portalUrl . $endpoint, false, $context);
        
        return json_decode($result, true) ?? [];
    }
}

---

## 7. Stratégie CORS {#cors}

### 7.1 Configuration CORS Requise

Le portail doit permettre les requêtes Cross-Origin des applications intégrées.

### 7.2 En-têtes CORS à Configurer

// Dans le backend API du portail
header('Access-Control-Allow-Origin: https://app-compta.labiau.fr');
header('Access-Control-Allow-Origin: https://app-rh.labiau.fr');
// Ajouter chaque application autorisée

header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Allow-Credentials: true');  // IMPORTANT : autorise les cookies
header('Access-Control-Max-Age: 3600');

### 7.3 Gestion du Pre-flight (OPTIONS)

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}

### 7.4 Appel JavaScript avec Credentials

fetch('https://famille.labiau.fr/access-point/profile', {
    method: 'GET',
    credentials: 'include',  // IMPORTANT : inclut les cookies
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(response => response.json())
.then(data => console.log(data));

---

## 8. Base de Données {#base-donnees}

### 8.1 Schéma Centralisé

**Principe :** La base de données du portail est **centralisée** et accessible en lecture par les applications (avec les permissions appropriées).

### 8.2 Tables Principales

#### `auth_users`
Utilisateurs du système

CREATE TABLE auth_users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

#### `auth_roles`
Rôles disponibles

CREATE TABLE auth_roles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(255) NULL
);

#### `auth_user_roles`
Assignation des rôles aux utilisateurs (N-N)

CREATE TABLE auth_user_roles (
    user_id INT NOT NULL,
    role_id INT NOT NULL,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES auth_roles(id) ON DELETE CASCADE
);

#### `auth_email_verifications`
Tokens d'activation d'email

CREATE TABLE auth_email_verifications (
    user_id INT NOT NULL PRIMARY KEY,
    token VARCHAR(255) NOT NULL UNIQUE,
    expires_at TIMESTAMP NOT NULL,
    FOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE
);

#### `auth_password_resets`
Tokens de réinitialisation

CREATE TABLE auth_password_resets (
    email VARCHAR(100) NOT NULL PRIMARY KEY,
    token VARCHAR(255) NOT NULL UNIQUE,
    expires_at TIMESTAMP NOT NULL
);

#### `auth_users_2fa`
Secrets 2FA

CREATE TABLE auth_users_2fa (
    user_id INT NOT NULL PRIMARY KEY,
    secret_key_encrypted VARCHAR(255) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE
);

#### `auth_users_2fa_backup_codes`
Codes de secours 2FA

CREATE TABLE auth_users_2fa_backup_codes (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    code_hash VARCHAR(255) NOT NULL,
    used_at TIMESTAMP NULL,
    FOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE
);

### 8.3 Accès Application à la BDD

**Règle :** Les applications ne doivent **pas** accéder directement à la BDD du portail. Elles doivent :

1. Passer par les endpoints API du portail
2. Utiliser des vues ou stored procedures pour lire les données autorisées
3. Loguer toute consultation via `/access-point/audit-log`

---

## 9. Authentification et Autorisation {#auth-autho}

### 9.1 Flux d'Authentification Complet

1. **Inscription** : `POST /access-point/register`
   - Validation des données (email, username, mot de passe)
   - Hachage du mot de passe (Argon2id)
   - Envoi d'un email de confirmation

2. **Vérification Email** : `GET /access-point/verify-email?token={token}`
   - Validation du token
   - Activation du compte (`is_active = TRUE`)

3. **Connexion** : `POST /access-point/login`
   - Vérification du username/password
   - Génération d'une session
   - Si 2FA activé : répondre 202 Accepted (pas 200)

4. **Vérification 2FA** : `POST /access-point/login/2fa` (si nécessaire)
   - Validation du code TOTP
   - Marquage de la session comme "pleinement authentifiée"

5. **Accès aux Ressources** : Application consulte `/access-point/profile`
   - Cookie `PHPSESSID` présent
   - Retour des données utilisateur + rôles

### 9.2 Mécanisme Argon2id pour les Mots de Passe

// Hachage (à l'enregistrement ou changement)
$passwordHash = password_hash($password, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,  // 64 MB
    'time_cost'   => 4,
    'threads'     => 2
]);

// Vérification (à la connexion)
if (password_verify($password, $passwordHash)) {
    // Mot de passe valide
}

### 9.3 Vérification des Autorisation avant Chaque Action

// Exemple dans une application
if (!isset($_SESSION['user_id'])) {
    // Non authentifié
    http_response_code(401);
    exit;
}

// Vérifier le rôle auprès du portail
$profile = $portalProxy->getProfile($_SESSION['user_id']);
if (!in_array('app_compta_editor', $profile['roles'])) {
    // Rôle insuffisant
    http_response_code(403);
    exit;
}

// Procéder à l'action

---

## 10. Authentification à Deux Facteurs (2FA) {#2fa}

### 10.1 Méthode TOTP (Time-based One-Time Password)

Le portail utilise **Google Authenticator** (TOTP RFC 6238).

### 10.2 Activation du 2FA

// 1. Générer un secret
use PragmaRX\Google2FA\Google2FA;
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();

// 2. Chiffrer et stocker
$encryptedSecret = encrypt($secret, APP_SECRET);
// INSERT INTO auth_users_2fa (user_id, secret_key_encrypted) VALUES (?, ?)

// 3. Générer un QR Code
$qrCode = $google2fa->getQRCodeUrl(
    'famille.labiau.fr',
    $username,
    $secret
);
// Afficher le QR code à l'utilisateur

// 4. Générer les codes de secours
$backupCodes = generateBackupCodes(10);
// Hacher chaque code et stocker dans auth_users_2fa_backup_codes

### 10.3 Vérification 2FA à la Connexion

// POST /access-point/login/2fa
// Body: { "code": "123456" }

function verify2FA($userId, $code) {
    // 1. Récupérer le secret
    $result = $db->query(
        "SELECT secret_key_encrypted FROM auth_users_2fa WHERE user_id = ?",
        [$userId]
    );
    
    if (!$result) return false; // 2FA non activé
    
    $encryptedSecret = $result['secret_key_encrypted'];
    $secret = decrypt($encryptedSecret, APP_SECRET);
    
    // 2. Vérifier le code TOTP (avec une fenêtre de ±30 secondes)
    $google2fa = new Google2FA();
    if ($google2fa->verifyKey($secret, $code, 2)) {
        return true;
    }
    
    // 3. Sinon, vérifier les codes de secours
    $backupCodeHash = password_hash($code, PASSWORD_ARGON2ID);
    $result = $db->query(
        "SELECT id FROM auth_users_2fa_backup_codes 
         WHERE user_id = ? AND used_at IS NULL AND code_hash = ?",
        [$userId, $backupCodeHash]
    );
    
    if ($result) {
        // Marquer le code comme utilisé
        $db->query(
            "UPDATE auth_users_2fa_backup_codes SET used_at = NOW() WHERE id = ?",
            [$result['id']]
        );
        return true;
    }
    
    return false;
}

### 10.4 Désactivation du 2FA

// DELETE FROM auth_users_2fa WHERE user_id = ?
// DELETE FROM auth_users_2fa_backup_codes WHERE user_id = ?

---

## 11. Communication API Sécurisée {#communication-api}

### 11.1 Requêtes Préparées (Prepared Statements)

**Obligation :** Toutes les requêtes SQL doivent utiliser des prepared statements pour prévenir les injections SQL.

// ✓ BON
$stmt = $db->prepare("SELECT * FROM auth_users WHERE username = ?");
$stmt->execute([$username]);

// ✗ MAUVAIS
$query = "SELECT * FROM auth_users WHERE username = '$username'";
$result = $db->query($query);

### 11.2 Escaping des Données

Pour l'affichage en HTML, échapper tous les données utilisateur :

// ✓ BON
echo htmlspecialchars($username, ENT_QUOTES, 'UTF-8');

// ✗ MAUVAIS
echo $username;

### 11.3 Validation des Entrées

// Valider un email
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    throw new InvalidArgumentException("Email invalide");
}

// Valider une URL
if (!filter_var($url, FILTER_VALIDATE_URL)) {
    throw new InvalidArgumentException("URL invalide");
}

// Valider la longueur
if (strlen($password) < 8) {
    throw new InvalidArgumentException("Mot de passe trop court");
}

### 11.4 Rate Limiting

Protéger les endpoints sensibles contre les attaques par force brute :

class RateLimiter {
    public function checkLimit($identifier, $maxAttempts = 5, $timeWindow = 300) {
        // $identifier : adresse IP, user_id, etc.
        // $maxAttempts : 5 tentatives
        // $timeWindow : 300 secondes (5 minutes)
        
        $key = "rate_limit:{$identifier}";
        $attempts = apcu_fetch($key) ?: 0;
        
        if ($attempts >= $maxAttempts) {
            http_response_code(429);
            exit(json_encode(['error' => 'Trop de tentatives. Réessayez plus tard.']));
        }
        
        apcu_store($key, $attempts + 1, $timeWindow);
    }
}

### 11.5 JWT (Optional pour les Applications tierces)

Pour les applications externes (non dans le même réseau interne), utiliser JWT pour l'authentification :

// Générer un JWT
$payload = [
    'user_id' => $userId,
    'username' => $username,
    'iat' => time(),
    'exp' => time() + 3600  // Expiration 1h
];

$jwt = JWT::encode($payload, APP_SECRET, 'HS256');

---

## 12. Gestion des Erreurs {#erreurs}

### 12.1 Codes d'Erreur Standardisés

Toujours retourner une structure cohérente :

{
    "error": "Description lisible de l'erreur",
    "code": "ERROR_CODE_UNIQUE",
    "timestamp": "2025-12-06T10:11:00Z",
    "request_id": "uuid-unique-pour-traçabilité"
}

### 12.2 Codes d'Erreur Courants

| Code | HTTP | Description |
|------|------|-----------|
| `AUTH_REQUIRED` | 401 | Authentification nécessaire |
| `PERMISSION_DENIED` | 403 | Permissions insuffisantes |
| `USER_NOT_FOUND` | 404 | Utilisateur inexistant |
| `EMAIL_ALREADY_EXISTS` | 409 | Email déjà utilisé |
| `INVALID_TOKEN` | 400 | Token expiré ou invalide |
| `WEAK_PASSWORD` | 400 | Mot de passe insuffisant |
| `RATE_LIMIT_EXCEEDED` | 429 | Trop de tentatives |
| `INTERNAL_ERROR` | 500 | Erreur serveur (loggée) |

### 12.3 Gestion Exception

try {
    // Opération
} catch (InvalidArgumentException $e) {
    http_response_code(400);
    exit(json_encode([
        'error' => $e->getMessage(),
        'code' => 'VALIDATION_ERROR'
    ]));
} catch (Exception $e) {
    // Logguer l'erreur
    logger()->error($e->getMessage(), ['trace' => $e->getTraceAsString()]);
    
    http_response_code(500);
    exit(json_encode([
        'error' => 'Erreur serveur interne',
        'code' => 'INTERNAL_ERROR'
    ]));
}

---

## 13. Logging et Monitoring {#logging}

### 13.1 Logs à Enregistrer Obligatoirement

1. **Tentatives de connexion** (réussies et échouées)
2. **Changements de mot de passe**
3. **Activation/désactivation du 2FA**
4. **Accès aux endpoints d'administration**
5. **Modifications de rôles utilisateur**
6. **Erreurs serveur**

### 13.2 Format de Log Standard

{
    "timestamp": "2025-12-06T10:11:00Z",
    "event": "USER_LOGIN",
    "user_id": 42,
    "username": "jean_dupont",
    "ip_address": "192.168.1.1",
    "user_agent": "Mozilla/5.0...",
    "status": "SUCCESS",
    "details": "Connexion réussie avec 2FA"
}

### 13.3 Implémentation Logger

class Logger {
    public static function log($event, $data) {
        $entry = [
            'timestamp' => date('c'),
            'event' => $event,
            'ip_address' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            ...$data
        ];
        
        // Écriture en fichier ou BDD
        file_put_contents(
            LOG_PATH . date('Y-m-d') . '.log',
            json_encode($entry) . PHP_EOL,
            FILE_APPEND
        );
        
        // Ou envoyer à un service de logging (Sentry, ELK)
        sentry_log($entry);
    }
}

### 13.4 Monitoring du Portail

**Métriques à suivre :**
- Nombre de logins réussis/échoués par heure
- Temps de réponse moyens
- Taux d'erreur
- Utilisation mémoire et CPU

---

## 14. Déploiement d'une Nouvelle Application {#deploiement}

### 14.1 Étapes Préliminaires

#### 1. Créer les Rôles Nécessaires

-- Dans la BDD du portail
INSERT INTO auth_roles (name, description) VALUES 
    ('app_ma_nouvelle_app_viewer', 'Lecteur de Ma Nouvelle App'),
    ('app_ma_nouvelle_app_editor', 'Éditeur de Ma Nouvelle App');

#### 2. Attribuer les Rôles aux Utilisateurs

-- Attribuer le rôle à un utilisateur
INSERT INTO auth_user_roles (user_id, role_id) VALUES 
    (1, (SELECT id FROM auth_roles WHERE name = 'app_ma_nouvelle_app_viewer'));

### 14.2 Configuration de l'Application

#### 1. Fichier de Configuration

// config/auth.php
return [
    'portal_url' => 'https://famille.labiau.fr',
    'access_point' => '/access-point',
    'app_name' => 'ma_nouvelle_app',
    'required_roles' => [
        'app_ma_nouvelle_app_viewer',
        'app_ma_nouvelle_app_editor'
    ]
];

#### 2. Middleware d'Authentification

// middleware/AuthMiddleware.php
class AuthMiddleware {
    public function handle($request, $next) {
        // 1. Vérifier la session
        if (!isset($_SESSION['user_id'])) {
            http_response_code(401);
            return json_encode(['error' => 'Non authentifié']);
        }
        
        // 2. Vérifier auprès du portail que la session est toujours valide
        $portalProxy = new ProxyService();
        $profile = $portalProxy->getProfile($_SESSION['user_id']);
        
        if (!$profile) {
            http_response_code(401);
            session_destroy();
            return json_encode(['error' => 'Session expirée']);
        }
        
        // 3. Vérifier le rôle requis
        $requiredRole = config('auth.app_name') . '_viewer'; // ou '_editor'
        if (!in_array($requiredRole, $profile['roles'])) {
            http_response_code(403);
            return json_encode(['error' => 'Accès refusé']);
        }
        
        // 4. Attacher l'utilisateur à la requête
        $request->user = $profile;
        
        return $next($request);
    }
}

### 14.3 CORS et Redirection

#### 1. Configuration CORS

// dans public/index.php ou bootstrap
header('Access-Control-Allow-Origin: https://famille.labiau.fr');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}

#### 2. Redirection depuis le Portail

Dans le portail, ajouter le lien :

// Dans le frontend du portail
const applications = [
    {
        id: 1,
        name: 'Ma Nouvelle App',
        description: 'Description',
        url: 'https://app-ma-nouvelle-app.labiau.fr',
        icon: 'icon.png'
    }
];

### 14.4 Tests de Conformité

Avant de mettre en production :

- [ ] La session est correctement reçue du portail
- [ ] Les rôles sont correctement vérifiés
- [ ] Les erreurs 401/403 sont correctement gérées
- [ ] Les logs sont correctement enregistrés
- [ ] HTTPS est obligatoire
- [ ] Pas d'SQL injection possible
- [ ] Pas de XSS possible
- [ ] CORS fonctionne correctement
- [ ] Rate limiting est actif

---

## 15. Checklist de Conformité {#checklist}

### Sécurité

- [ ] HTTPS obligatoire (pas de HTTP)
- [ ] Mots de passe hachés avec Argon2id
- [ ] Requêtes SQL avec prepared statements
- [ ] Toutes données utilisateur échappées (htmlspecialchars)
- [ ] Rate limiting sur endpoints sensibles
- [ ] Sessions avec HttpOnly, Secure, SameSite=Strict
- [ ] Token d'email/réinitialisation avec expiration
- [ ] Secrets 2FA chiffrés
- [ ] Pas de mots de passe en logs

### Fonctionnalités

- [ ] Authentification avec email + password
- [ ] Vérification d'email à l'inscription
- [ ] Réinitialisation de mot de passe
- [ ] 2FA TOTP optionnel
- [ ] Codes de secours 2FA
- [ ] Gestion des rôles (RBAC)
- [ ] Déconnexion

### API

- [ ] Endpoint `/access-point/health` public
- [ ] Endpoints publics (registration, login) sans authentification
- [ ] Endpoints protégés avec vérification session
- [ ] Codes HTTP corrects (200, 201, 202, 400, 401, 403, 404, 409, 422, 500)
- [ ] Réponses JSON structurées
- [ ] Gestion des erreurs avec message lisible

### Logs et Monitoring

- [ ] Logs pour chaque login
- [ ] Logs pour modifications de mot de passe
- [ ] Logs pour changes de rôles
- [ ] Format de log standardisé
- [ ] Fichiers logs protégés (pas lisibles via web)
- [ ] Analyse logs pour detection d'anomalies

### Déploiement

- [ ] Configuration en variables d'environnement (.env)
- [ ] Pas de secrets en dur dans le code
- [ ] Migrations BDD documentées
- [ ] Documentation complète
- [ ] Plan de rollback
- [ ] Tests avant production

---

## Conclusion

Ce document servira de référence pour tous les déploiements d'applications. Toute nouvelle application **doit** respecter ces spécifications pour garantir la sécurité, la compatibilité et la maintenabilité du portail.

**Pour toute question :** Contactez l'équipe infrastructure ou consultez ce document.

**Dernière mise à jour :** 6 décembre 2025