javascript

От localStorage к HttpOnly Cookie. Как меняется архитектура авторизации в SPA

  • пятница, 25 сентября 2026 г. в 00:00:09
https://habr.com/ru/companies/gnivc/articles/1082540/

Привет, я – фронтенд-разработчик и сегодня хотел бы поднять данную тему. Сразу скажу: так как работаю в основном с React, то в конце будет разбор структуры именно в разрезе React + стейт-менеджер MobX. Итак, поехали.

В большинстве SPA-приложений после успешного входа сервер возвращает JWT, фронтенд сохраняет его в localStorage, а затем при каждом запросе добавляет в заголовок Authorization.

Получается примерно такая последовательность:

POST /login -> JWT -> localStorage -> Authorization: Bearer <token> -> API

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

Однако за последние несколько лет рекомендации по построению безопасной авторизации заметно изменились. Все чаще можно встретить архитектуру, в которой токен вообще не попадает в JavaScript-код приложения. Вместо этого используются HttpOnly Cookie, а управление пользовательской сессией полностью переносится на сервер.

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

Фронтенд больше не отвечает за хранение, обновление и передачу токена. Эти задачи берет на себя браузер и сервер, используя встроенные механизмы HTTP, которые существуют уже много лет и изначально предназначались именно для работы с пользовательскими сессиями.

Давайте поговорим о том:

  • почему хранение JWT в localStorage постепенно перестает считаться лучшей практикой;

  • какие проблемы решает использование HttpOnly Cookie;

  • как меняется архитектура приложения при переходе на серверные сессии;

  • какие HTTP-заголовки участвуют в процессе авторизации;

  • как правильно настроить HttpOnly, Secure, SameSite, CORS и withCredentials;

  • как реализовать полноценную авторизацию в React без хранения токенов на клиенте.

Почему localStorage больше не считается хорошим местом для хранения токенов

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

В общих чертах схема выглядит просто. Сервер выдаёт JWT, клиент сохраняет его в localStorage и использует для авторизации при каждом запросе.

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

JavaScript получает полный доступ к токену

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

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

Поскольку localStorage является частью Web API, любой JavaScript-код, выполняющийся в контексте страницы, может получить доступ к его содержимому:

const token = localStorage.getItem('accessToken');

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

Нарушается разделение ответственности

Использование JWT в localStorage приводит к тому, что клиентское приложение начинает управлять пользовательской сессией. Помимо бизнес-логики, фронтенд берет на себя хранение токена, его удаление, восстановление после перезагрузки страницы, добавление заголовка Authorization, контроль времени жизни JWT и обновление Refresh Token.

Получается, что клиент начинает выполнять задачи, которые логичнее оставить серверу.

Вместе с этим в проекте появляется дополнительная инфраструктура: сервис для работы с localStorage, функции сохранения и удаления токена, Axios Interceptor для добавления Authorization, логика обновления Refresh Token, проверка срока действия JWT, восстановление авторизации после обновления страницы.

Кроме того, каждая дополнительная проверка, interceptor или ошибка в логике работы с токеном увеличивают сложность приложения и потенциально могут привести к проблемам.

При этом браузер уже предоставляет встроенный механизм для работы с пользовательскими сессиями и позволяет дополнительно ограничивать доступ к Cookie с помощью HttpOnly, Secure и SameSite. Он умеет автоматически хранить Cookie, отправлять их с каждым запросом и, при использовании атрибута HttpOnly, делать их полностью недоступными для JavaScript. А если точнее: «читать нельзя, использовать браузером можно!»

По поводу «С каждым запросом» уточню, что не с каждым, а только если выполняются условия: подходит Domain, подходит Path, соблюдается Secure, соблюдается политика SameSite, запрос выполняется в подходящем credential-контексте.

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

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

Как теперь работает HTTP

После перехода на HttpOnly Cookie меняется не только способ хранения токена, но и сам процесс авторизации.

