How to Secure useLocation Hook in React

The useLocation hook in React Router is a fundamental tool for accessing the current URL’s location object, but it can introduce security vulnerabilities like Cross-Site Scripting (XSS) and Open Redirects if user-controlled input is trusted blindly. This article explains the primary security risks associated with useLocation and provides straightforward, actionable strategies to secure your React application.

Understanding the Security Risks

The useLocation hook returns a location object containing properties like pathname, search, hash, and state. Because users can manipulate the URL directly, and attackers can craft malicious links, any data extracted from location must be treated as untrusted user input.

The two main vulnerabilities are: 1. Cross-Site Scripting (XSS): Occurs when query parameters or state values are rendered directly into the DOM or passed into dangerous sinks (like dangerouslySetInnerHTML) without sanitization. 2. Open Redirects: Occurs when your application uses URL parameters or location.state to redirect users after an action (like logging in) to an external, malicious website.


Best Practices to Secure useLocation

1. Validate and Sanitize Query Parameters

When extracting query parameters from location.search (often parsed using URLSearchParams), never render them directly in the UI or use them to fetch data without validation.

import { useLocation } from 'react-router-dom';
import DOMPurify from 'dompurify';

function SearchComponent() {
  const location = useLocation();
  const queryParams = new URLSearchParams(location.search);
  const rawQuery = queryParams.get('query') || '';

  // Sanitize the input to prevent XSS
  const safeQuery = DOMPurify.sanitize(rawQuery);

  return <div>Search results for: {safeQuery}</div>;
}

2. Safely Handle Redirects

A common pattern is to redirect users to a “destination” URL stored in location.state or a query parameter after they log in. Attackers can exploit this to redirect users to phishing sites.

To prevent open redirects, validate that the target URL is a relative path (starts with / and not //) or matches an approved whitelist of domains.

import { useLocation, useNavigate } from 'react-router-dom';

function Login() {
  const location = useLocation();
  const navigate = useNavigate();

  const handleLogin = () => {
    // Perform authentication logic...

    // Retrieve the redirect path
    const redirectTo = location.state?.from || '/dashboard';

    // Validate that the redirect path is internal
    const isSafeRedirect = redirectTo.startsWith('/') && !redirectTo.startsWith('//');

    if (isSafeRedirect) {
      navigate(redirectTo);
    } else {
      navigate('/dashboard'); // Fallback to safe default
    }
  };
}

3. Validate the Schema of location.state

location.state can be modified by external scripts or manipulated during page navigation. If your component expects a specific data structure in location.state, validate its shape at runtime before using it.

Using a validation library like Zod is an effective way to enforce schemas:

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

// Define the expected schema
const StateSchema = z.object({
  userId: z.string().uuid(),
  referrer: z.string().optional(),
});

function UserProfile() {
  const location = useLocation();
  
  // Safely parse and validate the state
  const result = StateSchema.safeParse(location.state);

  if (!result.success) {
    return <div>Error: Invalid state data provided.</div>;
  }

  const { userId } = result.data;
  return <div>User ID: {userId}</div>;
}

4. Never Store Sensitive Information in Location State

Because browser history tracks state objects, any data stored in location.state remains in the browser’s memory and can be accessed via the history API.