
JSON Web Tokens (JWTs) have become the de-facto standard for authenticating users in modern React and Node.js applications. They are stateless, scalable, and extremely easy to implement.
However, during penetration tests, I consistently find the exact same critical vulnerability across almost 80% of startups: Storing the JWT in localStorage.
localStorage is easily accessible via Javascript. If an attacker manages to inject even a tiny Cross-Site Scripting (XSS) payload into your application (perhaps via an unsanitized comment section or a poisoned NPM dependency), that script can simply execute console.log(localStorage.getItem('token')).
The attacker now has the user's JWT. Because JWTs cannot be easily invalidated like traditional session IDs, the attacker has complete control of the account until the token mathematically expires.
The only secure place to store a JWT in a web browser is inside an HttpOnly cookie.
When your Node.js server authenticates the user, it should manually attach the cookie: ```javascript res.cookie('token', jwtString, { httpOnly: true, // Javascript CANNOT read this! secure: process.env.NODE_ENV === "production", // HTTPS only sameSite: 'strict', // Mitigates CSRF attacks maxAge: 3600000 // 1 hour }); ```
When executing your React fetches, simply include credentials: 'include'. The browser automatically sends the secure cookie. Your JavaScript code never touches the token directly, completely neutralizing XSS token-theft variations.