How to Secure React Props in React

Securing React props is essential for preventing Cross-Site Scripting (XSS) attacks, avoiding data leaks, and ensuring application stability. This article provides a straightforward guide on how to protect your React components by implementing strict type checking, validating dynamic data, sanitizing user-generated inputs, and avoiding unsafe prop-spreading practices.

1. Implement Strict Type Checking

The first line of defense in securing props is ensuring that components only receive the data types they expect. This prevents runtime crashes and unexpected behavior caused by malformed data.

Use TypeScript

TypeScript is the industry standard for compile-time type safety in React. By defining explicit interfaces for your props, you catch potential security and logical errors before the code is deployed.

interface UserProfileProps {
  username: string;
  userId: number;
  isAdmin: boolean;
}

const UserProfile = ({ username, userId, isAdmin }: UserProfileProps) => {
  return <div>{username} ({userId})</div>;
};

Use PropTypes (Alternative)

If you are not using TypeScript, utilize the prop-types library for runtime type checking in development mode.

import PropTypes from 'prop-types';

const UserProfile = ({ username, userId, isAdmin }) => {
  return <div>{username} ({userId})</div>;
};

UserProfile.propTypes = {
  username: PropTypes.string.isRequired,
  userId: PropTypes.number.isRequired,
  isAdmin: PropTypes.bool
};

2. Avoid Blind Prop Spreading

Spreading objects directly onto JSX elements (e.g., <div {...props}>) is a common security risk. It can inadvertently pass sensitive data or invalid attributes directly to the HTML DOM, which may expose internal state or trigger browser vulnerabilities.

The Bad Practice:

// If props contains sensitive information, it gets rendered into the DOM
const Button = (props) => {
  return <button {...props}>Click me</button>;
};

The Secure Practice:

Destructure your props to separate custom component logic from standard HTML attributes.

const Button = ({ isAdmin, label, ...domProps }) => {
  // Use isAdmin internally, only spread safe DOM properties
  return <button {...domProps}>{label}</button>;
};

3. Sanitize User-Generated Props to Prevent XSS

React automatically escapes strings rendered in the JSX body, which protects against basic XSS attacks. However, vulnerabilities still arise when dealing with URLs or raw HTML passed as props.

Securing dangerouslySetInnerHTML

If a component accepts HTML markup as a prop, you must sanitize it before rendering. Use a library like dompurify to strip malicious scripts.

import DOMPurify from 'dompurify';

const RichTextRenderer = ({ rawHtml }) => {
  const cleanHtml = DOMPurify.sanitize(rawHtml);
  return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
};

Securing URL Props

Attackers can inject javascript: pseudo-protocol URLs into props designed for links (like href or src). Always validate that URL props use safe protocols (like http: or https:).

const SafeLink = ({ href, label }) => {
  const isSafe = href.startsWith('http://') || href.startsWith('https://') || href.startsWith('/');
  
  return (
    <a href={isSafe ? href : '#'} target="_blank" rel="noopener noreferrer">
      {label}
    </a>
  );
};

4. Validate Dynamic Props at Runtime

When props are populated by external APIs, compile-time checks like TypeScript cannot guarantee data integrity at runtime. Use a schema validation library like Zod to parse and validate incoming data before passing it down as props.

import { z } from 'zod';

// Define the schema
const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  age: z.number().min(18),
});

// Validate API data before passing it to components
const handleApiResponse = (data: unknown) => {
  const result = UserSchema.safeParse(data);
  if (!result.success) {
    console.error("Invalid prop data received", result.error);
    return null;
  }
  return <UserProfileUser props={result.data} />;
};