Поделюсь своим решением. Хотя у меня на сервере стоят рельсы, а реакт только на морде. Но тем не менее, вдруг будет полезно.
При авторизации сервер генерит:
- access token
- cookie
- refresh token
которые затем гоняются между клиентом и сервером.
Refresh token может отсутствовать. Вообще говоря он нужен только для обновления access token, и клиент добавляет его в запрос только когда истекает срок действия access token. В этом случае сервер генерит новый access и refresh token.
Cookie отдается с пометкой HTTP only, то есть клиент их прочитать никак не может. Cookie нужны только для проверки валидности access token, то есть никаких сессий по ним нет (вообще приложение полностью stateless).
Оба токена (access и refresh) клиент хранит в local storage, откуда извлекает их для каждого запроса на сервер.
Любой запрос от клиента, содержащий валидный access token, считается авторизованным, только если в нем есть соответствующие токену куки (не совпадающие, а соответствующие, то есть закодированные по какому-то алгоритму с access token в качестве базы). Ну и если есть refresh token, то также смотрим, подходит ли он к нашему access token.
Такая схема, в совокупности с HTTPS, неплохо защищает от XSS или от CSRF уязвимостей (оговорка: но не от обеих сразу).
Немного теории, почему это работает, можно прочитать здесь:
www.redotheweb.com/2015/11/09/api-security.html