How to Secure useSearchParams in React

The useSearchParams hook in React is a powerful tool for reading and modifying the query string in the URL. However, because query parameters are user-controlled inputs, they can expose your application to security vulnerabilities like Cross-Site Scripting (XSS), open redirects, and prototype pollution. This article provides a straightforward guide on how to secure the useSearchParams hook in React by validating inputs, sanitizing data, and safely handling application state.

1. Validate Query Parameters with Schemas

Because URL parameters are always strings, they must be validated and parsed into the correct data types. Using a schema validation library like Zod ensures that only expected values are processed by your application, mitigating type injection attacks.

import { useSearchParams } from 'react-router-dom';
import { z } from 'zod';

// Define a strict schema for search parameters
const searchSchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  search: z.string().max(100).optional().default(''),
});

export function SecureComponent() {
  const [searchParams] = useSearchParams();

  // Convert URLSearchParams to a plain object
  const paramsObject = Object.fromEntries(searchParams.entries());
  
  // Safely parse the object against the schema
  const result = searchSchema.safeParse(paramsObject);

  if (!result.success) {
    // Fallback or handle validation error
    return <div>Invalid query parameters.</div>;
  }

  const { page, search } = result.data;

  return (
    <div>
      <p>Page: {page}</p>
      <p>Search Query: {search}</p>
    </div>
  );
}

2. Prevent Cross-Site Scripting (XSS)

Never directly render raw values from useSearchParams into dangerous HTML attributes (such as href or src) or pass them directly to dangerouslySetInnerHTML.

If your application reads a URL from query parameters to dynamically generate a link, validate the protocol to prevent javascript: payload execution:

const redirectUrl = searchParams.get('continueTo');

const getSafeUrl = (url) => {
  if (!url) return '/';
  
  try {
    const parsed = new URL(url, window.location.origin);
    // Allow only HTTP and HTTPS protocols to prevent javascript: schemes
    if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
      return parsed.toString();
    }
  } catch {
    // Handle relative paths safely
    if (url.startsWith('/') && !url.startsWith('//')) {
      return url;
    }
  }
  return '/';
};

// Usage
<a href={getSafeUrl(redirectUrl)}>Continue</a>

3. Mitigate Open Redirect Vulnerabilities

If you use useSearchParams to retrieve a redirect path after an action (e.g., ?next=/profile), malicious actors can modify this parameter to redirect users to phishing sites.

To prevent open redirects: * Enforce relative paths: Ensure the target path starts with a single / and does not start with // or a protocol. * Use an allowlist: Only redirect to a predefined set of internal domain routes.

const nextPath = searchParams.get('next');

const safeRedirect = (path) => {
  const isRelative = path && path.startsWith('/') && !path.startsWith('//');
  return isRelative ? path : '/dashboard';
};

4. Limit Parameter Size to Prevent DoS

Large, deeply nested, or repetitive query strings can crash client-side parsing logic or trigger server-side errors. Restrict the maximum character length of the query string before processing it.

const rawQueryString = searchParams.toString();

if (rawQueryString.length > 2048) {
  throw new Error("URL query parameters exceed maximum safe length limit.");
}