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.
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
- Tokens qui expirent aléatoirement - L'utilisateur était déconnecté sans raison apparente
- Refresh token qui échoue - Le renouvellement automatique ne fonctionnait pas
- 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
# 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
// 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 :
# 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)
// 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
- La simplicité gagne toujours - Les solutions complexes créent des bugs complexes
- Les cookies HttpOnly sont sous-estimés - Plus sécurisés ET plus simples
- Ne suivez pas aveuglément les best practices - Un token de 5 minutes n'est pas toujours nécessaire
- 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
A lire aussi
Ma formule pour estimer un projet web sans me planter
Un mandat « refonte simple » estimé à 40 heures qui en prend 122 : le genre de dépassement qui te fait travailler à 26 $ l'heure sans t'en rendre compte. Voici la formule d'estimation que j'applique depuis, avec multiplicateur par type de client et marges fixes pour les zones grises récurrentes.
Jongler avec 6 clients à la fois sans rien laisser tomber
Gérer six projets clients en solo sans échapper une démo ni brûler ses weekends tient à quatre habitudes simples : une revue du lundi, un canal unique par client, trois signaux d'alarme et un tableau de capacité honnête. Voici le système, et pourquoi il tue l'anxiété du dimanche soir.
Facturer au forfait ou à l'heure : ce que j'ai appris en freelance
Le forfait mal cadré m'a fait travailler à moitié prix. Le taux horaire pur a puni ma rapidité. Voici comment j'ai fini par structurer mes devis avec un modèle hybride, cadrage payé puis heures plafonnées, et ce que ça a changé dans ma relation client.

