Внешняя безопасность – это методы и меры, принимаемые для защиты пользовательского интерфейса и клиентских компонентов веб-приложения.

Регулярно обновляйте зависимости📦

  • Регулярно обновляйте React JS и его зависимости до последних версий, чтобы получать исправления безопасности и исправления ошибок.
  • Используйте такие инструменты, как аудит npm или аудит пряжи, чтобы выявить и устранить уязвимости в зависимостях вашего проекта.
  • Используйте Snyk — для мониторинга и защиты зависимостей.
  • Используйте Dependabot — автоматическое обновление зависимостей.

Предотвращение межсайтового скриптинга (XSS)🛡️

Почти 65% веб-сайтов подвержены XSS-уязвимостям, обнаруженным в современных веб-приложениях.

  • Используйте JSX для рендеринга динамического контента — значения автоматически экранируются для предотвращения XSS-атак.
  • Избегайте использования dangerouslySetInnerHTML без крайней необходимости и тщательно очищайте пользовательский контент перед рендерингом.

В React.js опасноSetInnerHTML — это свойство, которое позволяет вам вставлять необработанный HTML в выходные данные компонента. Эту функцию следует использовать с осторожностью, поскольку неправильное использование может подвергнуть ваше приложение потенциальным уязвимостям безопасности, таким как атаки с использованием межсайтовых сценариев (XSS).

Avoid: ❌

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

import DOMPurify from 'dompurify';

// ...

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

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

Политика безопасности контента (CSP)🔒

Внедрите CSP, чтобы контролировать, какие сценарии и ресурсы можно загружать и выполнять на вашей веб-странице. Это предотвращает запуск неавторизованных сценариев.

Когда вы создаете приложение React JS, важно убедиться, что заголовки CSP правильно установлены на сервере.

Как добавить заголовки безопасности в приложение Next.js

const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,  
  // Adding policies:
  async headers() {
    return [
        {
          source: '/(.*)',
          headers: [
            {
              key: 'X-Frame-Options',
              value: 'DENY',
            },
            {
              key: 'Content-Security-Policy',
              value:
                "default-src 'self' script-src 'self' https://www.google-analytics.com; font-src 'self' 'https://fonts.googleapis.com'",
            },
            {
              key: 'X-Content-Type-Options',
              value: 'nosniff',
            },
            {
              key: 'Permissions-Policy',
              value: "camera=(); battery=(self); geolocation=(); microphone=('https://a-domain.com')",
            },
            {
              key: 'Referrer-Policy',
              value: 'origin-when-cross-origin',
            },
          ],
        },
      ];
  },   
}

module.exports = nextConfig

Использование react-helmet — еще один допустимый подход к добавлению заголовков безопасности в приложение Next.js. Вот правильный способ использования react-helmet для добавления заголовков безопасности.

Шлем React🎩

  • Используйте пакет react-helmet. Он используется в приложениях React JS для управления содержимым раздела HTML head. Это полезно для настройки метаданных, заголовков, стилей и других элементов в разделе заголовка.
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;
  • Установите заголовок Content-Security-Policy, используя React-Heled, чтобы обеспечить соблюдение политик безопасности на стороне сервера.

Экспресс-пример:

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');
});

Пример Next.js

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,
});

Кроме того, вам следует избегать использования небезопасных методов, таких как встроенные обработчики событий или встроенные стили, поскольку они могут противоречить вашей политике CSP.

Например:
Вместо использования <button onclick="doSomething()">Click me</button> вам следует использовать обработку событий React, например:

Avoid: ❌

<button onclick="doSomething()">Click me</button>
DO: ✅

<button onClick={doSomething}>Click me</button>

Аналогично, избегайте использования встроенных стилей и вместо этого используйте классы CSS.

Avoid: ❌

<div style="color: red;">Hello</div>
DO: ✅

<div className="text-color">Hello</div>

Проверка и очистка ввода🧹

  • Проверяйте и очищайте входные данные на стороне клиента, чтобы предотвратить атаки путем внедрения.
    Используйте атрибуты HTML, такие как required, min, max, type, etc., для обеспечения базовой проверки входных данных.
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>
  );
}
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>

Очистка ввода
Вы можете использовать сторонние библиотеки, чтобы упростить проверку и улучшить взаимодействие с пользователем. Такие библиотеки, как react-hook-form, Formik и yup, предоставляют инструменты для управления формами, проверки и обработки ошибок:

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>
  );
}

Использование HTTPS 🔒

  • Обслуживайте свое приложение React по протоколу HTTPS, чтобы обеспечить зашифрованную связь между клиентом и сервером.

Безопасная аутентификация и авторизация🔐

  • Внедрите надлежащие механизмы аутентификации и авторизации, такие как OAuth, JWT или cookie, и обеспечьте безопасное хранение токенов аутентификации.
  • Авторизуйте пользователей на основе ролей и разрешений, чтобы ограничить доступ к конфиденциальной информации.
  • Используйте установленную аутентификацию, например Next.js Auth, passport для безопасной обработки потоков аутентификации. Эти библиотеки были разработаны для решения общих проблем безопасности.

Безопасное управление состоянием🔒

Используйте такие библиотеки, как Redux и React Content API, для управления состоянием приложения.

TСторонние библиотеки и компоненты📚

  • Используйте только проверенные и хорошо поддерживаемые сторонние библиотеки и компоненты.
  • Регулярно просматривайте и обновляйте зависимости, чтобы избежать известных уязвимостей.

Защитите конфиденциальные данные с помощью шифрования 🔑

Убедитесь, что конфиденциальные данные, такие как пароли пользователей, ключи API и токены, надежно зашифрованы. Используйте библиотеки шифрования, такие как bcrypt или crypto js, чтобы защитить конфиденциальную информацию и смягчить последствия утечки данных.

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;
// 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;

Статический анализ кода📝

Используйте такие инструменты, как ESLint и Prettier, чтобы обеспечить соблюдение стандартов кодирования и выявить потенциальные уязвимости безопасности на этапе разработки.

Обработка ошибок 🔧

Обрабатывайте ошибки корректно, не раскрывая конфиденциальную информацию пользователям.

Спасибо, что дочитали до конца. Пожалуйста, следите за автором и этой публикацией. Посетите Stackademic, чтобы узнать больше о том, как мы демократизируем бесплатное образование в области программирования во всем мире.

Если вам понравилась моя работа, пожалуйста:

Следуйте за мной и подпишитесь

  • "Подписывайтесь на меня"
  • "Подписаться"
  • ЛинкедИн

1. Пожертвовать 💕

Оплата на сайте Revolut или используйте QR-код выше.

Ваше пожертвование подпитает мое стремление продолжать создавать значимую работу. Спасибо! 🦁💚