Sécurité des tokens Auth0 : prévenir XSS, CSRF et vol de session

Où stocker les tokens ? Comment prévenir le vol via XSS et CSRF ? Guide complet sur la sécurité des tokens dans les SPAs et applications mobiles.

Par Auth0.Expert · 2025-03-05 · 11 min de lecture

XSS CSRF Sécurité tokens

---

Sécurité des tokens Auth0 : XSS, CSRF et vol de session

TL;DR

Le vrai débat n'est pas "localStorage vs cookie" mais "quel niveau de risque acceptez-vous ?". Ce guide présente les menaces réelles et les mitigations concrètes.

---

Matrice des risques de stockage

Ce tableau compare les différents modes de stockage de tokens côté client, en évaluant leur exposition aux risques de XSS et CSRF.

| Stockage | Accessible JS | Envoyé auto | Risque XSS | Risque CSRF | |----------|--------------|-------------|------------|-------------| | localStorage | ✅ | ❌ | ⚠️ Élevé | ✅ Immunisé | | sessionStorage | ✅ | ❌ | ⚠️ Élevé | ✅ Immunisé | | Cookie HttpOnly | ❌ | ✅ | ✅ Immunisé | ⚠️ Vulnérable | | Cookie HttpOnly + SameSite=Strict | ❌ | Même domaine | ✅ Immunisé | ✅ Immunisé | | Mémoire (variable) | ✅ | ❌ | ✅ Survit pas refresh | ✅ Immunisé |

---

La solution BFF (Backend For Frontend)

La solution BFF consiste à gérer l’authentification et les tokens côté serveur pour renforcer la sécurité des applications SPA. Elle permet d’éviter l’exposition des tokens dans le navigateur et de limiter les risques de vol via XSS.

Browser (SPA)
    ↕ Cookie HttpOnly + SameSite=Strict (session ID)
BFF Server (Node/Python)
    ↕ Bearer Token (dans la mémoire du serveur)
API Backend

Ce code Node.js illustre comment le serveur BFF échange un code d'authentification contre un token, puis le stocke en session côté serveur, évitant ainsi d'exposer le token au browser.

// BFF: exchanging auth code for token (server-side)
app.get('/callback', async (req, res) => {
  const { code } = req.query;
  const tokenResponse = await auth0.getToken({ code });

  // Stocker le token côté serveur, jamais envoyé au browser
  req.session.accessToken = tokenResponse.access_token;
  req.session.refreshToken = tokenResponse.refresh_token;

  res.redirect('/dashboard');
});

// BFF: proxy les requêtes API
app.get('/api/data', async (req, res) => {
  if (!req.session.accessToken) return res.status(401).json({});

  const data = await fetch('https://api.example.com/data', {
    headers: { Authorization: `Bearer ${req.session.accessToken}` }
  });
  res.json(await data.json());
});

Ce bloc assure que le token d'accès reste sur le serveur et que toutes les requêtes API sont signées en interne.

---

Content Security Policy : défense XSS

La mise en place d'une Content Security Policy (CSP) limite les sources de scripts et contenus, ce qui réduit la surface d'attaque XSS sur votre application.

// Express.js avec helmet
const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'nonce-RANDOM_NONCE'"],
    styleSrc: ["'self'", "https://fonts.googleapis.com"],
    imgSrc: ["'self'", "data:", "https://*.gravatar.com"],
    connectSrc: [
      "'self'",
      "https://YOUR_DOMAIN.auth0.com",
      "https://api.yourapp.com",
    ],
    frameSrc: ["'none'"],
    objectSrc: ["'none'"],
  },
}));

---

CSRF Protection avec SameSite et CSRF Token

Pour protéger contre les attaques CSRF, il est recommandé d'utiliser les attributs de cookie SameSite et un token CSRF dédié pour les formulaires.

// Si vous utilisez des cookies pour les tokens
res.cookie('session', token, {
  httpOnly: true,      // Pas accessible via JS
  secure: true,        // HTTPS uniquement
  sameSite: 'strict',  // Jamais envoyé en cross-site
  maxAge: 24 * 60 * 60 * 1000,
  path: '/',
});

// Pour les formulaires : double submit cookie pattern
app.use(csrf({ cookie: true }));
app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

Cette configuration assure que seuls les utilisateurs légitimes et authentifiés peuvent soumettre une demande.

---

Subresource Integrity (SRI)

L’ajout de Subresource Integrity permet de vérifier que le script CDN chargé n’a pas été altéré, ce qui protège contre des scripts malicieux injectés.

<!-- Protéger les scripts CDN -->
<script
  src="https://cdn.auth0.com/js/auth0-spa-js/2.0/auth0-spa-js.production.js"
  integrity="sha384-HASH_HERE"
  crossorigin="anonymous"
></script>

---

Détection de token volé côté API

Pour détecter un token potentiellement volé, on peut vérifier contextuellement l’IP ou le pays d’origine lors de chaque requête API.

# Fingerprinting léger : stocker l'IP au moment du login
# Alerter si l'IP change drastiquement (pays différent)

async def validate_token_context(token_payload: dict, request: Request):
    token_ip_country = token_payload.get('https://myapp.com/login_country')
    current_country = get_country_from_ip(request.client.host)

    if token_ip_country and token_ip_country != current_country:
        # Log l'anomalie
        await log_security_event('token_country_mismatch', {
            'user': token_payload['sub'],
            'token_country': token_ip_country,
            'request_country': current_country,
        })
        # Ne pas bloquer (faux positifs VPN) mais alerter

Ce bloc permet d’alerter sur un changement suspect lors de l’utilisation du token, sans interrompre brutalement l’accès.

---

Checklist sécurité tokens

Voici une liste d'actions recommandées pour sécuriser le stockage et l’usage des tokens dans votre application Auth0.

---

> 🔒 Audit de sécurité complet : analyse de votre chaîne d'authentification complète, identification des vulnérabilités et plan de remédiation priorisé.

📧 [email protected]


Autres articles