Back to Blog
Blog/Engineering

Resolving Session Loss: State Persistence in Multi-Company SaaS Dashboards

How to design a reliable session and state synchronization layer in hybrid client-server apps to prevent authentication drops during tenant switching.

Akram Hafaiedh
Akram Hafaiedh
Full Stack Developer
2026-02-225 min read
// SYSTEM ARCHITECTURE PATTERN
PRODUCTION VERIFIED
TECHNICAL CASE STUDY

Resolving Session Loss: State Persistence in Multi-Company SaaS Dashboards

LARAVELREDISMULTI-TENANT

Resolving Session Loss in Multi-Company SaaS

In multi-tenant business platforms (like ERP or billing systems), users frequently manage or switch between multiple companies. Designing this flow requires a robust session management system.

A common issue in hybrid SPA architectures is the loss of session persistence: users refreshing the browser or switching between companies suddenly find themselves logged out due to state mismatches between the client local storage and the server session cookies. Let's look at how to build a synchronization layer to resolve this.


The Root Cause: Out-of-Sync Token States

When a user switches from "Company A" to "Company B," the application makes API requests to fetch new datasets. If the client updates its internal active_company_id in LocalStorage, but the server's session token or CSRF cookie is not correctly renewed or linked, request interceptors will return a 401 Unauthorized or 419 Page Expired error, forcing an unnecessary logout.


1. Implementing a Synchronized Auth Interceptor

To prevent token drops during quick navigation, use an Axios interceptor that dynamically syncs active headers on every outgoing request.

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 to pass server-side cookies
});

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">// Automatically retrieve the CRSF token from document cookies for Laravel/Sanctum
    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. Server-Side Context Verification Middleware

In your backend framework, verify that the requested company ID header matches the list of companies associated with the authenticated user. If a mismatch or session expiry is detected, handle the response gracefully rather than wiping the 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">// Confirm the authenticated user belongs to the requested company
            $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">// Bind the active tenant globally to the request context
                app()->instance('active_company', $user->companies()->find($companyId));
            } else {
                return response()->json([
                    'error' => 'Unauthorized company context. Please refresh your session.'
                ], 403);
            }
        }

        return $next($request);
    }
}

3. Graceful Client-Side Session Recovery

If an HTTP request fails with a 401 or 419 error code, try to silently refresh the session token before forcing the user back to the login screen.

ts
class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// lib/http-client.ts (Response Interceptor)
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">// Request a silent CSRF/Session token renewal
                await axios.get('/api/csrf-cookie');
                
                class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Retry the original request
                return httpClient(originalRequest);
            } catch (refreshError) {
                class=class="text-emerald-500 dark:text-emerald-400 font-medium">"text-slate-500 italic">// Force redirection to login only if silent renewal fails
                localStorage.removeItem('active_company_id');
                window.location.href = '/login?session_expired=true';
            }
        }
        return Promise.reject(error);
    }
);

Using a synchronized interceptor and context validation middleware preserves user sessions during navigation and simplifies multi-company account handling.

Engineering Collaboration

Building a High-Scale SaaS or Complex System?

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

Related Articles

// BACKEND & PIPELINE ENGINEERING
PRODUCTION VERIFIED
TECHNICAL CASE STUDY

Streaming Large Financial Exports: Preventing Memory Overflows in Laravel & Vue 3

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

Streaming Large Financial Exports: Preventing Memory Overflows in Laravel & Vue 3

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

Modernizing a Legacy Content Engine: Migrating a Laravel Blade Blog to Vue 3 with Search & Smart Recommendations

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

Modernizing a Legacy Content Engine: Migrating a Laravel Blade Blog to Vue 3 with Search & Smart Recommendations

Read Article