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.
- Do not store: JSON Web Tokens (JWTs), passwords, personally identifiable information (PII), or API keys.
- Do store: Non-sensitive UI state indicators, such as tab indexes, pagination pages, or simple redirect paths.