Если раньше сервер возвращал JWT в теле ответа, а фронтенд самостоятельно сохранял его в localStorage, то теперь ответственность разделяется иначе. Сервер создаёт пользовательскую сессию, браузер самостоятельно сохраняет Cookie, при каждом последующем подходящем запросе браузер автоматически отправляет ее обратно серверу. При этом JavaScript вообще не участвует в этом процессе.

После проверки логина и пароля сервер создаёт новую пользовательскую сессию и отправляет в ответе HTTP-заголовок Set-Cookie, с помощью которого браузер получает инструкции для установки Cookie. Ответ может выглядеть следующим образом:

HTTP/1.1 200 OK
Content-Type: application/json

Set-Cookie:
session=abc123;
HttpOnly;
Secure;
SameSite=Lax;
Path=/;
Max-Age=3600

{
    "user": {
        "id": 1,
        "name": "Иван Иванов"
    }
}

Обратите внимание, что токена больше нет в теле ответа. Сервер создаёт пользовательскую сессию и передаёт браузеру идентификатор этой сессии через заголовок Set-Cookie. О содержимом Cookie – и о том, почему это не обязательно должен быть session ID, – подробнее ниже.

После получения такого ответа браузер самостоятельно сохранит Cookie. Никаких вызовов localStorage.setItem(...). А при следующем запросе браузер автоматически добавит заголовок Cookie: session=abc123. И поэтому клиентскому приложению больше не нужно самостоятельно формировать Authorization: Bearer <JWT>.

Разбираем атрибуты Cookie

Самое интересное и сложное скрывается именно в параметрах Set-Cookie. Каждый из них отвечает за определенное поведение браузера.

HttpOnly

HttpOnly - самый важный атрибут. Именно он запрещает JavaScript получать доступ к Cookie. Если раньше злоумышленник мог написать localStorage.getItem("token"), то после перехода на HttpOnly Cookie такой возможности больше нет. Даже если обратиться к document.cookie , Cookie с флагом HttpOnly в результате отображаться не будет. Именно поэтому HttpOnly является одной из эффективных мер, снижающих риск кражи credential через XSS.

Важно понимать, что HttpOnly не делает XSS безвредным. Злоумышленник по-прежнему может выполнять действия от имени пользователя через браузер, а браузер при подходящих условиях сам приложит HttpOnly Cookie к запросу. Но получить и украсть саму Cookie через JavaScript он уже не сможет и, следовательно, не сможет просто перенести этот credential в свой браузер и использовать её независимо от браузера жертвы.

Именно поэтому HttpOnly следует воспринимать не как защиту от самого XSS, а как способ не дать украденному JavaScript-коду прочитать основной credential пользователя.

Secure

Он запрещает браузеру передавать Cookie по обычному HTTP. Такая кука будет отправляться только по HTTPS. Важно понимать одну деталь: Secure не шифрует Cookie, шифрование обеспечивает сам HTTPS. Флаг Secure лишь запрещает случайно отправить Cookie по незащищенному соединению. Поэтому в production его следует использовать всегда.

SameSite

Это, пожалуй, один из самых непонятных атрибутов Cookie для фронтенд-разработчика. Его задача — определить, в каких контекстах браузер может отправлять Cookie, если запрос выполняется в cross-site-контексте. Здесь важно не путать site и origin. Например, app.example.com и api.example.com являются разными origin, но при одинаковой схеме и общем регистрируемом домене обычно относятся к одному site. А вот example.com и another-site.com — уже разные site.

SameSite позволяет браузеру ограничивать автоматическую отправку Cookie в cross-site-сценариях. У атрибута есть три основных значения: Strict, Lax, None

Strict

Самый строгий режим. При SameSite=Strict браузер максимально ограничивает отправку Cookie в cross-site-контексте. Это обеспечивает сильную защиту от CSRF, но может повлиять на некоторые сценарии, когда пользователь переходит на ваш сайт с другого ресурса.

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

Lax

Более гибкий и часто используемый вариант. Cookie отправляется в обычных same-site сценариях и в некоторых cross-site сценариях, например, при переходе пользователя на сайт через верхнеуровневую навигацию.

При этом браузер ограничивает отправку Cookie в большинстве cross-site запросов, которые могут использоваться для CSRF-атак.

