DEV Community

Cover image for React JS - Security Best Practices
Kristiyan Velkov
Kristiyan Velkov

Posted on • Originally published at Medium

React JS - Security Best Practices

Front-end security refers to the practices and measures taken to secure the user interface and client-side components of a web application.


Update dependencies regularly 📦

  • Regularly update React JS and its dependencies to the latest versions to benefit from security patches and bug fixes.
  • Use tools like npm audit or yarn audit to identify and address vulnerabilities in your project's dependencies.
  • Use Snyk - to monitor and secure the dependencies.
  • Use Dependabot - Automated dependency update

Cross-Site Scripting (XSS) Prevention 🛡️

  • Use JSX for rendering dynamic content - automatically escapes values to prevent XSS attacks. Avoid using dangerouslySetInnerHTML unless absolutely necessary, and properly sanitize user-generated content before rendering.

In React.js, dangerouslySetInnerHTML is a prop that allows you to inject raw HTML into a component's output. This prop should be used with caution, as improper usage can expose your application to potential security vulnerabilities like cross-site scripting (XSS) attacks.

Avoid: ❌

function MyComponent({ htmlContent }) {
  return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
}

Enter fullscreen mode Exit fullscreen mode
DO: ✅
import DOMPurify from 'dompurify';

// ...

function renderUserContent(content) {
  const sanitizedContent = DOMPurify.sanitize(content);
  return { __html: sanitizedContent };
}

function ContentDisplay({ content }) {
  return <div dangerouslySetInnerHTML={renderUserContent(content)} />;
}
Enter fullscreen mode Exit fullscreen mode

Content Security Policy (CSP) 🔒

Implement a CSP to control which scripts and resources can be loaded and executed on your web page. This prevents unauthorized scripts from running.
When you're building a React JS application, it's important to make sure that the CSP headers are set correctly on the server. Additionally, you should avoid using unsafe practices like inline event handlers or inline styles, as they might conflict with your CSP policy. 

For example:

 Instead of using Click me, you should use React's event handling like:

Avoid: ❌

<button onclick="doSomething()">Click me</button>
Enter fullscreen mode Exit fullscreen mode
DO: ✅
<button onClick={doSomething}>Click me</button>
Enter fullscreen mode Exit fullscreen mode

Similarly, avoid using inline styles and use CSS classes instead.

Avoid: ❌

<div style="color: red;">Hello</div>
Enter fullscreen mode Exit fullscreen mode
DO: ✅
<div style="text-color">Hello</div>
Enter fullscreen mode Exit fullscreen mode

React Helmet 🎩

  • Use the react-helmet package is used in React JS applications to manage and control the content of the HTML section. This is useful for setting metadata, titles, styles, and other elements in the head section.
DO: ✅

import React from 'react';
import { Helmet } from 'react-helmet';

function MyPage() {
  return (
    <div>
      <Helmet>
        <title>My Page Title</title>
        <meta name="description" content="This is my page's description." />
        <link rel="stylesheet" href="path/to/styles.css" />
      </Helmet>
      {/* Other JSX content for your page */}
    </div>
  );
}

export default MyPage;
Enter fullscreen mode Exit fullscreen mode
  • Set the Content-Security-Policy header using react-helmed to enforce security policies on the server side. 

Express example:


 

DO: ✅
const express = require('express');
const helmet = require('helmet');
const { Helmet } = require('react-helmet');
const React = require('react');
const ReactDOMServer = require('react-dom/server');

const app = express();

app.use(helmet());

