问题
Note: I have seperated my client(Vue.js) and server(DjangoRest). I'm using JWT to validate every request made from the client to the server. Flow- Client sends user credentials to server. Server sends back a refresh and access token if credentials are valid. Client stores the access and refresh token. I have set the refresh token expiry to 1 week,access to 30 mins. Next, I want to make sure that the access token is auto refreshed 15 mins prior to its expiry. To do this, the stored refresh token in client side is send to the server, the server then issues a new access token and refresh token, sends it back to the client. How do i implement this in the Vuex store?. I'm a complete newbie to web development and vue.js. It would be great if someone could provide some code or explain in details.
I have already implemented loginUser,logout user,registerUser in store and they are working fine. But I'm stuck with the auto refresh logic. My guess is that the client has to repeatedly check the access token expiry time left. When about 15 mins is left, we have to initialize the autorefresh function. Please help me with this logic.
Here's my Vueex store:
import Vue from 'vue'
import Vuex from 'vuex'
import axiosBase from './api/axios-base'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
accessToken: '' || null,
refreshToken: '' || null
},
getters: {
loggedIn (state) {
return state.accessToken != null
}
},
mutations: {
loginUser (state) {
state.accessToken = localStorage.getItem('access_token')
state.refreshToken = localStorage.getItem('refresh_token')
},
destroyToken (state) {
state.accessToken = null
state.refreshToken = null
}
},
actions: {
registerUser (context, data) {
return new Promise((resolve, reject) => {
this.axios.post('/register', {
name: data.name,
email: data.email,
username: data.username,
password: data.password,
confirm: data.confirm
})
.then(response => {
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
// fetch data from api whenever required.
backendAPI (context, data) {
},
logoutUser (context) {
if (context.getters.loggedIn) {
return new Promise((resolve, reject) => {
axiosBase.post('/api/token/logout/')
.then(response => {
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
context.commit('destroyToken')
})
.catch(error => {
context.commit('destroyToken')
resolve()
})
})
}
},
autoRefresh (context, credentials) {
},
loginUser (context, credentials) {
return new Promise((resolve, reject) => {
axiosBase.post('/api/token/', {
username: credentials.username,
password: credentials.password
})
.then(response => {
localStorage.setItem('access_token', response.data.access)
localStorage.setItem('refresh_token', response.data.refresh)
context.commit('loginUser')
resolve(response)
})
.catch(error => {
console.log(error)
reject(error)
})
})
}
}
})
Thank you in advance.
回答1:
This is very much an idea question as you've pointed out and as such, there are many ways of solving it.
One thing I try to keep in mind when dealing with such mechanisms is to always avoid polling when possible. Here's a solution inspired by that design principle.
JWT tokens are valid for a very specific amount of time. The time left for expiration is readily available as part of the access token. You can use a library such as jwt-decode to decode the access token and extract the expiration time. Once you have the expiration time, you have a several options available:
- Check token every time before making a request to know if it needs to be refreshed
- Use
setTimeout
to refresh it periodically X seconds before it expires
Your code could be implemented as follows:
Note: Please treat the following as pseudo-code. I have not tested it for errors---syntax or otherwise.
export default new Vuex.Store({
...
actions: {
refreshTokens (context, credentials) {
// Do whatever you need to do to exchange refresh token for access token
...
// Finally, call autoRefresh to set up the new timeout
dispatch('autoRefresh', credentials)
},
autoRefresh (context, credentials) {
const { state, commit, dispatch } = context
const { accessToken } = state
const { exp } = jwt_decode(accessToken)
const now = Date.now() / 1000 // exp is represented in seconds since epoch
let timeUntilRefresh = exp - now
timeUntilRefresh -= (15 * 60) // Refresh 15 minutes before it expires
const refreshTask = setTimeout(() => dispatch('refreshTokens', credentials), timeUntilRefresh * 1000)
commit('refreshTask', refreshTask) // In case you want to cancel this task on logout
}
}
})
回答2:
I think it's better to rely on your server response code than expiration time. Try to access the protected route, if it returns 401, ask for a new access token and than try again. If your refresh route also returns 401, make your user log in again.
Here we use this flow. I'm the backend developer so I will explain in pseudocode. You can implement an interceptor in Vuex with no problems.
res = router.desiredRoute();
// if not authorized, get a new access token from refresh route
if(res === 401) {
refreshRes = router.refreshRoute();
// if not authorized again, you need a new refresh
if(refreshRes === 401) {
logout();
router.loginPage();
} else {
// store your new access token an go to desired route again
storeAccessToken()
router.desiredRoute();
}
}
来源:https://stackoverflow.com/questions/55747521/how-to-implement-auto-refresh-in-client-sidevue-js