How to Secure Controlled Components in React
Controlled components in React offer robust form handling by binding input values directly to the component’s state. While this architecture provides excellent control over user input, it also introduces security risks such as Cross-Site Scripting (XSS), state manipulation, and input injection. This article explains how to secure controlled components in React using input sanitization, strict validation schemas, secure state management, and defensive coding practices.
1. Sanitize and Validate Input Data
React automatically escapes values rendered in JSX, which protects against basic HTML injection. However, security risks still exist when data is sent to a backend or rendered using risky properties.
- Client-Side Validation: Use robust schema
validation libraries like Zod or Yup to validate the structure, type,
and length of the input on the
onChangeevent or during form submission. - Sanitization: If you must render user-provided
HTML, never use
dangerouslySetInnerHTMLwithout first sanitizing the input. Use a library like DOMPurify to strip malicious scripts from the string before rendering or updating the state.
2. Enforce Strict Type and Length Constraints
Attackers may attempt to inject unexpectedly large payloads or incorrect data types to crash the application or cause a Denial of Service (DoS) in the browser.
- Limit Input Length: Set the
maxLengthattribute on HTML input elements to prevent users from pasting massive blocks of text into the state. - Enforce Type Safeness: In TypeScript, explicitly
type your React state to ensure it only accepts strings, numbers, or
booleans. Validate that the value from
event.target.valuematches the expected type before updating the state.
3. Prevent State Injection and Manipulation
React state should only be updated through verified handlers. Ensure that custom input components do not blindly spread properties from user input into the state, as this can lead to prototype pollution or unexpected state overrides.
- Explicit State Updates: Always specify the exact keys you want to update in the state instead of dynamically mapping event names directly to state keys without validation.
- Read-Only Defaults: Provide fallback values (e.g.,
value={userInput || ''}) to prevent React from switching a component from controlled to uncontrolled, which can bypass validation logic.
4. Secure the Form Submission Flow
Securing the controlled component’s state is only effective if the data remains secure during submission.
- Prevent Default Behavior: Always call
event.preventDefault()in your submit handler to prevent default browser behavior that could expose state data in the URL query parameters. - Server-Side Validation: Client-side security is bypassed easily by manipulating API requests. Always re-validate and re-sanitize all incoming data on the server side, treating the React client as inherently untrusted.