Существует несколько способов аутентификации в приложениях React.js для рендеринга на стороне клиента, также известных как CSR, по всему Интернету и на курсах, будь то Udemy, Youtube или где-либо еще в Интернете, кроме большинство из них делают это неправильно!

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

1- Хранение токена доступа и данных пользователя для аутентификации в локальном хранилище ==> XSS-атака

Если вы пойдете по этому пути, который на сегодняшний день является наихудшим из возможных способов аутентификации, вы будете уязвимы для атаки XSS с межсайтовым скриптингом, что означает, что злоумышленник может внедрить вредоносные скрипты внутрь вашего HTML и легко украсть данные localStorage.

Атаки с использованием межсайтовых сценариев (XSS) — это тип внедрения, при котором вредоносные сценарии внедряются на безопасные и надежные веб-сайты. Атаки XSS происходят, когда злоумышленник использует веб-приложение для отправки вредоносного кода, как правило, в виде скрипта на стороне браузера, другому конечному пользователю.

2- Хранение токена доступа и данных пользователя для аутентификации в файлах cookie ==› Атака CSRF

Если вы следуете пути использования файлов cookie, это немного лучше, но все же вы действительно уязвимы для атаки Cross-Site-Request-Forgery, также известной как CSRF, что означает, что злоумышленник может захватить ваши данные файлов cookie и вашего пользователя. сессии.

Подделка межсайтовых запросов (CSRF) — это атака, которая заставляет конечного пользователя выполнять нежелательные действия в веб-приложении, в котором он в настоящее время аутентифицирован. С небольшой помощью социальной инженерии (например, отправив ссылку по электронной почте или в чат) злоумышленник может обманом заставить пользователей веб-приложения выполнять действия по выбору злоумышленника.

Если жертва является обычным пользователем, успешная атака CSRF может заставить пользователя выполнять запросы на изменение состояния, такие как перевод средств, изменение адреса электронной почты и т. д. Если жертва является административной учетной записью, CSRF может скомпрометировать все веб-приложение.

Какое здесь решение?

Что ж, в прошлом году я опубликовал статью об ошибке 401 и о том, как реализовать правильный токен доступа и архитектуру аутентификации токена обновления для нашего приложения Front-End React.js, вы можете прочитать ее здесь:

Как сделать безопасные приложения React на стороне клиента с защищенными маршрутами, используя память приложений!?

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

1- Требования к внутреннему серверу

Бэкенд-сервер обеспечивает доступ и поток токенов обновления в архитектуре аутентификации.

2- Требования к интерфейсу

Интерфейсное приложение с возможностями хранения, такими как React + Context или Redux, или любой другой библиотекой управления состоянием на стороне клиента, которая может сохранять память, пока доступен экземпляр приложения.

3 — Рекомендации по хранению и примечания

Мы никогда не будем хранить наш токен доступа в API-интерфейсах хранилища браузера, таких как localStorage, Cookies, sessionStorage и т. д., с другой стороны, мы будем хранить наш токен доступа только в контексте памяти экземпляра нашего приложения, которое мы используем.

4 – Конфигурация файлов cookie

Наш сервер должен иметь возможность отправлять и устанавливать токен обновления вместе с ответом на вход с параметрами httpOnly: true, secure: true, sameSite: none. флаги, когда мы входим в приложение и устанавливаем для sameSite значение Strict, если ваш внутренний базовый URL-адрес идентичен адресу вашего домена.

httpOnly: true, secure: true, sameSite: none

Для получения дополнительной информации и более подробных сведений о файлах cookie нажмите здесь.

5 – Настройка Axios или Fetch API

Наше внешнее приложение должно добавить withCredentials: true, если оно использует Axios, и withCredentials: include, если оно использует API-интерфейс выборки в каждом запросе, который выполняется для наших частных маршрутов и маршрут входа на внутренний сервер.

На рисунке ниже показан пример конфигурации Axios.

Примечание. Если вы столкнулись с ошибкой CORS из-за приведенной выше рекомендации, вам необходимо добавить следующие заголовки в ответы вашего сервера:

Access-Control-Allow-Credentials: true;
Access-Control-Allow-Origin: * or your whitelist domains
Access-Control-Allow-Headers: Content-Type,Content-Length, Authorization, Accept,X-Requested-With
Access-Control-Allow-Methods: PUT,POST,GET,DELETE,OPTIONS

Здесь вы можете углубиться в приведенные выше заголовки.

6- Срок действия токена доступа и обновления

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

