Securing React Query in React Applications
React Query (TanStack Query) is an industry-standard library for managing, caching, and syncing server state in React applications. While it simplifies data fetching, keeping your application secure requires specific configurations to protect sensitive data and prevent unauthorized access. This article covers the essential strategies to secure React Query, including authenticating API requests, clearing cached data on logout, disabling devtools in production, and protecting local state storage.
1. Authenticating API Requests Securing Query Functions
React Query does not handle network requests directly; it relies on
fetchers like axios or the fetch API. To secure your
queries, you must ensure that all network requests securely transmit
authentication tokens (like JWTs).
Using Axios interceptors is the most effective way to attach authorization headers globally:
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('authToken'); // Or secure cookie storage
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});When defining your queryFn, always use the authenticated
client:
const { data } = useQuery({
queryKey: ['userData'],
queryFn: () => api.get('/user').then(res => res.data),
});2. Clearing Query Cache on User Logout
React Query caches fetched data in memory. If a user logs out and another user logs in on the same browser session, the new user might temporarily see the previous user’s sensitive data before a refetch occurs.
To prevent this, clear the query cache immediately when a user logs out:
import { useQueryClient } from '@tanstack/react-query';
const useLogout = () => {
const queryClient = useQueryClient();
const logout = () => {
// 1. Remove auth tokens from storage
localStorage.removeItem('authToken');
// 2. Clear all cached queries in React Query
queryClient.clear();
};
return logout;
};Using queryClient.clear() destroys all cached data,
ensuring zero leakage of stale, authenticated data.
3. Disabling React Query DevTools in Production
The React Query DevTools are excellent for debugging, but they expose your entire cache, query keys, and variables to anyone inspecting your application.
Ensure that DevTools are excluded from your production bundle:
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourAppComponents />
{process.env.NODE_ENV === 'development' && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</QueryClientProvider>
);
}This prevents external users from accessing sensitive state payloads in a live environment.
4. Securing Persisted Query Cache
If you use plug-ins like persistQueryClient to persist
cache state across browser reloads (using localStorage or
IndexedDB), you risk exposing sensitive data to Cross-Site
Scripting (XSS) attacks.
To mitigate this risk: * Avoid persisting sensitive
endpoints: Exclude sensitive queries (like billing details or
user profiles) from persistence using the dehydrateOptions
filter. * Encrypt persisted data: If you must persist
sensitive data, encrypt the storage payload before writing it to
localStorage.
const persister = createSyncStoragePersister({
storage: window.localStorage,
serialize: (data) => encryptData(JSON.stringify(data)),
deserialize: (data) => JSON.parse(decryptData(data)),
});5. Sanitizing Query Keys to Prevent Injection
Query keys are serialized internally to track dependencies. If user-generated input is directly injected into a query key, ensure it is sanitized to avoid potential injection vulnerabilities or unexpected cache collisions.
// Avoid directly using unvalidated user input as keys without sanitization
const { data } = useQuery({
queryKey: ['search', sanitizeInput(userInput)],
queryFn: () => fetchResults(userInput),
});