├── vue.config.js ├── babel.config.js ├── public ├── favicon.ico └── index.html ├── src ├── assets │ └── logo.png ├── models │ └── user.js ├── store │ ├── index.js │ └── auth.module.js ├── services │ ├── api.js │ ├── user.service.js │ ├── auth.service.js │ ├── token.service.js │ └── setupInterceptors.js ├── common │ └── EventBus.js ├── views │ ├── Home.vue │ ├── BoardAdmin.vue │ ├── BoardUser.vue │ ├── BoardModerator.vue │ ├── Profile.vue │ ├── Login.vue │ └── Register.vue ├── main.js ├── router.js └── App.vue ├── vue-refresh-token-axios-jwt-example-flow.png ├── .gitignore ├── package.json └── README.md /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | port: 8081 4 | } 5 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/vue-axios-refresh-token/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/vue-axios-refresh-token/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /vue-refresh-token-axios-jwt-example-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/vue-axios-refresh-token/HEAD/vue-refresh-token-axios-jwt-example-flow.png -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | export default class User { 2 | constructor(username, email, password) { 3 | this.username = username; 4 | this.email = email; 5 | this.password = password; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import { auth } from './auth.module'; 5 | 6 | Vue.use(Vuex); 7 | 8 | export default new Vuex.Store({ 9 | modules: { 10 | auth 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /src/services/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const instance = axios.create({ 4 | baseURL: "http://localhost:8080/api", 5 | headers: { 6 | "Content-Type": "application/json", 7 | }, 8 | }); 9 | 10 | export default instance; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /src/common/EventBus.js: -------------------------------------------------------------------------------- 1 | const eventBus = { 2 | on(event, callback) { 3 | document.addEventListener(event, (e) => callback(e.detail)); 4 | }, 5 | dispatch(event, data) { 6 | document.dispatchEvent(new CustomEvent(event, { detail: data })); 7 | }, 8 | remove(event, callback) { 9 | document.removeEventListener(event, callback); 10 | }, 11 | }; 12 | 13 | export default eventBus; 14 | -------------------------------------------------------------------------------- /src/services/user.service.js: -------------------------------------------------------------------------------- 1 | import api from './api'; 2 | 3 | class UserService { 4 | getPublicContent() { 5 | return api.get('/test/all'); 6 | } 7 | 8 | getUserBoard() { 9 | return api.get('/test/user'); 10 | } 11 | 12 | getModeratorBoard() { 13 | return api.get('/test/mod'); 14 | } 15 | 16 | getAdminBoard() { 17 | return api.get('/test/admin'); 18 | } 19 | } 20 | 21 | export default new UserService(); 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue Refresh Token with Axios example 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 34 | -------------------------------------------------------------------------------- /src/services/auth.service.js: -------------------------------------------------------------------------------- 1 | import api from "./api"; 2 | import TokenService from "./token.service"; 3 | 4 | class AuthService { 5 | login({ username, password }) { 6 | return api 7 | .post("/auth/signin", { 8 | username, 9 | password 10 | }) 11 | .then((response) => { 12 | if (response.data.accessToken) { 13 | TokenService.setUser(response.data); 14 | } 15 | 16 | return response.data; 17 | }); 18 | } 19 | 20 | logout() { 21 | TokenService.removeUser(); 22 | } 23 | 24 | register({ username, email, password }) { 25 | return api.post("/auth/signup", { 26 | username, 27 | email, 28 | password 29 | }); 30 | } 31 | } 32 | 33 | export default new AuthService(); 34 | -------------------------------------------------------------------------------- /src/services/token.service.js: -------------------------------------------------------------------------------- 1 | class TokenService { 2 | getLocalRefreshToken() { 3 | const user = JSON.parse(localStorage.getItem("user")); 4 | return user?.refreshToken; 5 | } 6 | 7 | getLocalAccessToken() { 8 | const user = JSON.parse(localStorage.getItem("user")); 9 | return user?.accessToken; 10 | } 11 | 12 | updateLocalAccessToken(token) { 13 | let user = JSON.parse(localStorage.getItem("user")); 14 | user.accessToken = token; 15 | localStorage.setItem("user", JSON.stringify(user)); 16 | } 17 | 18 | getUser() { 19 | return JSON.parse(localStorage.getItem("user")); 20 | } 21 | 22 | setUser(user) { 23 | console.log(JSON.stringify(user)); 24 | localStorage.setItem("user", JSON.stringify(user)); 25 | } 26 | 27 | removeUser() { 28 | localStorage.removeItem("user"); 29 | } 30 | } 31 | 32 | export default new TokenService(); 33 | -------------------------------------------------------------------------------- /src/views/BoardAdmin.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | -------------------------------------------------------------------------------- /src/views/BoardUser.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | -------------------------------------------------------------------------------- /src/views/BoardModerator.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import { router } from './router'; 4 | import store from './store'; 5 | import 'bootstrap'; 6 | import 'bootstrap/dist/css/bootstrap.min.css'; 7 | import VeeValidate from 'vee-validate'; 8 | import Vuex from 'vuex'; 9 | import { library } from '@fortawesome/fontawesome-svg-core'; 10 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'; 11 | import { 12 | faHome, 13 | faUser, 14 | faUserPlus, 15 | faSignInAlt, 16 | faSignOutAlt 17 | } from '@fortawesome/free-solid-svg-icons'; 18 | 19 | import setupInterceptors from './services/setupInterceptors'; 20 | 21 | library.add(faHome, faUser, faUserPlus, faSignInAlt, faSignOutAlt); 22 | 23 | Vue.config.productionTip = false; 24 | 25 | Vue.use(VeeValidate); 26 | Vue.component('font-awesome-icon', FontAwesomeIcon); 27 | 28 | Vue.use(Vuex); 29 | 30 | setupInterceptors(store); 31 | 32 | new Vue({ 33 | router, 34 | store, 35 | render: h => h(App) 36 | }).$mount('#app'); 37 | -------------------------------------------------------------------------------- /src/views/Profile.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-axios-refresh-token", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "^1.2.35", 12 | "@fortawesome/free-solid-svg-icons": "^5.15.3", 13 | "@fortawesome/vue-fontawesome": "^2.0.2", 14 | "axios": "^0.21.1", 15 | "bootstrap": "^4.6.0", 16 | "core-js": "^3.6.5", 17 | "jquery": "^3.6.0", 18 | "popper.js": "^1.16.1", 19 | "vee-validate": "^2.2.15", 20 | "vue": "^2.6.11", 21 | "vue-router": "^3.5.2", 22 | "vuex": "^3.6.2" 23 | }, 24 | "devDependencies": { 25 | "@vue/cli-plugin-babel": "~4.5.0", 26 | "@vue/cli-plugin-eslint": "~4.5.0", 27 | "@vue/cli-plugin-router": "~4.5.0", 28 | "@vue/cli-plugin-vuex": "~4.5.0", 29 | "@vue/cli-service": "~4.5.0", 30 | "babel-eslint": "^10.1.0", 31 | "eslint": "^6.7.2", 32 | "eslint-plugin-vue": "^6.2.2", 33 | "vue-template-compiler": "^2.6.11" 34 | }, 35 | "eslintConfig": { 36 | "root": true, 37 | "env": { 38 | "node": true 39 | }, 40 | "extends": [ 41 | "plugin:vue/essential", 42 | "eslint:recommended" 43 | ], 44 | "parserOptions": { 45 | "parser": "babel-eslint" 46 | }, 47 | "rules": {} 48 | }, 49 | "browserslist": [ 50 | "> 1%", 51 | "last 2 versions", 52 | "not dead" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /src/services/setupInterceptors.js: -------------------------------------------------------------------------------- 1 | import axiosInstance from "./api"; 2 | import TokenService from "./token.service"; 3 | 4 | const setup = (store) => { 5 | axiosInstance.interceptors.request.use( 6 | (config) => { 7 | const token = TokenService.getLocalAccessToken(); 8 | if (token) { 9 | // config.headers["Authorization"] = 'Bearer ' + token; // for Spring Boot back-end 10 | config.headers["x-access-token"] = token; // for Node.js Express back-end 11 | } 12 | return config; 13 | }, 14 | (error) => { 15 | return Promise.reject(error); 16 | } 17 | ); 18 | 19 | axiosInstance.interceptors.response.use( 20 | (res) => { 21 | return res; 22 | }, 23 | async (err) => { 24 | const originalConfig = err.config; 25 | 26 | if (originalConfig.url !== "/auth/signin" && err.response) { 27 | // Access Token was expired 28 | if (err.response.status === 401 && !originalConfig._retry) { 29 | originalConfig._retry = true; 30 | 31 | try { 32 | const rs = await axiosInstance.post("/auth/refreshtoken", { 33 | refreshToken: TokenService.getLocalRefreshToken(), 34 | }); 35 | 36 | const { accessToken } = rs.data; 37 | 38 | store.dispatch('auth/refreshToken', accessToken); 39 | TokenService.updateLocalAccessToken(accessToken); 40 | 41 | return axiosInstance(originalConfig); 42 | } catch (_error) { 43 | return Promise.reject(_error); 44 | } 45 | } 46 | } 47 | 48 | return Promise.reject(err); 49 | } 50 | ); 51 | }; 52 | 53 | export default setup; -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import Home from './views/Home.vue'; 4 | import Login from './views/Login.vue'; 5 | import Register from './views/Register.vue'; 6 | 7 | Vue.use(Router); 8 | 9 | export const router = new Router({ 10 | mode: 'history', 11 | routes: [ 12 | { 13 | path: '/', 14 | name: 'home', 15 | component: Home 16 | }, 17 | { 18 | path: '/home', 19 | component: Home 20 | }, 21 | { 22 | path: '/login', 23 | component: Login 24 | }, 25 | { 26 | path: '/register', 27 | component: Register 28 | }, 29 | { 30 | path: '/profile', 31 | name: 'profile', 32 | // lazy-loaded 33 | component: () => import('./views/Profile.vue') 34 | }, 35 | { 36 | path: '/admin', 37 | name: 'admin', 38 | // lazy-loaded 39 | component: () => import('./views/BoardAdmin.vue') 40 | }, 41 | { 42 | path: '/mod', 43 | name: 'moderator', 44 | // lazy-loaded 45 | component: () => import('./views/BoardModerator.vue') 46 | }, 47 | { 48 | path: '/user', 49 | name: 'user', 50 | // lazy-loaded 51 | component: () => import('./views/BoardUser.vue') 52 | } 53 | ] 54 | }); 55 | 56 | // router.beforeEach((to, from, next) => { 57 | // const publicPages = ['/login', '/register', '/home']; 58 | // const authRequired = !publicPages.includes(to.path); 59 | // const loggedIn = localStorage.getItem('user'); 60 | 61 | // // trying to access a restricted page + not logged in 62 | // // redirect to login page 63 | // if (authRequired && !loggedIn) { 64 | // next('/login'); 65 | // } else { 66 | // next(); 67 | // } 68 | // }); 69 | -------------------------------------------------------------------------------- /src/store/auth.module.js: -------------------------------------------------------------------------------- 1 | import AuthService from '../services/auth.service'; 2 | 3 | const user = JSON.parse(localStorage.getItem('user')); 4 | const initialState = user 5 | ? { status: { loggedIn: true }, user } 6 | : { status: { loggedIn: false }, user: null }; 7 | 8 | export const auth = { 9 | namespaced: true, 10 | state: initialState, 11 | actions: { 12 | login({ commit }, user) { 13 | return AuthService.login(user).then( 14 | user => { 15 | commit('loginSuccess', user); 16 | return Promise.resolve(user); 17 | }, 18 | error => { 19 | commit('loginFailure'); 20 | return Promise.reject(error); 21 | } 22 | ); 23 | }, 24 | logout({ commit }) { 25 | AuthService.logout(); 26 | commit('logout'); 27 | }, 28 | register({ commit }, user) { 29 | return AuthService.register(user).then( 30 | response => { 31 | commit('registerSuccess'); 32 | return Promise.resolve(response.data); 33 | }, 34 | error => { 35 | commit('registerFailure'); 36 | return Promise.reject(error); 37 | } 38 | ); 39 | }, 40 | refreshToken({ commit }, accessToken) { 41 | commit('refreshToken', accessToken); 42 | } 43 | }, 44 | mutations: { 45 | loginSuccess(state, user) { 46 | state.status.loggedIn = true; 47 | state.user = user; 48 | }, 49 | loginFailure(state) { 50 | state.status.loggedIn = false; 51 | state.user = null; 52 | }, 53 | logout(state) { 54 | state.status.loggedIn = false; 55 | state.user = null; 56 | }, 57 | registerSuccess(state) { 58 | state.status.loggedIn = false; 59 | }, 60 | registerFailure(state) { 61 | state.status.loggedIn = false; 62 | }, 63 | refreshToken(state, accessToken) { 64 | state.status.loggedIn = true; 65 | state.user = { ...state.user, accessToken: accessToken }; 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Refresh Token with Axios and JWT example 2 | 3 | ![vue-refresh-token-axios-jwt-example-flow](vue-refresh-token-axios-jwt-example-flow.png) 4 | 5 | For instruction, please visit: 6 | > [Vue Refresh Token with Axios and JWT example](https://www.bezkoder.com/vue-refresh-token/) 7 | 8 | > [Vue 2 JWT Authentication with Vuex and Vue Router](https://bezkoder.com/jwt-vue-vuex-authentication/) 9 | 10 | ## Note: 11 | Open `src/services/setupInterceptors.js` and modify `config.headers` for appropriate back-end (found in the tutorial). 12 | 13 | ```js 14 | instance.interceptors.request.use( 15 | (config) => { 16 | const token = TokenService.getLocalAccessToken(); 17 | if (token) { 18 | // config.headers["Authorization"] = 'Bearer ' + token; // for Spring Boot back-end 19 | config.headers["x-access-token"] = token; // for Node.js Express back-end 20 | } 21 | return config; 22 | }, 23 | (error) => { 24 | return Promise.reject(error); 25 | } 26 | ); 27 | ``` 28 | 29 | ## Project setup 30 | ``` 31 | npm install 32 | ``` 33 | 34 | ### Compiles and hot-reloads for development 35 | ``` 36 | npm run serve 37 | ``` 38 | 39 | 40 | ## Related Posts 41 | > [Using Typescript](https://bezkoder.com/vuex-typescript-jwt-auth/) 42 | 43 | > [Vue 3 JWT Authentication with Vuex and Vue Router](https://bezkoder.com/vue-3-authentication-jwt/) 44 | 45 | More Practice: 46 | > [Vue.js CRUD App with Vue Router & Axios](https://bezkoder.com/vue-js-crud-app/) 47 | 48 | > [Vue Pagination with Axios and API example](https://bezkoder.com/vue-pagination-axios/) 49 | 50 | > [Vue File Upload example using Axios](https://bezkoder.com/vue-axios-file-upload/) 51 | 52 | Fullstack with Spring Boot Back-end: 53 | > [Spring Boot + Vue.js: Authentication with JWT & Spring Security Example](https://bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/) 54 | 55 | Fullstack with Node.js Express Back-end: 56 | > [Node.js Express + Vue.js: JWT Authentication & Authorization example](https://bezkoder.com/node-express-vue-jwt-auth/) 57 | 58 | Fullstack CRUD: 59 | > [Vue.js + Node.js + Express + MySQL example](https://bezkoder.com/vue-js-node-js-express-mysql-crud-example/) 60 | 61 | > [Vue.js + Node.js + Express + PostgreSQL example](https://bezkoder.com/vue-node-express-postgresql/) 62 | 63 | > [Vue.js + Node.js + Express + MongoDB example](https://bezkoder.com/vue-node-express-mongodb-mevn-crud/) 64 | 65 | > [Vue.js + Spring Boot + MySQL/PostgreSQL example](https://bezkoder.com/spring-boot-vue-js-crud-example/) 66 | 67 | > [Vue.js + Spring Boot + MongoDB example](https://bezkoder.com/spring-boot-vue-mongodb/) 68 | 69 | > [Vue.js + Django example](https://bezkoder.com/django-vue-js-rest-framework/) 70 | 71 | Integration (run back-end & front-end on same server/port) 72 | > [Integrate Vue.js with Spring Boot](https://bezkoder.com/integrate-vue-spring-boot/) 73 | 74 | > [Integrate Vue App with Node.js Express](https://bezkoder.com/serve-vue-app-express/) 75 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 95 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 104 | 105 | -------------------------------------------------------------------------------- /src/views/Register.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 116 | 117 | --------------------------------------------------------------------------------