Именно поэтому SameSite=Lax часто является хорошим выбором по умолчанию для обычных веб-приложений, которым не требуется отправлять Cookie в произвольных cross-site запросах.

None

SameSite=None разрешает браузеру отправлять Cookie и в cross-site-контексте. Такой режим может потребоваться, когда Cookie должна использоваться между разными site или в других сценариях, где стандартные ограничения SameSite мешают работе приложения. При этом для SameSite=None обязательно должен быть указан атрибут Secure. Без Secure современные браузеры отклонят такую Cookie.

Max-Age

Определяет срок жизни Cookie. Например, Max-Age=3600 означает, что Cookie будет храниться один час. По истечении времени браузер автоматически удалит ее. Вместо Max-Age иногда используют Expires, где указывается конкретная дата окончания действия Cookie. Однако Max-Age считается более предпочтительным, так как задаёт относительное время жизни и меньше зависит от настроек часов на клиентском устройстве.

Path

Определяет, для каких URL браузер будет отправлять Cookie. Например, Path=/ означает, что Cookie будет прикладываться ко всем запросам сайта.

Если указать Path=/api , то браузер станет отправлять ее только при обращении к ресурсам внутри /api. Это позволяет ограничить область действия Cookie и не передавать ее туда, где она не требуется.

Domain

Определяет, для каких доменов действует Cookie. Например, Domain=example.com означает, что Cookie будет доступна как для самого домена example.com, так и для его поддоменов, например, api.example.com или admin.example.com.

При этом точка перед доменом сегодня не требуется. Раньше запись «.example.com» использовалась для обозначения Cookie, доступной поддоменам, но современные браузеры игнорируют начальную точку. Поэтому Domain=example.com и Domain=.example.com имеют одинаковое поведение.

Если параметр Domain не указывать, Cookie будет привязана только к тому хосту, который ее установил. Такой вариант обычно является более безопасным, поскольку ограничивает область ее действия.

Что в итоге происходит

Если посмотреть на весь процесс целиком, то он оказывается значительно проще, чем схема с хранением JWT в localStorage.

  • Пользователь отправляет логин и пароль.

  • Сервер создаёт сессию и возвращает Set-Cookie.

  • Браузер автоматически сохраняет Cookie.

  • При следующем подходящем запросе браузер автоматически добавит заголовок Cookie.

  • Сервер находит соответствующую сессию и определяет пользователя.

Фронтенд при этом не знает значение Cookie, не сохраняет токены, не формирует заголовок Authorization и вообще не участвует в механизме передачи данных авторизации.

Что на самом деле хранится в Cookie

До этого момента мы говорили о Cookie как о замене localStorage, но важно понимать, что хранить в Cookie можно не только JWT, который раньше лежал в localStorage. На практике это только один из возможных вариантов. HttpOnly определяет не содержимое Cookie, а возможность получения JavaScript доступа к нему. В самой Cookie при этом может находиться как JWT, так и обычный идентификатор серверной сессии. И это два довольно разных подхода.

JWT внутри Cookie

Самый простой вариант выглядит так:

Set-Cookie: access_token=eyJhbGciOiJIUzI1NiIs...; HttpOnly; Secure; SameSite=Lax

В таком случае в Cookie кладется JWT. При следующем запросе браузер автоматически отправит: Cookie: access_token=eyJhbGciOiJIUzI1NiIs.... Сервер достанет JWT из Cookie, проверит его подпись, срок действия и другие необходимые данные. И здесь ничего принципиально не изменилось в самой природе JWT. Он по-прежнему является credential, просто теперь JavaScript не имеет к нему доступа.

Session ID внутри Cookie

Другой вариант работает совершенно иначе. В Cookie находится не JWT, а короткий идентификатор:

Set-Cookie: session=8f3a91c4...; HttpOnly; Secure; SameSite=Lax

Само значение session практически ничего не говорит о пользователе. А вот сервер использует этот идентификатор для поиска сессии и понимает, кто выполняет запрос.

