Существует несколько способов аутентификации в приложениях 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, вы можете прочитать ее здесь:
Я перечисляю рекомендации, которым мы должны следовать и внедрять, чтобы воспользоваться преимуществами вышеупомянутой архитектуры, а также настроить ее, чтобы сделать ее более безопасной, возможно, для финансовых приложений или приложений с высокой конфиденциальностью.
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 и каждый раз проверяйте достоверность данных путем расшифровки или вы можете поместить эти данные в поток токена обновления, чтобы получить их также в этом ответе, или вы можете сделать другой запрос после токена обновления на свой сервер, чтобы получить данные пользователя аутентификации и сохранить их в памяти, все они в порядке, поскольку нет теперь можно украсть ваши жетоны.
Не забудьте проверять данные пользователя для аутентификации и проверку токена каждый раз, когда память вашего приложения повторно создается, например, при жестком обновлении, новой вкладке и т. д., чтобы убедиться, что пользователь не прошел проверку подлинности, и вы перенаправляете его на страницу входа.
Я надеюсь, что эта статья может быть полезна для вас, пожалуйста, не стесняйтесь комментировать и задавать вопросы.