Привет всем.
У меня есть небольшое приложение на Vue.js (2.6).
В приложении можно авторизоваться. Токен (JWT) живёт 1 день.
Недавно решил посмотреть, как работают системы с рефреш токенами (надоело всё время авторизовываться заново). Увы, не смог найти ничего путного, кроме как решения с Axios.
Так встали звёзды, что я не люблю Axios, да и вообще какие-то сторонние библиотеки для запросов. Да и в целом не люблю сторонние пакеты - Vue и так за собой 100500 зависимостей тянет...
Приложения работает на нативном Fetch API.
На сколько я понимаю принцип работы рефреш токена, если в ответ на запрос нам прилетает "токен просрочен", то мы сохраняем параметры последнего запроса и делаем запрос на получение свежего токена. Если приходит новый токен, достаём последний сохранённый запрос и повторяем его с новым токеном.
Вот пример рядового запроса из модуля в сторе:
async readUsers({rootState, state, getters, dispatch}, payload) {
const token = rootState.token.access
if(token) {
state.usersProcess = true
let params = ''
if(payload) {
for( let param in payload) {
params.length ?
params += '&' + param + '=' + payload[param] :
params = '?' + param + '=' + payload[param]
}
}
const request = await dispatch('fetchRequest', {
address: `${rootState.config.httpServer}/users${params}`,
method: 'GET',
token: true
})
if(!request.error) {
state.users = request
state.usersMsg = { success: true }
} else {
state.usersMsg = { success: false }
state.usersMsg.text = getters.getTranslate[request.error] || request.error
}
state.usersProcess = false
} else {
state.usersMsg = { success: false, text: getters.getTranslate.EUnauthRequest }
}
}
Для запросов написал такой "хелпер":
async fetchRequest({rootState, dispatch}, payload) {
let parameters = {
method: payload.method,
cache: 'no-cache',
referrerPolicy: 'origin-when-cross-origin',
headers: new Headers()
}
if(payload.token) {
parameters.headers.append('Authorization', 'Bearer ' + rootState.token.access)
}
if(payload.data) {
f(payload.type) {
payload.type == 'json' ?
parameters.body = JSON.stringify(payload.data) :
parameters.body = payload.data
} else {
parameters.body = JSON.stringify(payload.data)
}
}
try {
const request = await fetch(payload.address, parameters)
const response = await request.text()
const result = JSON.parse( response )
if(request.ok) {
return result
} else if(result.details) {
if(result.details == 'TokenExpired') {
dispatch('refreshToken').then(() => {
if(rootState.token.access) {
return dispatch('fetchRequest', payload)
}
})
} else if(result.details == 'TokenError') {
localStorage.clear()
document.location.reload()
}
return { error: result.details }
} else if(result.message) {
return { error: result.message }
} else {
return { error: 'EUnknown' }
}
} catch {
return { error: 'EConnection' }
}
}
Экшен для получения свежего токена:
async refreshToken({rootState, state, dispatch}) {
const request = await dispatch('fetchRequest', {
address: `${rootState.config.httpServer}/refresh_tokens`,
method: 'POST',
data: `"${rootState.token.refresh}"`,
type: 'string'
})
if(request.tokens) {
state.access = request.tokens.access
state.refresh = request.tokens.refresh
localStorage.setItem('TOKEN_ACCESS', state.access)
localStorage.setItem('TOKEN_REFRESH', state.refresh)
} else {
state.access = null
state.refresh = null
localStorage.clear()
}
}
И тут я столкнулся с проблемой: каждый компонент с эарана отправляет асинхронные запросы, несколько компонентов могут получить ответ "токен просрочен" практически одновременно. Затем каждый из них отправит запрос на обновление токена, послав параметром старый. Первому компоненту вернётся корректный ответ (новый токен). А вот получив запрос с от остальных компонентов сервер уже вернёт ошибку "неверный токен", т.к. такого у него уже нет (он был заменён другом ранее).
Вероятно, с самого начала ошибка проектирования, но как сделать по другому никак не могу сообразить. На сколько я понимаю, нужен какой-нибудь промис, по которому будет понятно находится ли система в стадии восстановления токена и, по окончании которого, нужно будет продолжить запросы уже с новым токеном. Но в реализации что-то совсем запутался.
Пытался объяснить максимально подробно, надеюсь, что понятно о чём я говорю
Подсобите советом!