В первом варианте с JWT браузер хранит саму credential, которую сервер умеет проверить без дополнительного состояния. Во втором браузер хранит только ссылку на состояние, находящееся на сервере.

Но у Cookie появляется другая проблема — CSRF

У автоматической отправки Cookie есть обратная сторона. Браузер отправляет Cookie самостоятельно. Ему не важно, какой именно JavaScript-код инициировал запрос. Представим, что пользователь авторизован на нашем сайте example.com. И в браузере уже есть: session=abc123. Далее пользователь открывает другой сайт - evil.example, и этот сайт пытается отправить запрос. Если браузер приложит к нему нашу Cookie, сервер может воспринять запрос как настоящий запрос пользователя.

В этом и заключается суть CSRF (Cross-Site Request Forgery).

HttpOnly защищает credential от чтения через JavaScript, но сам по себе не защищает приложение от CSRF-атак. Именно поэтому при переходе на Cookie недостаточно просто добавить HttpOnly; Secure. Нужно отдельно подумать о том, в каких запросах браузер имеет право отправлять эту Cookie.

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

Для большинства обычных приложений хорошим вариантом будет SameSite=Lax. В таком режиме браузер ограничивает передачу Cookie в cross-site контексте.

Это значительно уменьшает поверхность CSRF-атаки без необходимости добавлять специальный токен в каждый запрос. Однако SameSite нельзя воспринимать как универсальное решение.

Например, если API находится на другом origin: frontend.example.com и api.example.com, необходимо отдельно учитывать различия между site и origin, а также настройки CORS и credentialed-запросов. Если же API находится ещё и на другом site, дополнительно возникает вопрос политики SameSite.

SameSite, CORS и CSRF — это не одно и то же

Условно их можно разделить так: SameSite определяет, в каких cross-site сценариях браузер отправляет Cookie, CORS – определяет, может ли JavaScript одного origin читать ответы другого origin.

CSRF Token позволяет серверу получить дополнительный сигнал о том, что запрос сформирован в ожидаемом контексте, а не просто инициирован сторонним сайтом. Это разные уровни защиты. CORS сам по себе не является полноценной защитой от CSRF. Даже если браузер не позволит JavaScript с evil.example прочитать ответ сервера, сам запрос потенциально все равно может быть отправлен.

Важная деталь: Access-Control-Allow-Origin: *

Есть нюанс при переходе с JWT в Authorization на авторизацию через Cookie.

Допустим, фронтенд находится на: https://app.example.com, а API — на https://api.example.com. Это разные origin, поэтому браузер применяет к запросам CORS-политику. На сервере может возникнуть соблазн разрешить запросы откуда угодно.

Access-Control-Allow-Origin: *

Для публичного API это вполне нормальная настройка. Но для запросов с Cookie она не подходит. Если запрос выполняется с credentials, например, через Axios с withCredentials, сервер должен явно указать разрешённый origin. Использовать * в таком случае нельзя. Вместо:

Access-Control-Allow-Origin: *

сервер должен вернуть конкретный origin, например:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

Здесь важно понимать, что Access-Control-Allow-Credentials: true не означает «разрешить всем использовать Cookie». Наоборот, сервер говорит браузеру, что этому конкретному origin разрешено выполнять credentialed cross-origin запросы.

Если сервер оставит Access-Control-Allow-Origin: *, то браузер не разрешит JavaScript получить ответ в credentialed cross-origin запросе. В зависимости от типа запроса сам запрос может быть отправлен с Cookie, либо браузер сначала выполнит CORS preflight и не отправит основной запрос, если сервер не разрешит такой обмен.

В общем, CORS не следует воспринимать как механизм, который просто запрещает отправлять Cookie. Он определяет, может ли JavaScript получить доступ к cross-origin ответу, а для сложных запросов ещё и участвует в проверке того, разрешён ли сам запрос.

Это нужно помнить при миграции с JWT. С Authorization: Bearer ... разработчик явно передавал credential в HTTP-заголовке. После перехода на Cookie credential начинает контролировать браузер, а вместе с ним появляются дополнительные ограничения CORS.

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

Иногда SameSite недостаточно

