With previous posts, we’ve known how to build Authentication and Authorization in a Vue Vuex Application. In this tutorial, I will continue to show you way to implement Vue Refresh Token with Axios Interceptors and JWT.
Related Posts:
– Axios Interceptors tutorial with Refresh Token example
– In-depth Introduction to JWT-JSON Web Token
– Vue.js JWT Authentication with Vuex and Vue Router
Vue 3 version: Vue 3 Refresh Token with Axios and JWT example
Contents
Vue Refresh Token overview
The diagram shows flow of how we implement Vue.js JWT Refresh Token with Axios.
– A refreshToken
will be provided at the time user signs in.
– A legal JWT
must be added to HTTP Header if Client accesses protected resources.
– With the help of Axios Interceptors, Vue App can check if the accessToken
(JWT) is expired (401), sends /refreshToken
request to receive new accessToken
and use it for new resource request.
Let’s see how the Vue Refresh Token example works with demo UI.
– User makes an account login first.
– Then user can access resources with available Access Token.
– When the Access Token is expired, Vue App automatically send Refresh Token request, receive new Access Token and use it with new request.
So the server still accepts resource access from the user.
– After a period of time, the new Access Token is expired again, and the Refresh Token too.
The Back-end server for this Vue.js Client can be found at:
- Spring Boot JWT Refresh Token example
- Node.js JWT Refresh Token example with MySQL/PostgreSQL
- Node.js JWT Refresh Token example with MongoDB
We’re gonna implement Token Refresh feature basing on the code from previous posts, so you need to read following tutorial first:
Vue.js JWT Authentication with Vuex and Vue Router
Vuex with Refresh Token
Now we need to add a Vuex action and a mutation – refreshToken
.
store/auth.module.js
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
? { status: { loggedIn: true }, user }
: { status: { loggedIn: false }, user: null };
export const auth = {
namespaced: true,
state: initialState,
actions: {
...
refreshToken({ commit }, accessToken) {
commit('refreshToken', accessToken);
}
},
mutations: {
...
refreshToken(state, accessToken) {
state.status.loggedIn = true;
state.user = { ...state.user, accessToken: accessToken };
}
}
};
Axios interceptors in Vue App
Let’s create a service that provides an Axios instance.
services/api.js
import axios from "axios";
const instance = axios.create({
baseURL: "http://localhost:8080/api",
headers: {
"Content-Type": "application/json",
},
});
export default instance;
Then we’re gonna create a setupInterceptors()
function with interceptors
request and dispatch the Vuex action for refresh token in the response
.
services/setupInterceptors.js
import axiosInstance from "./api";
import TokenService from "./token.service";
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
// config.headers["Authorization"] = 'Bearer ' + token; // for Spring Boot back-end
config.headers["x-access-token"] = token; // for Node.js Express back-end
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/signin" && err.response) {
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/refreshtoken", {
refreshToken: TokenService.getLocalRefreshToken(),
});
const { accessToken } = rs.data;
store.dispatch('auth/refreshToken', accessToken);
TokenService.updateLocalAccessToken(accessToken);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
export default setup;
In the code above, we:
– intercept requests or responses before they are handled by then or catch.
– handle 401
status on interceptor response (without response of login request).
– use a flag call _retry
on original Request (config) to handle Infinite loop. It is the case that request is failed again, and the server continue to return 401
status code.
For more details, please visit:
Axios Interceptors tutorial with Refresh Token example
Let’s import the setupInterceptors()
function in main.js where we can pass the Vuex store.
src/main.js
import Vue from 'vue';
import App from './App.vue';
import { router } from './router';
import store from './store';
import Vuex from 'vuex';
...
import setupInterceptors from './services/setupInterceptors';
Vue.use(Vuex);
setupInterceptors(store);
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
Update Services with new Axios Interceptors
Axios Interceptors manipulate the header, body, parameters of the requests sent to the server so that we don’t need to add headers
in Axios requests like this:
axios.get(API_URL, { headers: authHeader() })
So we remove auth-header.js file, then update services that use it with new api
service.
services/user.service.js
import api from './api';
class UserService {
getPublicContent() {
return api.get('/test/all');
}
getUserBoard() {
return api.get('/test/user');
}
getModeratorBoard() {
return api.get('/test/mod');
}
getAdminBoard() {
return api.get('/test/admin');
}
}
export default new UserService();
services/auth.service.js
import api from "./api";
import TokenService from "./token.service";
class AuthService {
login({ username, password }) {
return api
.post("/auth/signin", {
username,
password
})
.then((response) => {
if (response.data.accessToken) {
TokenService.setUser(response.data);
}
return response.data;
});
}
logout() {
TokenService.removeUser();
}
register({ username, email, password }) {
return api.post("/auth/signup", {
username,
email,
password
});
}
}
export default new AuthService();
We also need to create TokenService
which Axios instance and other services use above.
TokenService
provides get, set, remove methods to work with Token and User Data stored on Browser.
services/token.service.js
class TokenService {
getLocalRefreshToken() {
const user = JSON.parse(localStorage.getItem("user"));
return user?.refreshToken;
}
getLocalAccessToken() {
const user = JSON.parse(localStorage.getItem("user"));
return user?.accessToken;
}
updateLocalAccessToken(token) {
let user = JSON.parse(localStorage.getItem("user"));
user.accessToken = token;
localStorage.setItem("user", JSON.stringify(user));
}
getUser() {
return JSON.parse(localStorage.getItem("user"));
}
setUser(user) {
console.log(JSON.stringify(user));
localStorage.setItem("user", JSON.stringify(user));
}
removeUser() {
localStorage.removeItem("user");
}
}
export default new TokenService();
Handle expired Token in Vue
When both Access Token and Refresh Token are expired, we need to logout the user. How to do it? We read response status from the server, then dispatch logout
event to App
component when the response status tells us the JWT token is expired.
First we need to set up a global event-driven system, or a PubSub system, which allows us to listen and dispatch events from independent components.
An Event Bus implements the PubSub pattern, events will be fired from other components so that they don’t have direct dependencies between each other.
We’re gonna create Event Bus with three methods: on
, dispatch
, and remove
.
common/EventBus.js
const eventBus = {
on(event, callback) {
document.addEventListener(event, (e) => callback(e.detail));
},
dispatch(event, data) {
document.dispatchEvent(new CustomEvent(event, { detail: data }));
},
remove(event, callback) {
document.removeEventListener(event, callback);
},
};
export default eventBus;
– on()
method attachs an EventListener
to the document
object. The callback
will be called when the event
gets fired.
– dispatch()
method fires an event
using the CustomEvent
API.
– remove()
method removes the attached event
from the document
object.
Next we import EventBus
in App component and listen to "logout"
event.
src/App.vue
<template>
..
</template>
<script>
import EventBus from "./common/EventBus";
export default {
computed: {
..
},
methods: {
logOut() {
this.$store.dispatch('auth/logout');
this.$router.push('/login');
}
},
mounted() {
EventBus.on("logout", () => {
this.logOut();
});
},
beforeDestroy() {
EventBus.remove("logout");
}
};
</script>
Finally we only need to dispatch "logout"
event in the components when getting Unauthorized
response status.
components/BoardUser.js
<template>
..
</template>
<script>
import UserService from '../services/user.service';
import EventBus from "../common/EventBus";
export default {
name: 'User',
...
mounted() {
UserService.getUserBoard().then(
response => { ... },
error => {
this.content =
(error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString();
if (error.response && error.response.status === 403) {
EventBus.dispatch("logout");
}
}
);
}
};
</script>
Conclusion
Today we know how to implement JWT Refresh Token into a Vue Vuex Application using Axios Interceptors. For your understanding the logic flow, you should read one of following tutorial first:
Vue.js JWT Authentication with Vuex and Vue Router
The Back-end server for this Vue Client can be found at:
- Spring Boot JWT Refresh Token example
- Node.js JWT Refresh Token example with MySQL/PostgreSQL
- Node.js JWT Refresh Token example with MongoDB
Further Reading
Related Posts:
– In-depth Introduction to JWT-JSON Web Token
– Axios Interceptors tutorial with Refresh Token example
Fullstack Authentication & Authorization:
– Vue + Spring Boot
– Vue + Node.js Express
Source Code
The source code for this React Application can be found at Github.
Vue 3 version: Vue 3 Refresh Token with Axios and JWT example
Hi and thanks for very nice tutorials!
A question:
Is it a bad idea to check for 403 (=expired refresh token) in the interceptor once and for all and there dispatch the
‘logout’ to the event bus, rather than – as in the example – do this for each call to the API in the component or view?
what does mean “?.” in “return user?.refreshToken;” ? And how to replace it in other way? I get errror ts “Expression expected.”
Hi,
‘Spring boot refresh token’ project is working fine with Postman. But it is failing with ‘Vue js Refresh Token’ project. SignIn, SignOut and Public contents links are working fine. But role access links (User, Moderator, Administartor) are failing with 401 status code (Unauthorized access).
I have checked in browser network that accesstoken is passing with GET request. But still it is throwing unauthorized access error.
Please help 🙂
Hi,
I got your comment in setupInterceptors.js file on line number 9. Where I have set token with Bearer prefix in Authorization header.
Thanks 🙂
Hi,
I am getting CORS error during refreshtoken in DevTools Network.
Please guide 🙂
Hi, you can check if your backend code accepts resource sharing from the client or not. If you use my code for server, check the port.
I was using wrong server side code which was not using refresh authentication. Now this problem is solved.
Thanks 🙂
Hi, I have a question. Why would you use an EventBus instead of using a vuex getter that returns the value of the Vuex store state auth.status.loggedIn ? You could add a watcher to this getter in the App.vue.
Was this a design decision based on exploring the concept of an Event Bus besides Vuex for this tutorial?
Shouldn’t we keep just use Vuex Store/State changes as the triggers for the components changes?