L'authentification JWT entre Django et React m'a rendu fou. Voici comment j'ai tout simplifié.
Développement Web
En vedette

L'authentification JWT entre Django et React m'a rendu fou. Voici comment j'ai tout simplifié.

Après 3 jours à debug des tokens qui expiraient n'importe quand, j'ai trouvé une solution radicalement simple. Cookies HttpOnly, token plus long, et un seul intercepteur Axios.

19 décembre 2025
8 min de lecture
Par Tokam Darius

L'authentification JWT entre Django et React peut rapidement devenir un cauchemar. Des tokens qui expirent de façon imprévisible, des refresh tokens qui ne fonctionnent pas, des erreurs CORS mystérieuses... J'ai vécu tout ça pendant 3 jours intenses de debug.

Voici comment j'ai finalement tout simplifié.

Le problème initial

Mon setup initial était "classique" :

  • Django REST Framework avec Simple JWT
  • React avec Axios
  • Tokens stockés dans localStorage

Ça semblait simple sur le papier. En pratique, c'était l'enfer.

Les symptômes

  1. Tokens qui expirent aléatoirement - L'utilisateur était déconnecté sans raison apparente
  2. Refresh token qui échoue - Le renouvellement automatique ne fonctionnait pas
  3. Race conditions - Plusieurs requêtes simultanées causaient des conflits

La solution : Simplifier radicalement

Après avoir essayé des dizaines de solutions complexes, voici ce qui a fonctionné.

Côté Django

python
# settings.py
SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),  # Plus long
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": True,
    "UPDATE_LAST_LOGIN": True,
}

# Cookies HttpOnly au lieu de localStorage
SIMPLE_JWT["AUTH_COOKIE"] = "access_token"
SIMPLE_JWT["AUTH_COOKIE_HTTP_ONLY"] = True
SIMPLE_JWT["AUTH_COOKIE_SECURE"] = True
SIMPLE_JWT["AUTH_COOKIE_SAMESITE"] = "Lax"

Côté React

typescript
// api.ts - Configuration Axios simplifiée
const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  withCredentials: true,  // Crucial pour les cookies
});

// Intercepteur unique pour le refresh
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      try {
        await api.post("/auth/refresh/");
        return api.request(error.config);
      } catch {
        // Rediriger vers login
        window.location.href = "/login";
      }
    }
    return Promise.reject(error);
  }
);

Les 3 changements clés

1. Cookies HttpOnly au lieu de localStorage

Avant : Tokens dans localStorage, vulnérables aux attaques XSS.

Après : Cookies HttpOnly gérés automatiquement par le navigateur. Plus sécurisé et plus simple.

2. Token d'accès plus long

Avant : 5 minutes (recommandation "standard").

Après : 60 minutes. Moins de refresh = moins de bugs. Le risque de sécurité est minime avec des cookies HttpOnly.

3. Un seul intercepteur Axios

Avant : Logique complexe avec queues de requêtes et mutex.

Après : Un intercepteur simple qui refresh et retry. C'est tout.

Le code final

Voici la configuration complète qui fonctionne :

python
# Django - views.py
from rest_framework_simplejwt.views import TokenObtainPairView

class CookieTokenObtainPairView(TokenObtainPairView):
    def finalize_response(self, request, response, *args, **kwargs):
        if response.data.get("access"):
            response.set_cookie(
                "access_token",
                response.data["access"],
                max_age=3600,
                httponly=True,
                secure=True,
                samesite="Lax"
            )
            del response.data["access"]
        return super().finalize_response(request, response, *args, **kwargs)
typescript
// React - AuthContext.tsx
export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  const login = async (email: string, password: string) => {
    await api.post("/auth/login/", { email, password });
    const { data } = await api.get("/auth/me/");
    setUser(data);
  };

  const logout = async () => {
    await api.post("/auth/logout/");
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

Leçons apprises

  1. La simplicité gagne toujours - Les solutions complexes créent des bugs complexes
  2. Les cookies HttpOnly sont sous-estimés - Plus sécurisés ET plus simples
  3. Ne suivez pas aveuglément les best practices - Un token de 5 minutes n'est pas toujours nécessaire
  4. Testez les edge cases - Plusieurs onglets, perte de connexion, tokens expirés simultanément

Conclusion

L'authentification JWT n'a pas besoin d'être compliquée. En passant aux cookies HttpOnly et en simplifiant ma logique de refresh, j'ai éliminé tous mes bugs d'authentification.

Le code est maintenant plus court, plus lisible, et surtout : il fonctionne.

Si vous galérez avec JWT entre Django et React, essayez cette approche. Vous me remercierez.

Tags

Authentification
Django
JWT
React
TypeScript

A lire aussi

T

Tokam Darius

Développeur web à Jonquière, au Saguenay. Je conçois et je code des applications web sur mesure en Django, React et TypeScript.