Бывает, что приложению необходимо использовать SameSite=None, например, когда Cookie должна отправляться в определенных cross-site сценариях. Тогда браузер уже не сможет использовать SameSite как основную защиту от CSRF. И тогда серверу необходимо иметь дополнительный механизм проверки происхождения запроса. Один из вариантов — CSRF Token.

CSRF Token

SameSite значительно снижает риск CSRF-атак, но не во всех архитектурах его достаточно. Если приложение должно работать в cross-site-сценариях или требуется дополнительный уровень защиты, сервер может использовать CSRF Token.

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

Возникает вопрос: откуда фронтенд получает этот токен? Есть несколько вариантов. Первый – это отдельный эндпоинт, например, приложение после загрузки может обратиться к /api/csrf. Сервер возвращает случайный CSRF-токен, после чего фронт использует его при изменяющих состояние запросах. При обработке запроса сервер проверяет сразу Cookie и X-CSRF-Token. Если сессия недействительна или CSRF-токен отсутствует либо не проходит проверку, сервер отклоняет запрос.

Другой распространённый подход — передать CSRF-токен через отдельную Cookie, которая не имеет атрибута HttpOnly. Например: Set-Cookie: csrf_token=7f83a91c...

В отличие от Cookie с идентификатором сессии, эту Cookie JavaScript может прочитать. Фронт получает её значение и добавляет его в специальный HTTP-заголовок X-CSRF-Token: 7f83a91c...  При этом сама пользовательская сессия продолжает находиться в отдельной HttpOnly Cookie.

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

Такой подход часто называют Double Submit Cookie. Его идея заключается в том, что одно и то же случайное значение должно присутствовать и в Cookie, и в отдельном заголовке запроса.

Сторонний сайт может попытаться заставить браузер отправить нашу Cookie, но не должен иметь возможности прочитать её значение и самостоятельно сформировать корректный X-CSRF-Token.

Токен в HTML

Для приложений с серверным рендерингом есть ещё один вариант, когда сервер может встроить CSRF-токен непосредственно в HTML, который получает браузер. Фронтенд извлекает его из страницы и использует при последующих запросах. Таким образом, конкретный способ передачи токена может отличаться, но принцип остаётся одним, что сервер должен получить от клиента дополнительное значение, которое сторонний сайт не способен корректно воспроизвести.

При этом CSRF-токен не заменяет пользовательскую сессию. Cookie с идентификатором сессии используется для аутентификации запроса, а CSRF-токен даёт серверу дополнительную информацию о контексте, в котором этот запрос был сформирован.

Это не означает, что Cookie и CSRF-токен являются двумя независимыми доказательствами личности пользователя. Это разные механизмы, решающие разные задачи.

Проверка Origin

CSRF-защита не обязательно должна ограничиваться токеном.

Дополнительным механизмом может быть проверка HTTP-заголовка Origin. Браузер передаёт его для многих типов запросов, и сервер может проверить, что запрос пришёл с разрешённого источника. Если вместо него приходит недоверенный источник, запрос можно отклонить ещё до выполнения операции.

Заголовок Referer также может использоваться как дополнительный сигнал, однако Origin обычно удобнее для такой проверки, поскольку он содержит только origin источника, без полного URL.

Для простого same-site приложения с SameSite=Lax дополнительный CSRF-токен может оказаться избыточным. Но если архитектура требует SameSite=None, сложных cross-site-сценариев или повышенного уровня защиты, дополнительная проверка становится значительно более важной.

Реализация на React: Axios и MobX

После перехода на HttpOnly Cookie фронтенд заметно меняет свою структуру.

Раньше он был хранителем ключа от двери и получал JWT, складывал его в localStorage, следил за сроком жизни, добавлял к каждому запросу Authorization и решал, что делать после истечения токена. Теперь ключ от двери ему больше не принадлежит. Cookie хранится в браузере и недоступна JavaScript. Поэтому React-приложению не нужно знать ни значение, ни идентификатор сессии, ни тем более уметь их восстанавливать.

Для этого удобно разделить ответственность между тремя слоями.

