Vue Axios拦截器响应Firebase 401令牌过期/刷新(未定义)
我在 Vuejs v2 网站中使用以下拦截器将 firebase 令牌推送到我的节点后端。在后端,我检测/验证令牌,使用 uid 从数据库中提取一些数据,然后处理任何 api 调用。
即使我使用 firebase onIdTokenChanged 自动检索新的 ID 令牌,有时,如果用户已登录但处于非活动状态一小时,令牌就会过期而不会刷新。现在,这不是什么大问题 - 我可以检查 axios 响应拦截器并将它们推送到登录页面,但这似乎很烦人,如果我可以检测到 401 令牌过期,重新发送 axios 调用并刷新令牌,如果用户碰巧与需要 API 调用数据的组件进行交互,他们甚至不会知道发生了这种情况。所以这就是我所拥有的:
main.js
Vue.prototype.$axios.interceptors.request.use(function (config) {
const token = store.getters.getSessionToken;
config.headers.Authorization = `Bearer ${token}`;
return config;
});
Vue.prototype.$axios.interceptors.response.use((response) => {
return response }, async function (error) {
let originalRequest = error.config
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
let user = auth.currentUser;
await store.dispatch("setUser", {user: user, refresh: true}).then(() => {
const token = store.getters.getSessionToken;
Vue.prototype.$axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
return Vue.prototype.$axios.request(originalRequest);
});
}
return Promise.reject(error); });
let app;
auth.onAuthStateChanged(async user => {
await store.dispatch("setUser", {user: user, refresh: false}).then(() => {
if (!app) {
app = new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
}
})
.catch(error => {
console.log(error);
});
});
vuex
setUser({dispatch, commit}, {user, refresh}) {
return new Promise((resolve) => {
if(user)
{
user.getIdToken(refresh).then(token => {
commit('SET_SESSION_TOKEN', token);
this._vm.$axios.get('/api/user/session').then((response) => {
if(response.status === 200) {
commit('SET_SESSION_USER', response.data);
resolve(response);
}
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Server unavailable: '+error
});
resolve();
});
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Unable to verify auth token.'+error
});
resolve();
});
}
else
{
console.log('running logout');
commit('SET_SESSION_USER', null);
commit('SET_SESSION_TOKEN', null);
resolve();
}
})
},
我在 vuex 中设置令牌,然后在所有 API 调用的拦截器中使用它。所以我在这段代码中看到的问题是,我正在使用过期的令牌向后端进行 API 调用。这将返回 401,axios 响应拦截器会拾取它并执行刷新 firebase 令牌的过程。然后,使用更新后的令牌向后端发出与原始配置相同的新 API 调用,并将其返回到原始 API 调用(如下)。
这一切似乎都有效,我可以在开发工具/网络中看到,API 调用的响应正在发送回正确的数据。然而,它似乎陷入了以下 api 调用/代码的陷阱。例如,当尝试使用response.data.server加载表单字段时,我收到“未定义”。如果我刷新页面,该页面会正常加载所有内容(同样,正常的令牌/加载过程应该如此),所以我知道不存在加载问题。
vue 组件(将 smtp 设置加载到页面中)
getSMTPSettings: async function() {
await this.$axios.get('/api/smtp')
.then((response) => {
this.form.server = response.data.server;
this.form.port = response.data.port;
this.form.authemail = response.data.authemail;
this.form.authpassword = response.data.authpassword;
this.form.sendemail = response.data.sendemail;
this.form.testemail = response.data.testemail;
this.form.protocol = response.data.protocol;
})
.catch(error => {
console.log(error);
});
},
我已经研究这个好几天了,但我不明白为什么它不会加载它。数据好像是有的。我正在做的事情的时间安排是否给我带来了问题?这似乎不是 CORS 问题,我没有收到任何错误。
I'm using the following interceptors in a Vuejs v2 website to push a firebase token to my node backend. There in the backend, I detect/verify the token, pull some data using the uid from a database and then process any api calls.
Even though I am using the firebase onIdTokenChanged to automatically retrieve new ID tokens, sometimes, if the user is logged in, yet inactive for an hour, the token expires without refreshing. Now, this isn't a huge deal - I could check in the axios response interceptor and push them to a login page, but that seems annoying, if I can detect a 401 token expired, resend the axios call and have a refreshed token, the user won't even know it happened if they happen to interact with a component that requires data from an API call. So here is what I have:
main.js
Vue.prototype.$axios.interceptors.request.use(function (config) {
const token = store.getters.getSessionToken;
config.headers.Authorization = `Bearer ${token}`;
return config;
});
Vue.prototype.$axios.interceptors.response.use((response) => {
return response }, async function (error) {
let originalRequest = error.config
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
let user = auth.currentUser;
await store.dispatch("setUser", {user: user, refresh: true}).then(() => {
const token = store.getters.getSessionToken;
Vue.prototype.$axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
return Vue.prototype.$axios.request(originalRequest);
});
}
return Promise.reject(error); });
let app;
auth.onAuthStateChanged(async user => {
await store.dispatch("setUser", {user: user, refresh: false}).then(() => {
if (!app) {
app = new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
}
})
.catch(error => {
console.log(error);
});
});
vuex
setUser({dispatch, commit}, {user, refresh}) {
return new Promise((resolve) => {
if(user)
{
user.getIdToken(refresh).then(token => {
commit('SET_SESSION_TOKEN', token);
this._vm.$axios.get('/api/user/session').then((response) => {
if(response.status === 200) {
commit('SET_SESSION_USER', response.data);
resolve(response);
}
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Server unavailable: '+error
});
resolve();
});
})
.catch(error => {
dispatch('logout');
dispatch('setSnackbar', {
color: "error",
timeout: 4000,
text: 'Unable to verify auth token.'+error
});
resolve();
});
}
else
{
console.log('running logout');
commit('SET_SESSION_USER', null);
commit('SET_SESSION_TOKEN', null);
resolve();
}
})
},
I am setting the token in vuex and then using it in the interceptors for all API calls. So the issue I am seeing with this code is, I'm making an API call with an expired token to the backend. This returns a 401 and the axios response interceptor picks it up and goes through the process of refreshing the firebase token. This then makes a new API call with the same config as the original to the backend with the updated token and returns it to the original API call (below).
This all seems to work, and I can see in dev tools/network, the response from the API call is sending back the correct data. However, it seems to be falling into the catch of the following api call/code. I get an "undefined" when trying to load the form field with response.data.server, for example. This page loads everything normally if I refresh the page (again, as it should with the normal token/loading process), so I know there aren't loading issues.
vue component (loads smtp settings into the page)
getSMTPSettings: async function() {
await this.$axios.get('/api/smtp')
.then((response) => {
this.form.server = response.data.server;
this.form.port = response.data.port;
this.form.authemail = response.data.authemail;
this.form.authpassword = response.data.authpassword;
this.form.sendemail = response.data.sendemail;
this.form.testemail = response.data.testemail;
this.form.protocol = response.data.protocol;
})
.catch(error => {
console.log(error);
});
},
I have been looking at this for a few days and I can't figure out why it won't load it. The data seems to be there. Is the timing of what I'm doing causing me issues? It doesn't appear to be a CORS problem, I am not getting any errors there.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您的主要问题是将 async/await 与
.then()
混合。您的响应拦截器不会返回下一个响应,因为您已将该部分包装在then()
中,而没有返回外部承诺。随处使用 async/await 让事情变得简单。
此外,设置通用标头会破坏使用拦截器的意义。你已经有了一个请求拦截器,让它完成它的工作
你的存储操作也属于显式承诺构造反模式并且可以简化
Your main issue is mixing async / await with
.then()
. Your response interceptor isn't returning the next response because you've wrapped that part inthen()
without returning the outer promise.Keep things simple with async / await everywhere.
Also, setting common headers defeats the point in using interceptors. You've already got a request interceptor, let it do its job
Your store action also falls into the explicit promise construction antipattern and can be simplified