How to Secure SWR Library in React

This article provides a concise guide on how to secure the SWR (Stale-While-Revalidate) data-fetching library in React applications. You will learn the essential security practices for SWR, including secure credential handling, clearing cached data upon user logout, preventing parameter injection, and mitigating Cross-Site Scripting (XSS) risks.

1. Secure Authorization and Token Handling

SWR itself does not handle authentication; it relies on your fetcher function. To prevent token leakage, pass authorization credentials securely through SWR keys or a React Context rather than relying on global variables.

When using Bearer tokens, pass the token as part of the SWR key array. This ensures that SWR automatically re-fetches data whenever the token changes and keeps the cache scoped to the active session.

import useSWR from 'swr';

const fetcher = ([url, token]) => 
  fetch(url, {
    headers: { Authorization: `Bearer ${token}` }
  }).then(res => {
    if (!res.ok) throw new Error('Network error');
    return res.json();
  });

function UserProfile({ token }) {
  // Passing token in the key array secures cache separation
  const { data, error } = useSWR(token ? ['/api/user', token] : null, fetcher);
  
  if (error) return <div>Failed to load profile</div>;
  if (!data) return <div>Loading...</div>;
  return <div>Welcome, {data.name}</div>;
}

2. Clear SWR Cache on Logout

Because SWR stores cached data globally in-memory, sensitive user data may persist in the cache even after a user logs out. If another user logs into the same browser session, they might view stale, unauthorized cached data.

To prevent this, clear the SWR cache completely upon logout using the mutate configuration.

import { useSWRConfig } from 'swr';

function LogoutButton() {
  const { mutate } = useSWRConfig();

  const handleLogout = () => {
    // Perform backend logout operations...
    
    // Clear all SWR cache keys
    mutate(
      () => true, // Match all keys in the cache
      undefined,  // Set data to undefined
      { revalidate: false } // Do not revalidate the cleared keys
    );
    
    // Redirect user to login page
  };

  return <button onClick={handleLogout}>Log Out</button>;
}

3. Prevent Parameter Injection in SWR Keys

SWR uses the fetcher arguments as its cache key. If you dynamically build request URLs based on unvalidated user input, attackers could manipulate the query parameters to access unauthorized endpoints or poison the cache.

Always sanitize user inputs and structure your keys using arrays rather than manually concatenating strings.

// AVOID: Vulnerable to parameter injection
const { data } = useSWR(`/api/search?q=${userInput}`, fetcher);

// SAFE: Let SWR handle parameters safely or serialize inputs
const { data } = useSWR(
  userInput ? ['/api/search', userInput] : null,
  ([url, query]) => {
    const params = new URLSearchParams({ q: query });
    return fetch(`${url}?${params}`).then(res => res.json());
  }
);

4. Protect Against Cross-Site Scripting (XSS) in Cached Data

If your API responds with user-generated content, SWR will cache and serve that payload. If an attacker injects malicious scripts into the API database, SWR will faithfully return that script, leading to stored XSS when rendered.

Always sanitize or escape fetched data before rendering it in the React DOM, especially when using dangerouslySetInnerHTML.

import DOMPurify from 'dompurify';
import useSWR from 'swr';

function CommentComponent({ commentId }) {
  const { data } = useSWR(`/api/comments/${commentId}`, fetcher);

  if (!data) return null;

  // Sanitize the HTML payload from SWR cache before rendering
  const cleanHTML = DOMPurify.sanitize(data.content);

  return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}

5. Implement Secure HTTP-Only Cookies

The most secure way to handle sessions with SWR is to avoid storing JWTs in Javascript memory or LocalStorage altogether. Configure your backend to use HttpOnly, Secure, and SameSite cookies for authentication.

When configuring SWR to fetch from cross-origin resource sharing (CORS) APIs, ensure your fetcher includes the credentials flag:

const fetcher = (url) => 
  fetch(url, { credentials: 'include' }).then(res => res.json());