How to Secure Uncontrolled Components in React
Uncontrolled components in React rely on the DOM to handle form data
rather than React state, making them a lightweight option for form
handling but exposing them to unique security vulnerabilities like
Cross-Site Scripting (XSS). This article explains how to secure
uncontrolled components by implementing robust input validation, data
sanitization, and safe handling of React refs to prevent
malicious exploits.
Understand the Security Risk of Uncontrolled Components
In React, controlled components keep form data in the component
state, allowing React to filter and control changes. Uncontrolled
components use refs to pull field values directly from the
DOM when needed.
Because uncontrolled components bypass React’s state management, they
can easily bypass React’s built-in XSS protections if you take the
retrieved values and render them directly into the DOM using properties
like dangerouslySetInnerHTML, or if you pass the raw values
to APIs and databases without sanitization.
1. Sanitize All Ref Inputs
The primary defense against XSS in uncontrolled components is
sanitizing the data retrieved from refs before it is
processed, stored, or rendered.
Never trust user input. Use a trusted sanitization library like
DOMPurify to clean HTML and strip out malicious scripts
before doing anything with the input value.
import React, { useRef } from 'react';
import DOMPurify from 'dompurify';
function SecureForm() {
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
// Retrieve raw value
const rawInput = inputRef.current.value;
// Sanitize the input
const cleanInput = DOMPurify.sanitize(rawInput);
// Safely use cleanInput
console.log(cleanInput);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
}2. Validate Input Against Strict Rules
Sanitization cleans data, but validation ensures the data adheres to
expected formats. Implement strict validation rules for length, type,
and character sets on the values pulled from your refs.
- Type checking: Ensure numbers are parsed as numbers.
- Length constraints: Limit the maximum characters allowed.
- Regex validation: Use regular expressions to enforce specific formats (e.g., email addresses, zip codes).
const handleSubmit = (event) => {
event.preventDefault();
const username = inputRef.current.value;
// Validate: Alphanumeric only, between 3 and 16 characters
const usernameRegex = /^[a-zA-Z0-9]{3,16}$/;
if (!usernameRegex.test(username)) {
alert('Invalid username format.');
return;
}
// Proceed with validated input
};3. Avoid Modifying the DOM Directly via Refs
React refs provide direct access to DOM nodes. It is
highly insecure to use these refs to manually manipulate
DOM elements—especially updating innerHTML with
user-supplied values.
If you must update the DOM based on an uncontrolled input, update the React state with the sanitized value and let React handle the rendering safely.
// BAD: Vulnerable to XSS
myDisplayRef.current.innerHTML = inputRef.current.value;
// GOOD: Use textContent to prevent script execution
myDisplayRef.current.textContent = inputRef.current.value;4. Implement Server-Side Validation and Sanitization
Client-side security measures can be bypassed by malicious actors. Securing uncontrolled components on the front end is only the first line of defense.
Always validate and sanitize all incoming data on your backend server before saving it to a database or returning it to other users. Treat all data coming from uncontrolled React inputs as untrusted.