Retour au Blog
Blog/Engineering

Gestion des Sessions SaaS : Persister l'État lors du Changement de Locataire

Comment concevoir une couche de synchronisation fiable des sessions dans les architectures hybrides pour éviter les déconnexions intempestives lors du changement d'entreprise.

Akram Hafaiedh
Akram Hafaiedh
Développeur Full Stack
2026-02-225 min de lecture
// SYSTEM ARCHITECTURE PATTERN
PRODUCTION VERIFIED
TECHNICAL CASE STUDY

Gestion des Sessions SaaS : Persister l'État lors du Changement de Locataire

LARAVELREDISMULTI-TENANT

Persistance des Sessions dans un SaaS Multi-Entreprises

Dans les plateformes professionnelles multi-locataires (comme les ERP ou les outils de facturation), les utilisateurs gèrent ou naviguent souvent entre plusieurs entreprises. La conception de ce flux nécessite un système de gestion de session particulièrement robuste.

Un problème fréquent dans les architectures hybrides est la perte de persistance des sessions : lors du rechargement de la page ou du passage d'une entreprise à une autre, l'utilisateur se retrouve subitement déconnecté en raison d'un désalignement entre le LocalStorage client et les cookies de session du serveur. Voyons comment résoudre ce dysfonctionnement.


La Cause Racine : Désynchronisation des États de Jetons

Lorsqu'un utilisateur bascule de l'entreprise A vers l'entreprise B, le client effectue des appels API pour charger les nouvelles données. Si le client met à jour son active_company_id dans le LocalStorage mais que le jeton de session ou le cookie CSRF côté serveur n'est pas correctement synchronisé ou renouvelé, les intercepteurs renvoient une erreur 401 Unauthorized ou 419 Page Expired, forçant une déconnexion intempestive.


1. Mettre en place un Intercepteur Authentifié Synchrone

Pour éviter la perte de jeton lors de navigations rapides, utilisez un intercepteur Axios chargeant dynamiquement les en-têtes d'envoi.

ts
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// lib/http-client.ts
import axios from 'axios';

const httpClient = axios.create({
    baseURL: '/api',
    withCredentials: true, class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Crucial pour inclure les cookies de session
});

httpClient.interceptors.request.use((config) => {
    const activeCompanyId = localStorage.getItem('active_company_id');
    
    if (activeCompanyId) {
        config.headers['X-Company-ID'] = activeCompanyId;
    }
    
    class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Récupère automatiquement le jeton CSRF depuis les cookies du document
    const csrfToken = document.cookie
        .split('; ')
        .find(row => row.startsWith('XSRF-TOKEN='))
        ?.split('=')[1];

    if (csrfToken) {
        config.headers['X-XSRF-TOKEN'] = decodeURIComponent(csrfToken);
    }

    return config;
}, (error) => {
    return Promise.reject(error);
});

2. Middleware Serveur de Validation du Contexte Client

Côté backend, assurez-vous que l'identifiant de l'entreprise demandé dans les en-têtes correspond bien aux entreprises associées à l'utilisateur authentifié. En cas d'incohérence, renvoyez une réponse d'erreur maîtrisée plutôt que de détruire directement la session.

php
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// app/Http/Middleware/TenantContextMiddleware.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class TenantContextMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $companyId = $request->header('X-Company-ID');
        $user = $request->user();

        if ($companyId && $user) {
            class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Vérifie que l'utilisateur appartient bien à l'entreprise demandée
            $hasAccess = $user->companies()->where('companies.id', $companyId)->exists();
            
            if ($hasAccess) {
                class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Instancie l'entreprise active dans le conteneur global
                app()->instance('active_company', $user->companies()->find($companyId));
            } else {
                return response()->json([
                    'error' => 'Contexte entreprise invalide. Veuillez rafraîchir la page.'
                ], 403);
            }
        }

        return $next($request);
    }
}

3. Mécanisme de Récupération Transparente de Session

Si une requête HTTP échoue avec un code 401 ou 419, tentez de renouveler silencieusement le jeton de session avant de rediriger l'utilisateur vers la page de connexion.

ts
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// lib/http-client.ts (Intercepteur de réponse)
httpClient.interceptors.response.use(
    (response) => response,
    async (error) => {
        const originalRequest = error.config;

        if ((error.response?.status === 401 || error.response?.status === 419) && !originalRequest._retry) {
            originalRequest._retry = true;
            
            try {
                class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Demande de renouvellement de cookie de session
                await axios.get('/api/csrf-cookie');
                
                class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Relance la requête originale
                return httpClient(originalRequest);
            } catch (refreshError) {
                class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Redirection forcée uniquement si le renouvellement échoue
                localStorage.removeItem('active_company_id');
                window.location.href = '/login?session_expired=true';
            }
        }
        return Promise.reject(error);
    }
);

L'utilisation combinée d'un intercepteur synchronisé et d'un middleware de vérification préserve les sessions utilisateur lors des bascules d'environnements SaaS.

Engineering Collaboration

Building a High-Scale SaaS or Complex System?

Let’s discuss your architecture, full-stack development, or system modernization needs.

Articles Similaires

// BACKEND & PIPELINE ENGINEERING
PRODUCTION VERIFIED
TECHNICAL CASE STUDY

Exportation de Données Massives : Éviter les Surcharges Mémoire avec Laravel & Vue 3

PHP 8.3QUEUESPARALLEL WORKERS
Backend
2026-03-25•Backend

Exportation de Données Massives : Éviter les Surcharges Mémoire avec Laravel & Vue 3

Read Article
// FRONTEND & UI RENDERING
PRODUCTION VERIFIED
TECHNICAL CASE STUDY

Moderniser un Moteur de Contenu : Migration d'un Blog Laravel Blade vers Vue 3 avec Recherche & Recommandations

VUE 3NEXT.JS 15TAILWIND
Frontend
2026-03-15•Frontend

Moderniser un Moteur de Contenu : Migration d'un Blog Laravel Blade vers Vue 3 avec Recherche & Recommandations

Read Article