app.get('/', (req, res) => {
  const helmetData = Helmet.renderStatic(); // Collect helmet data from your React components

  const contentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"; // Your CSP rules

  res.set({
    'Content-Security-Policy': contentSecurityPolicy,
    ...helmetData.meta.toComponent(),
    ...helmetData.title.toComponent(),
    // Add other headers if needed
  });

  const reactApp = ReactDOMServer.renderToString(React.createElement(YourAppComponent));

  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        ${helmetData.title.toString()}
        ${helmetData.meta.toString()}
      </head>
      <body>
        <div id="root">${reactApp}</div>
      </body>
    </html>
  `);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

Next.js example

DO: ✅
import { NextSecureHeadersMiddleware } from 'next-secure-headers';

const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
  },
  // Add other headers if needed
];

export default NextSecureHeadersMiddleware({
  headers: securityHeaders,
});

Enter fullscreen mode Exit fullscreen mode

Input Validation and Sanitization 🧹

  • Validate and sanitize inputs on the client side to prevent injection attacks.  - Use HTML attributes like required, min, max, type, etc. to enforce basic input validation.
DO: ✅

function MyForm() {
  const handleSubmit = (event) => {
    event.preventDefault();
    // Validate inputs here
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" required pattern="[A-Za-z]+" />
      <button type="submit">Submit</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode
Avoid: ❌

<form action="/register" method="post">
  <label for="username">Username:</label>
  <input type="text" id="username" name="username" pattern="[A-Za-z0-9]+" required>

  <label for="password">Password:</label>
  <input type="password" id="password" name="password" pattern=".{8,}" required>

  <button type="submit">Register</button>
</form>
Enter fullscreen mode Exit fullscreen mode
  • Input Sanitization

You can use third-party libraries to simplify validation and improve user experience. Libraries like react-hook-form, Formik, and yup provide tools for managing forms, validation, and error handling:

DO: ✅

import { useForm } from 'react-hook-form';
import * as yup from 'yup';

const schema = yup.object().shape({
  username: yup.string().required(),
  email: yup.string().email().required(),
});

function MyForm() {
  const { register, handleSubmit, errors } = useForm({
    validationSchema: schema,
  });

  const onSubmit = (data) => {
    // Handle form submission
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="username" ref={register} />
      <input name="email" ref={register} />
      <button type="submit">Submit</button>
      {errors.username && <p>Username is required</p>}
      {errors.email && <p>Invalid email</p>}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

HTTPS usage 🔒

Serve your React app over HTTPS protocol to ensure encrypted communication between client and server.


Secure Authentication and Authorization 🔐

  • Implement proper authentication and authorization mechanisms, such as OAuth, JWT, or cookie, and ensure secure storage of auth tokens.
  • Authorize users based on roles and permissions to restrict access to sensitive information.
  • Leverage established authentication, like Next.js Auth, passport to handle authentication flows securely. These libraries have been designed to handle common security concerns.

Secure State Management 🔒

Use libraries like Redux and React Content API to manage the application state.


Third-Party Libraries and Components 📚

  • Only use trusted and well-maintained third-party libraries and components.
  • Regularly view and update dependencies to avoid known vulnerabilities. 

Protect Sensitive Data with Encryption 🔑

Ensure sensitive data such as user passwords, API keys, and tokens are adequately encrypted. Utilize encryption libraries like bcrypt or crypto js to secure sensitive information and mitigate the impact of data breaches.

DO: ✅

// Client Side - BcryptJS

import React from 'react';
import bcrypt from 'bcryptjs';

function RegistrationForm() {
  const handleSubmit = async (event) => {
    event.preventDefault();

    const plainPassword = event.target.password.value;
    const hashedPassword = await bcrypt.hash(plainPassword, 10);

    // Send hashedPassword to the server for storage
    // ...
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="password" name="password" placeholder="Password" />
      <button type="submit">Register</button>
    </form>
  );
}

export default RegistrationForm;
Enter fullscreen mode Exit fullscreen mode

// Client Side - CryptoJS

import React from 'react';
import CryptoJS from 'crypto-js';

function EncryptDecryptExample() {
  const plaintext = 'sensitive_data';
  const secretKey = 'super_secret_key';

  // Encrypt data
  const ciphertext = CryptoJS.AES.encrypt(plaintext, secretKey).toString();

  // Decrypt data
  const bytes = CryptoJS.AES.decrypt(ciphertext, secretKey);
  const decryptedData = bytes.toString(CryptoJS.enc.Utf8);

  return (
    <div>
      <p>Encrypted: {ciphertext}</p>
      <p>Decrypted: {decryptedData}</p>
    </div>
  );
}

export default EncryptDecryptExample;
Enter fullscreen mode Exit fullscreen mode

Static Code Analysis 📝

Use tools like ESLint and Prettier to enforce coding standards and catch potential security vulnerabilities during the developing phase.  


Error Handling 🔧

Handle errors gracefully without revealing sensitive information to the users.


Image description

linkedin


Image description

If you like my work and want to support me to work hard, please donate via:

Revolut website payment or use the QR code above.

Thanks a bunch for supporting me! It means a LOT 😍

Top comments (1)

Collapse
 
aungmyooo2k17 profile image
Aung Myo Oo

At the Content Security Policy (CSP) section.

DO: ✅
<div style="text-color">Hello</div>

Should be
DO: ✅
<div class="text-color">Hello</div>

Right?