Axios отвечает за транспорт — отправляет запросы и, когда это необходимо, позволяет браузеру приложить Cookie к запросу.

Стейт менеджер MobX отвечает за состояние приложения, он знает, кто сейчас авторизован, загружается ли информация о пользователе и нужно ли показать интерфейс.

Backend остаётся единственным местом, где действительно принимается решение: является ли текущий запрос авторизованным.

Аутентификация становится обычным запросом. После отправки формы входа React передаёт логин и пароль на сервер. Сервер проверяет их и, если всё в порядке, создаёт пользовательскую сессию. В ответ он устанавливает HttpOnly Cookie.

На этом работа с секретом для фронтенда заканчивается.  Поэтому после успешного входа фронтенду фактически нужен только один ответ на вопрос: «Кто сейчас авторизован?»

Для этого приложение делает отдельный запрос к условному /me.

Сервер видит Cookie, находит по ней сессию, проверяет её и возвращает данные текущего пользователя. Таким образом, аутентификация превращается из процесса управления токеном в процесс получения состояния.

Что хранить в MobX

В MobX теперь нет необходимости хранить саму учётную информацию, которая даёт доступ к аккаунту. Там находится только состояние интерфейса вокруг авторизации.

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

Это довольно тонкое, но принципиальное разделение. Состояние приложения может быть прочитано JavaScript — и это нормально. Секрет, подтверждающий личность пользователя, JavaScript больше не видит.

Запросы через Axios

Для API имеет смысл создать отдельный экземпляр Axios, настроенный на работу с авторизационными Cookie.

Если API находится на другом origin, запросам понадобится withCredentials. Именно этот параметр говорит браузеру, что запрос должен выполняться с учётом credential-механизма, в том числе Cookie.

При этом важно понимать, что withCredentials не заставляет браузер отправить любую Cookie подряд. Итоговое решение всё равно принимается браузером с учётом Domain, Path, Secure, SameSite и других ограничений.

Точка восстановления состояния

Одним из самых полезных эндпоинтов при такой архитектуре становится условный «/me».

При открытии приложения MobX ещё ничего не знает о пользователе. После перезагрузки страницы его состояние снова создаётся с нуля, поэтому приложение обращается к /me. Если сервер возвращает пользователя, то MobX заполняет состояние, и приложение понимает, что сессия существует.

Если сервер отвечает 401 Unauthorized, значит текущий запрос не прошёл аутентификацию. В нашем сценарии это обычно означает, что действующей пользовательской сессии нет или сервер больше не считает её валидной.

Защищённые маршруты

На уровне React Router логика становится простой. Пока /me выполняется, приложение показывает состояние загрузки. Если пользователь найден — защищённый маршрут становится доступен. Если сервер ответил 401 — пользователь отправляется на страницу входа.

Выход из аккаунта

Logout при такой архитектуре тоже становится проще. Фронтенду не нужно искать токен и удалять его из localStorage. Он просто отправляет запрос на /logout. Сервер завершает сессию и отдаёт браузеру команду удалить соответствующую Cookie. А также сервер должен инвалидировать саму серверную сессию.

После этого MobX очищает информацию о текущем пользователе.

Отдельного внимания заслуживает ответ 401 Unauthorized.

В приложении удобно централизованно обрабатывать такие ответы через Axios interceptor. Если сервер сообщает, что сессия больше недействительна, MobX сбрасывает текущего пользователя.

Причины могут быть разными: сессия истекла, пользователь вышел из аккаунта в другой вкладке, сервер удалил сессию или Cookie больше не подходит. Но нельзя бездумно считать любой 401 командой «немедленно отправить пользователя на /login». Например, 401 при попытке входа может означать всего лишь неправильный пароль.

Поэтому interceptor лучше использовать прежде всего для синхронизации состояния авторизации, а решение о навигации оставить уровню приложения.

Заключение

На этом, думаю, основные принципы мы разобрали. От хранения JWT в localStorage до HttpOnly Cookie меняется не просто место, где лежит credential, а сама ответственность между браузером, фронтендом и сервером.

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