Fortify React Applications: Securely Manage Sensitive Data
Activate robust security within your React applications. The digital frontier demands impregnable defenses, especially when handling sensitive user data. This authoritative resource decodes the inherent dangers of storing critical information, such as authentication tokens and passwords, directly within browser-side mechanisms like LocalStorage. We expose the vulnerabilities that transform client-side storage into a security liability, empowering you to preempt catastrophic data breaches and uphold user trust.
Forge a mastery of secure data handling strategies. This article engineers a comprehensive framework for safeguarding sensitive information, moving beyond superficial fixes to foundational architectural shifts. We reveal why cleartext storage is a critical misstep and how to implement advanced techniques that protect against pervasive threats like XSS. Prepare to transform your approach to application security, ensuring that your React deployments are not only functional but resilient. This is an essential read for any developer committed to building enterprise-grade applications, particularly those focused on optimizing the secure and efficient shipping and scaling of React applications globally.
The Peril of Plaintext: Why LocalStorage Fails for Sensitive Data
Deconstruct the fundamental flaws within LocalStorage, a seemingly convenient client-side storage mechanism. We activate a critical understanding: LocalStorage, by its very design, offers no inherent security. Any JavaScript code executing within the same origin can trivially access its entire contents. This vulnerability transforms into a direct conduit for malicious actors exploiting
Cross-Site Scripting (XSS) attacks.
An XSS vulnerability allows an attacker to inject and execute arbitrary scripts in a user's browser. If your authentication token or password resides inLocalStorage, that malicious script simply reads it, transmits it to the attacker, and compromises the user's session. We forge the understanding that browser storage is engineered for user preferences, not security-critical data.Illuminate further attack vectors. While HTTPS encrypts transit, it cannot protect data once it resides on the client and is susceptible to XSS. A successful XSS attack bypasses HTTPS client-side. Even
Man-in-the-Middle (MITM) attacks
, though typically mitigated by HTTPS, can, in highly sophisticated scenarios (e.g., certificate compromise), still lead to client-side code injection. Considerphysical access vulnerabilities
: if an attacker gains control of a user's device, they can readily inspectLocalStorage contents. We demand a re-evaluation of common developer misconceptions, dispelling the myth that obscurity or local storage somehow equates to security. This mechanism provides persistence; it does not provide confidentiality or integrity against client-side compromise. Recognize LocalStorage as an open book to any script executing on the page.Architecting Immutable Security: Shifting Critical Data to the Server
Engineer a paradigm shift in your security architecture: critical data, including authentication tokens, sensitive user information, and session state, must reside exclusively on the server side. We decode the immutable principle:
'Never trust the client.'
The client environment, inherently exposed to user interaction and potential compromise, cannot serve as the authoritative source or secure repository for sensitive credentials. We advocate for server-managed sessions and the strategic deployment ofHttpOnly cookies
. This architectural mandate isolates sensitive data from direct JavaScript access, thereby significantly mitigating XSS risks by cutting off the primary attack vector for token theft.Activate a robust security perimeter. Every client request demanding access to protected resources must undergo rigorous
server-side validation and authorization
. This enforces a zero-trust model where the server independently verifies the legitimacy of each operation, irrespective of client-side assertions. This approach not only secures data but also fortifies the application's integrity against various forms of tampering and unauthorized access. Explore the architectural implications: while shifting state to the server might introduce considerations for statelessness in microservices, secure session management can be engineered efficiently using distributed caches. This strategy not only enhances security but also simplifies client-side logic, allowing React applications to focus on user experience while the backend rigorously manages security and data sovereignty.Masterful Token Management: Implementing HttpOnly Cookies in React Applications
Forging secure authentication demands the deployment of
HttpOnly cookies
as the bedrock for handling authentication tokens, whether JSON Web Tokens (JWTs) or session IDs. We illustrate a robust flow: the backend initiates and sets these cookies upon successful user authentication. Critically, theHttpOnly flag instructs the browser to render the cookie inaccessible to client-side JavaScript. This mechanism robustly defends against XSS-driven token theft, as an injected script simply cannot read the authentication credential. The browser, however, automatically includes these cookies in subsequent HTTP requests to the same domain, allowing for seamless, secure authentication without client-side exposure.Implement a meticulous secure authentication flow. After a user provides credentials, the server validates them and responds by setting an HttpOnly cookie containing the session or access token. The React frontend then proceeds with authenticated requests, relying on the browser to append the cookie automatically. We illuminate the necessity of
CSRF (Cross-Site Request Forgery) protection mechanisms
. Configure theSameSite attribute of your cookies (e.g., Lax or Strict) to restrict when cookies are sent on cross-site requests. For enhanced protection, engineer server-generated CSRF tokens, which the client includes in non-GET requests, allowing the server to verify request authenticity. This dual-layered defense is paramount. Furthermore, develop refresh token strategies
: issue a short-lived access token via HttpOnly cookie and a longer-lived refresh token (also HttpOnly) to securely obtain new access tokens without requiring re-authentication, balancing security with user experience./*
To set up this demonstration:
1. Backend (Node.js/Express):
- Create a file named `server.js`.
- `npm init -y`
- `npm install express jsonwebtoken cookie-parser cors`
- Paste the 'Backend (Node.js/Express) Example' code below into `server.js`.
- Run the backend: `node server.js`
2. Frontend (React):
- Ensure your React app is running (e.g., `npm start`).
- Replace the content of your `App.js` with the 'React Frontend (App.js) Example' code below.
- Access your React app in the browser (e.g., `http://localhost:3000`).
- Interact: Login (user/pass), fetch protected data, logout.
*/
// ----------------------------------------------------------------
// Backend (Node.js/Express) Example: Setting HttpOnly Cookies
// This code demonstrates the server-side action. Frontend React interacts indirectly.
// ----------------------------------------------------------------
const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const cors = require('cors'); // Essential for handling CORS if frontend is on different origin
const app = express();
const PORT = 3001;
// IMPORTANT: In a real application, SECRET_KEY must come from environment variables or a secure vault!
const SECRET_KEY = 'YOUR_SUPER_SECRET_KEY_REPLACE_ME_IN_PROD';
app.use(express.json());
app.use(cookieParser());
// Configure CORS to allow credentials (cookies) from your React app's origin
// Replace 'http://localhost:3000' with your actual React app's URL in production.
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));
// --- User Login Endpoint ---
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
// Simulate user authentication: In a real app, validate against a database.
if (username === 'user' && password === 'pass') {
// Generate an access token. This token itself is not sent to the client-side JS.
const accessToken = jwt.sign({ userId: 123, username: username }, SECRET_KEY, { expiresIn: '15m' });
// Set the access token as an HttpOnly cookie.
// httpOnly: true -> CRUCIAL for security; client-side JavaScript cannot access.
// secure: true -> Only send cookie over HTTPS. Use `process.env.NODE_ENV === 'production'` for adaptability.
// sameSite: 'Lax' -> Helps protect against some CSRF attacks by limiting cookie sending on cross-site requests.
// maxAge: Duration for which the cookie is valid (e.g., 15 minutes in milliseconds).
res.cookie('accessToken', accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'Lax',
maxAge: 15 * 60 * 1000
});
// Send a success message or non-sensitive user data back to the client.
return res.status(200).json({ message: 'Login successful!', user: { id: 123, username: username } });
} else {
return res.status(401).json({ message: 'Invalid credentials' });
}
});
// --- Protected Endpoint Example ---
// This endpoint requires a valid HttpOnly 'accessToken' cookie.
app.get('/api/protected', (req, res) => {
const token = req.cookies.accessToken; // Access the HttpOnly cookie from the request.
if (!token) {
return res.status(401).json({ message: 'No token, authorization denied' });
}
try {
const decoded = jwt.verify(token, SECRET_KEY);
// Attach decoded user information to the request for subsequent use.
req.user = decoded;
res.status(200).json({ message: `Access granted, welcome ${req.user.username}!`, data: 'This is highly sensitive and protected data.' });
} catch (error) {
console.error('Token verification error:', error);
res.status(403).json({ message: 'Token is not valid or expired' });
}
});
// --- Logout Endpoint ---
// Clears the HttpOnly cookie, effectively logging the user out.
app.post('/api/logout', (req, res) => {
res.clearCookie('accessToken', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'Lax'
});
res.status(200).json({ message: 'Logged out successfully' });
});
// Start the backend server.
app.listen(PORT, () => {
console.log(`Backend server running on http://localhost:${PORT}`);
});
// ----------------------------------------------------------------
// React Frontend (App.js) Example: Interacting with HttpOnly Cookies
// This code demonstrates how React applications make requests that automatically send HttpOnly cookies.
// ----------------------------------------------------------------
import React, { useState } from 'react';
function App() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [message, setMessage] = useState('');
const [protectedData, setProtectedData] = useState('');
const API_BASE_URL = 'http://localhost:3001/api'; // Ensure this matches your backend URL
// Crucial: Configure fetch to send credentials (cookies) with cross-origin requests.
// 'include' ensures cookies are sent even to different origins (if CORS allows).
const fetchOptions = {
credentials: 'include'
};
const handleLogin = async (e) => {
e.preventDefault();
setMessage('');
try {
const response = await fetch(`${API_BASE_URL}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
...fetchOptions
});
const data = await response.json();
if (response.ok) {
setMessage(data.message);
} else {
setMessage(`Login failed: ${data.message}`);
}
} catch (error) {
setMessage(`Error during login: ${error.message}`);
}
};
const fetchProtectedData = async () => {
setMessage('');
setProtectedData('');
try {
const response = await fetch(`${API_BASE_URL}/protected`, {
method: 'GET',
...fetchOptions
});
const data = await response.json();
if (response.ok) {
setProtectedData(data.data);
setMessage(data.message);
} else {
setMessage(`Access denied: ${data.message}`);
}
} catch (error) {
setMessage(`Error fetching protected data: ${error.message}`);
}
};
const handleLogout = async () => {
setMessage('');
setProtectedData('');
try {
const response = await fetch(`${API_BASE_URL}/logout`, {
method: 'POST',
...fetchOptions
});
const data = await response.json();
if (response.ok) {
setMessage(data.message);
} else {
setMessage(`Logout failed: ${data.message}`);
}
} catch (error) {
setMessage(`Error during logout: ${error.message}`);
}
};
return (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h2>React Secure Authentication Demo</h2>
<p>
<strong>Note:</strong> This frontend demonstrates interaction with a backend that sets HttpOnly cookies.
The React app itself does <strong>not</strong> directly access or store the authentication token.
It relies on the browser to send the cookie automatically with authenticated requests.
</p>
<h3>Login</h3>
<form onSubmit={handleLogin}>
<input
type="text"
placeholder="Username (e.g., user)"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{ marginRight: '10px', padding: '8px' }}
/>
<input
type="password"
placeholder="Password (e.g., pass)"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ marginRight: '10px', padding: '8px' }}
/>
<button type="submit" style={{ padding: '8px 15px' }}>Login</button>
</form>
<h3 style={{ marginTop: '20px' }}>Protected Resource</h3>
<button onClick={fetchProtectedData} style={{ padding: '8px 15px' }}>Fetch Protected Data</button>
{protectedData && <p><strong>Data:</strong> {protectedData}</p>}
<h3 style={{ marginTop: '20px' }}>Logout</h3>
<button onClick={handleLogout} style={{ padding: '8px 15px' }}>Logout</button>
{message && <p style={{ marginTop: '20px', color: 'blue' }}>{message}</p>}
<p style={{ marginTop: '30px', fontSize: '0.9em', color: '#666' }}>
To run this example:
<br />1. Set up the Node.js backend (server.js) as instructed above.
<br />2. In a React project, replace App.js content with this code.
<br />3. Run both the backend (e.g., <code>node server.js</code>) and the React app (e.g., <code>npm start</code>).
</p>
</div>
);
}
export default App;
Ephemeral Security and Client-Side Encryption for Non-Critical Data
While critical data belongs firmly server-side, specific scenarios may necessitate temporary client-side residence for non-critical, sensitive information. We activate strategies for this niche requirement. Employ
in-memory state management solutions
such as Redux, Zustand, or React Context for ephemeral data. This ensures that data persists only for the duration of the user's active session within the browser tab, vanishing automatically upon tab closure or refresh. This approach mitigates persistent client-side exposure, ideal for transient user inputs or application state containing PII (Personally Identifiable Information) that does not underpin security decisions.For truly essential client-side persistence of sensitive *but not critical* data (e.g., encrypted user preferences, non-authentication-related PII where server interaction is impractical), we consider
client-side encryption
. We decode its inherent challenges: the paramount issue ofkey management
. A robust strategy involves deriving encryption keys from short-lived, server-issued session tokens. These keys must resideexclusively in memory
and never be persistently stored client-side (e.g., inLocalStorage). This creates a temporary, isolated decryption environment. The encrypted data stored in LocalStorage becomes unreadable once the in-memory key is lost (e.g., browser tab closed). We mandate a clear understanding: client-side encryption is a last-resort mitigation, never a substitute for the fundamental security provided by server-side data management. It augments, rather than replaces, core security principles./*
To set up this demonstration:
1. Frontend (React):
- `npm install crypto-js`
- Paste the 'Encryption Utility Functions' and 'React Component Example' code below into your `App.js` or a new component file.
- Ensure your React app is running (e.g., `npm start`).
- Access your React app in the browser (e.g., `http://localhost:3000`).
- Interact: Enter a preference, save it encrypted, clear it.
*/
// ----------------------------------------------------------------
// Encryption Utility Functions
// ----------------------------------------------------------------
import CryptoJS from 'crypto-js'; // npm install crypto-js
/**
* Encrypts data using AES-256 with a given key.
* @param {string} data The data to encrypt.
* @param {string} key The encryption key. This key should be volatile and never persisted client-side.
* @returns {string|null} The encrypted data (Base64 encoded) or null if encryption fails.
*/
const encryptData = (data, key) => {
if (!key || key.length === 0) {
console.error("Encryption key is required and cannot be empty.");
return null;
}
try {
// AES-256 encryption. toString() converts the WordArray to a Base64 string.
const encrypted = CryptoJS.AES.encrypt(data, key).toString();
return encrypted;
} catch (error) {
console.error("Encryption failed:", error);
return null;
}
};
/**
* Decrypts data using AES-256 with a given key.
* @param {string} encryptedData The encrypted data (Base64 encoded).
* @param {string} key The encryption key. Must be the same key used for encryption.
* @returns {string|null} The decrypted data (UTF-8 string) or null if decryption fails.
*/
const decryptData = (encryptedData, key) => {
if (!key || key.length === 0) {
console.error("Decryption key is required and cannot be empty.");
return null;
}
try {
// Decrypt the Base64 string back to a WordArray.
const decrypted = CryptoJS.AES.decrypt(encryptedData, key);
// Convert the WordArray to a UTF8 string.
return decrypted.toString(CryptoJS.enc.Utf8);
} catch (error) {
console.error("Decryption failed, likely due to incorrect key or corrupt data:", error);
return null;
}
};
// ----------------------------------------------------------------
// React Component Example: Encrypted Client-Side Preferences
// ----------------------------------------------------------------
import React, { useState, useEffect, createContext, useContext } from 'react';
// 1. Create a Context for the Session Key (in-memory only)
// This context holds the encryption key, which should *never* be persisted.
const SessionKeyContext = createContext(null);
const SessionKeyProvider = ({ children }) => {
const [sessionKey, setSessionKey] = useState(null);
useEffect(() => {
// Simulate fetching a session key from a secure backend after user login.
// IMPORTANT: In a real application, this key MUST be unique per session,
// generated by the server, and securely transmitted (e.g., via a secure API response body
// or a temporary HttpOnly cookie that's immediately consumed).
// NEVER hardcode keys in production.
const simulatedFetchSessionKey = () => {
// For demonstration, use a simple key. In production, this would be cryptographically strong.
// Example: A UUID or a securely random generated string.
return 'MyVolatileSessionKey123ABCxyz!';
};
const key = simulatedFetchSessionKey();
setSessionKey(key);
console.log("Session key initialized (in-memory only). This key is NOT stored persistently.");
}, []);
return (
<SessionKeyContext.Provider value={sessionKey}>
{children}
</SessionKeyContext.Provider>
);
};
// 2. Component demonstrating encrypted local storage for *non-critical* data
function EncryptedPreferenceManager() {
const sessionKey = useContext(SessionKeyContext);
const [preference, setPreference] = useState('');
const [displayPreference, setDisplayPreference] = useState('');
const localStorageKey = 'encryptedUserPreference'; // Key for LocalStorage entry
useEffect(() => {
// Attempt to decrypt and load preference from LocalStorage when session key becomes available.
if (sessionKey) {
const storedEncrypted = localStorage.getItem(localStorageKey);
if (storedEncrypted) {
const decrypted = decryptData(storedEncrypted, sessionKey);
if (decrypted) {
setPreference(decrypted);
setDisplayPreference(decrypted);
console.log("Loaded and decrypted preference from LocalStorage:", decrypted);
} else {
console.warn("Could not decrypt stored preference. Data might be corrupt or key incorrect.");
setPreference('');
setDisplayPreference('');
}
}
}
}, [sessionKey]); // Re-run effect if the sessionKey changes.
const handleSavePreference = () => {
if (!sessionKey) {
console.error("Cannot save: No session key available for encryption. Please ensure it's initialized.");
return;
}
if (!preference) {
console.warn("No preference entered to save.");
return;
}
const encrypted = encryptData(preference, sessionKey);
if (encrypted) {
localStorage.setItem(localStorageKey, encrypted);
setDisplayPreference(preference); // Show the unencrypted version to the user.
console.log("Preference encrypted and saved to LocalStorage.");
console.log("Raw encrypted data in LocalStorage (for inspection):
", encrypted);
}
};
const handleClearPreference = () => {
localStorage.removeItem(localStorageKey);
setPreference('');
setDisplayPreference('');
console.log("Preference cleared from LocalStorage.");
};
return (
<div style={{ marginTop: '30px', borderTop: '1px solid #eee', paddingTop: '20px' }}>
<h3>Encrypted Client-Side Preferences (Non-critical Data)</h3>
<p>
This example demonstrates storing sensitive, but <strong>non-critical</strong>, user preferences
(e.g., PII like a user's chosen display name or specific content filters) in LocalStorage after client-side encryption.
The encryption relies on a <strong>volatile, in-memory session key</strong>, meaning if the browser tab is closed
and the key is lost, the data in LocalStorage becomes unreadable. This is a last-resort strategy;
server-side storage is always preferred for truly sensitive information.
</p>
<input
type="text"
placeholder="Enter sensitive preference (e.g., 'John Doe's secret list')"
value={preference}
onChange={(e) => setPreference(e.target.value)}
style={{ marginRight: '10px', padding: '8px', width: '300px' }}
/>
<button onClick={handleSavePreference} style={{ padding: '8px 15px', marginRight: '10px' }}>
Save Encrypted Preference
</button>
<button onClick={handleClearPreference} style={{ padding: '8px 15px' }}>
Clear LocalStorage
</button>
{displayPreference && <p style={{ marginTop: '15px' }}><strong>Current Preference (Decrypted for Display):</strong> {displayPreference}</p>}
{!sessionKey && <p style={{color: 'red', marginTop: '15px'}}>Waiting for session key to be initialized...</p>}
<p style={{ marginTop: '30px', fontSize: '0.8em', color: '#888' }}>
To integrate this into your React app (e.g., in <code>App.js</code>):
<br />1. Install crypto-js: <code>npm install crypto-js</code>
<br />2. Wrap your application content with the <code>SessionKeyProvider</code>, and place <code>EncryptedPreferenceManager</code> where desired.
<br /> Example: <code><SessionKeyProvider><YourAppComponentContent /><EncryptedPreferenceManager /></SessionKeyProvider></code>
</p>
</div>
);
}
// Example of how to structure your App.js
/*
import React from 'react';
// ... other imports ...
function App() {
return (
<SessionKeyProvider>
<div className="App">
<h1>My React Application</h1>
// ... your main app content goes here ...
<EncryptedPreferenceManager />
</div>
</SessionKeyProvider>
);
}
export default App;
*/
Fortifying React Applications: Essential Security Best Practices
To truly conquer the development frontier, we must activate a comprehensive security posture extending beyond specific data storage mechanisms. We demand
strict adherence to HTTPS
across all environments, ensuring every communication channel remains encrypted and protected against eavesdropping and tampering. Implement robustContent Security Policies (CSP)
to mitigate XSS attacks by explicitly whitelisting trusted sources for scripts, styles, and other content, effectively blocking unauthorized code injection. Forge a culture of continuoussecurity audits and penetration testing
, regularly challenging your application's defenses with the mindset of an attacker. This proactive validation identifies and remediates vulnerabilities before they can be exploited in the wild.Educate your development coalition on the
OWASP Top 10 vulnerabilities
, fostering an ingrained understanding of common security pitfalls. Engineer rigorousinput validation and output encoding
for every user-supplied data point, preventing injection attacks and ensuring data integrity. We mandate thatno secrets
– API keys, database credentials, or sensitive configuration parameters – are ever hardcoded or exposed client-side. Utilize environment variables, secure configuration services, or backend proxy servers to manage sensitive credentials. Cultivate awareness of third-party library dependencies; regularly audit them for known vulnerabilities and keep them updated. This multi-layered, proactive defense strategy builds an impregnable React application, ready for any challenge on the digital frontier.Key Takeaways
LocalStorage: An Open Book for Sensitive Data
LocalStorage fundamentally lacks security; any JavaScript on the page can trivially access its contents. This makes it a direct conduit for XSS attacks to steal passwords, authentication tokens, and PII. Never store critical sensitive data here.
Server-Side is the Only Secure Frontier for Critical Data
True security for authentication tokens and highly sensitive user data demands server-side residence. Adopt a 'never trust the client' philosophy. Implement server-managed sessions and rigorously validate all requests on the backend.
HttpOnly Cookies: Your Shield Against XSS Token Theft
Deploy HttpOnly cookies for authentication tokens. This browser-level security flag prevents client-side JavaScript from accessing the cookie, blocking XSS from stealing session credentials. Integrate CSRF protection (e.g., SameSite attribute, CSRF tokens) for comprehensive defense.
Client-Side Encryption: A Measured Risk for Non-Critical Data
For non-critical sensitive data requiring temporary client-side persistence, consider in-memory state management or client-side encryption. Crucially, any encryption key must be volatile and stored only in memory (e.g., derived from a short-lived, server-issued session token), never persistently client-side. This is a last-resort, not a primary security solution.
Comprehensive Security Posture: Beyond Storage
Fortify your React applications with essential practices: enforce HTTPS, implement Content Security Policy (CSP), conduct regular security audits, validate all user inputs, and never hardcode secrets. Continuous vigilance across all layers is paramount for an impregnable application.
FAQ
-
Can I encrypt sensitive data before storing it in LocalStorage to make it safe?
Yes, you can encrypt data before storing it in
LocalStorage, but this introduces significantkey management challenges
. The encryption key itself cannot be securely stored persistently client-side alongside the encrypted data, as any client-side attack (like XSS) that could read the data could also read the key. A more viable, though still limited, strategy involves deriving a temporary encryption key from a short-lived, server-issued session token, storing this keyonly in memory
(e.g., in React state or context). If the key is lost (e.g., browser tab closes), theLocalStoragedata becomes unreadable. This approach is a last resort and still carries risks compared to server-side data storage. -
What about SessionStorage? Is it safer than LocalStorage for sensitive data?
SessionStorageoffers a marginal security improvement overLocalStorageprimarily because its data is cleared automatically when the browser tab or window closes. However, it suffers from the same fundamental vulnerability: any XSS attack can still access its contents during the active session. This means if an attacker injects malicious JavaScript, they can still read the contents ofSessionStoragewhile the user is active. Therefore, it isnot suitable for authentication tokens or highly sensitive data
that must be protected from client-side access. -
How do HttpOnly cookies prevent XSS from accessing authentication tokens?
HttpOnly cookies are a critical browser security feature.
When a cookie is set with theHttpOnlyflag, it instructs the browser to make that cookie inaccessible to client-side JavaScript (i.e., thedocument.cookieAPI). While the browser will still automatically send these cookies with HTTP requests to the correct domain, JavaScript running on the page cannot read, write, or modify them. This robustly preventsXSS attacks
from stealing authentication tokens, even if malicious scripts are successfully injected, thereby significantly reducing the risk of session hijacking.