2 - на клиенте они пишутся в localStorage, и далее в каждом запросе передаются в хэдере
Нет, не в ls, а в стейте приложения
При правильном подходе JWT, это когда refreshToken присылается в httpOnly куке, к которой програмно ты не имеешь доступа ну никак. Плюс access токен в теле ответа.
В access токене зашифрована какая либо инфа, обычно время жизни и какие либо стартовые данные.
Вот время жизни и текущую дату ты уже пишешь в ls и при каждом запросе к серверу, перехватывая запрос проверяешь не истекло ли время жизни. Если истекло, то запрос отменяешь. А вместо него отправляешь запрос на рефреш (тут уже нужен рефреш токен, время жизни которого обычно гораздо больше чем у access, например 30дней против 1 дня) и после успешного рефреша посылаешь тот самый запрос, который был прерван
почему refresh только в httpOnly куке - Если он будет приходить в ответе с access токеном, то сразу две уязвимости: могут перехватить запрос и в нем получат сразу и access и refresh токены и спокойно зайдут в систему, либо своровать куки и access из приложения и опять же получить доступ ко всему
1) Как быть с несколькими устройствами при такой схеме? Получается, что пользователя разлогинит на неактивном устройстве?
Полагаю что так - один общий рефреш на все устройства и разные access токены. Таким образом можно отличать устройства юзера, какие залогинены, какие надо разлогинить