Ниже приведено лишь рекомендуемое время истечения срока действия токена, и вы можете настроить его по своему усмотрению в свою пользу.

Для высокочувствительных приложений, таких как финансовые или аналогичные приложения:

Время токена => 30 сек, 1 мин, 5 мин
Время обновления токена => 10 мин, 20 мин, 30 мин

Для конфиденциальных приложений, таких как информационные панели или частные панели:

Время токена =› 15 мин, 30 мин

Время обновления токена => 1д, 2д, 3д

Для обычных приложений или социальных сетей

Токен времени =› 1ч, 6ч, 12ч, 24ч

время обновления токена => 5д, 7д, 15д, 30д, 3мес.

Вы также можете добавить очень удобную функцию для улучшения UX для ваших пользователей, добавить галочку доверять этому устройству, чтобы спросить своих пользователей, хотят ли они оставаться в системе на текущем устройстве, которое они используют в течение длительного времени, или если это это просто временный сеанс, и они хотят закрыть его, как только покинут вкладку браузера, это хорошо для тех случаев, когда ваши пользователи получают доступ к вашему приложению с общедоступного устройства, и вы можете настроить время токена обновления, добавив такую ​​функцию и улучшить взаимодействие с пользователем, а также сделать его более безопасным при входе в систему.

7 – Личный менеджер Axios или Fetch API

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

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

export const usePrivateAxios = () => {
  const { token } = yourAppMemory();

  useEffect(() => {
    const requestInterceptor = privateAxios.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        // Do something before request is sent
        if (token) config.headers.Authorization = `Bearer ${token}`;
        return config;
      },
      (error) => {
        // Do something with request error
        return Promise.reject(error);
      },
    );
    const responseInterceptor = privateAxios.interceptors.response.use(
      // Within the range of 2xx
      function success(response) {
        return response;
      },
      // Outside the range of 2xx
      async function failure(error) {
        if (!error.response) {
          // Network issues
          return Promise.reject(error);
        } else {
          //** Bad Request 400 */
          if (error?.response?.status === 400) {
            return Promise.reject(error);
          }

          //** Unauthenticated 401 */
          if (error?.response?.status === 401) {
            const originalReq = error?.config;
            const refTokenURL = `${baseURL}/refresh`;

            if (
              error?.response?.status === 401 &&
              originalReq.url === refTokenURL
            ) {
              return Promise.reject(error);
            }
            if (!originalReq._retry) {
              originalReq._retry = true;
              try {
                const res = await privateAxios.post(refTokenURL);
                if (res?.status === 200) {
                  const token = res.data.response?.access_token;
                  // 1) Set the new token
                  setInMemoryAgain({ token });
                  // 2) Change Authorization header
                  privateAxios.defaults.headers.common.Authorization = token;
                  return privateAxios(originalReq);
                }
                return Promise.reject(error);
              } catch (err) {
                return Promise.reject(error);
              }
            }
            return Promise.reject(error);
          }
          //** Unauthorized 403 */
          if (error?.response?.status === 403) {
            return Promise.reject(error);
          }
          //** Not Found 404 */
          if (error?.response?.status === 404) {
            return Promise.reject(error);
          }
          //** Server Error 500 */
          if (error?.response?.status === 500) {
            return Promise.reject(error);
          }
        }
        return Promise.reject(error);
      },
    );
    return () => {
      privateAxios.interceptors.request.eject(requestInterceptor);
      privateAxios.interceptors.response.eject(responseInterceptor);
    };
  }, [token]);

  return privateAxios;
};

8- Сохранение при потере экземпляра памяти!

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

В этом случае вы можете добавить useEffect или React Query в свой компонент аутентификации верхнего уровня, чтобы проверить, недоступен ли токен в памяти, ему необходимо запросить маршрут обновления токена, чтобы получить новый токен для пользователя, если он возможный.

9- Данные пользователя, прошедшие проверку подлинности

Последняя часть головоломки — это управление данными пользователя для аутентификации. Это совершенно необязательно, и вам нужно выполнить соответствующие шаги. Возможно, вы захотите зашифроватьданные пользователя, поступающие из части входа, используя AESи хэш SHA-256 и поместите его в secure: true, sameSite: StrictCookie и каждый раз проверяйте достоверность данных путем расшифровки или вы можете поместить эти данные в поток токена обновления, чтобы получить их также в этом ответе, или вы можете сделать другой запрос после токена обновления на свой сервер, чтобы получить данные пользователя аутентификации и сохранить их в памяти, все они в порядке, поскольку нет теперь можно украсть ваши жетоны.

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

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