├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ └── logo.png ├── common │ └── EventBus.js ├── components │ ├── BoardAdmin.vue │ ├── BoardModerator.vue │ ├── BoardUser.vue │ ├── Home.vue │ ├── Login.vue │ ├── Profile.vue │ └── Register.vue ├── main.js ├── plugins │ └── font-awesome.js ├── router.js ├── services │ ├── api.js │ ├── auth.service.js │ ├── setupInterceptors.js │ ├── token.service.js │ └── user.service.js └── store │ ├── auth.module.js │ └── index.js ├── vue-3-refresh-token-axios-jwt-example-flow.png └── vue.config.js /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 JWT Refresh Token with Axios example 2 | 3 | ![vue-3-refresh-token-axios-jwt-example-flow](vue-3-refresh-token-axios-jwt-example-flow.png) 4 | 5 | For instruction, please visit: 6 | > [Vue 3 JWT Refresh Token with Axios example](https://bezkoder.com/vue-3-refresh-token/) 7 | 8 | > [Vue 3 Authentication & Authorization with JWT, Vuex and Vue Router](https://bezkoder.com/vue-3-authentication-jwt/) 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 | Related Posts: 30 | > [Vue 2 JWT Authentication with Vuex and Vue Router](https://bezkoder.com/jwt-vue-vuex-authentication/) 31 | 32 | > [Using Typescript](https://bezkoder.com/vuex-typescript-jwt-auth/) 33 | 34 | > [Vue 3 CRUD example with Axios and Vue Router](https://bezkoder.com/vue-3-crud/) 35 | 36 | Fullstack with Spring Boot Back-end: 37 | > [Spring Boot + Vue.js: Authentication with JWT & Spring Security Example](https://bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/) 38 | 39 | Fullstack with Node.js Express Back-end: 40 | > [Node.js Express + Vue.js: JWT Authentication & Authorization example](https://bezkoder.com/node-express-vue-jwt-auth/) 41 | 42 | Fullstack CRUD: 43 | > [Vue.js + Node.js + Express + MySQL example](https://bezkoder.com/vue-js-node-js-express-mysql-crud-example/) 44 | 45 | > [Vue.js + Node.js + Express + PostgreSQL example](https://bezkoder.com/vue-node-express-postgresql/) 46 | 47 | > [Vue.js + Node.js + Express + MongoDB example](https://bezkoder.com/vue-node-express-mongodb-mevn-crud/) 48 | 49 | > [Vue.js + Spring Boot + MySQL/PostgreSQL example](https://bezkoder.com/spring-boot-vue-js-crud-example/) 50 | 51 | > [Vue.js + Spring Boot + MongoDB example](https://bezkoder.com/spring-boot-vue-mongodb/) 52 | 53 | > [Vue.js + Django example](https://bezkoder.com/django-vue-js-rest-framework/) 54 | 55 | Integration (run on same server/port): 56 | > [Integrate Vue.js with Spring Boot](https://bezkoder.com/integrate-vue-spring-boot/) 57 | 58 | > [Integrate Vue App with Node.js Express](https://bezkoder.com/serve-vue-app-express/) 59 | 60 | 61 | ## Project setup 62 | ``` 63 | npm install 64 | ``` 65 | 66 | ### Compiles and hot-reloads for development 67 | ``` 68 | npm run serve 69 | ``` 70 | 71 | ### Compiles and minifies for production 72 | ``` 73 | npm run build 74 | ``` 75 | 76 | ### Lints and fixes files 77 | ``` 78 | npm run lint 79 | ``` 80 | 81 | ### Customize configuration 82 | See [Configuration Reference](https://cli.vuejs.org/config/). 83 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-3-jwt-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": "^3.0.0-4", 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": "^4.4.7", 20 | "vue": "^3.0.0", 21 | "vue-router": "^4.0.10", 22 | "vuex": "^4.0.2", 23 | "yup": "^0.32.9" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "~4.5.0", 27 | "@vue/cli-plugin-eslint": "~4.5.0", 28 | "@vue/cli-service": "~4.5.0", 29 | "@vue/compiler-sfc": "^3.0.0", 30 | "babel-eslint": "^10.1.0", 31 | "eslint": "^6.7.2", 32 | "eslint-plugin-vue": "^7.0.0" 33 | }, 34 | "eslintConfig": { 35 | "root": true, 36 | "env": { 37 | "node": true 38 | }, 39 | "extends": [ 40 | "plugin:vue/vue3-essential", 41 | "eslint:recommended" 42 | ], 43 | "parserOptions": { 44 | "parser": "babel-eslint" 45 | }, 46 | "rules": {} 47 | }, 48 | "browserslist": [ 49 | "> 1%", 50 | "last 2 versions", 51 | "not dead" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/vue-3-jwt-refresh-token/435adcd1e6f0ed8a9a27e75f68f59e9672f712f2/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue 3 JWT Refresh Token 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 95 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/vue-3-jwt-refresh-token/435adcd1e6f0ed8a9a27e75f68f59e9672f712f2/src/assets/logo.png -------------------------------------------------------------------------------- /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/components/BoardAdmin.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | -------------------------------------------------------------------------------- /src/components/BoardModerator.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | -------------------------------------------------------------------------------- /src/components/BoardUser.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | -------------------------------------------------------------------------------- /src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | -------------------------------------------------------------------------------- /src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 96 | 97 | 135 | -------------------------------------------------------------------------------- /src/components/Profile.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/Register.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 124 | 125 | 163 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } 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 { FontAwesomeIcon } from './plugins/font-awesome' 8 | 9 | import setupInterceptors from './services/setupInterceptors'; 10 | 11 | setupInterceptors(store); 12 | 13 | createApp(App) 14 | .use(router) 15 | .use(store) 16 | .component("font-awesome-icon", FontAwesomeIcon) 17 | .mount("#app"); 18 | -------------------------------------------------------------------------------- /src/plugins/font-awesome.js: -------------------------------------------------------------------------------- 1 | import { library } from "@fortawesome/fontawesome-svg-core"; 2 | import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; 3 | import { 4 | faHome, 5 | faUser, 6 | faUserPlus, 7 | faSignInAlt, 8 | faSignOutAlt, 9 | } from "@fortawesome/free-solid-svg-icons"; 10 | 11 | library.add(faHome, faUser, faUserPlus, faSignInAlt, faSignOutAlt); 12 | 13 | export { FontAwesomeIcon }; 14 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter } from "vue-router"; 2 | import Home from "./components/Home.vue"; 3 | import Login from "./components/Login.vue"; 4 | import Register from "./components/Register.vue"; 5 | // lazy-loaded 6 | const Profile = () => import("./components/Profile.vue") 7 | const BoardAdmin = () => import("./components/BoardAdmin.vue") 8 | const BoardModerator = () => import("./components/BoardModerator.vue") 9 | const BoardUser = () => import("./components/BoardUser.vue") 10 | 11 | const 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: Profile, 34 | }, 35 | { 36 | path: "/admin", 37 | name: "admin", 38 | // lazy-loaded 39 | component: BoardAdmin, 40 | }, 41 | { 42 | path: "/mod", 43 | name: "moderator", 44 | // lazy-loaded 45 | component: BoardModerator, 46 | }, 47 | { 48 | path: "/user", 49 | name: "user", 50 | // lazy-loaded 51 | component: BoardUser, 52 | }, 53 | ]; 54 | 55 | const router = createRouter({ 56 | history: createWebHistory(), 57 | routes, 58 | }); 59 | 60 | // router.beforeEach((to, from, next) => { 61 | // const publicPages = ['/login', '/register', '/home']; 62 | // const authRequired = !publicPages.includes(to.path); 63 | // const loggedIn = localStorage.getItem('user'); 64 | 65 | // // trying to access a restricted page + not logged in 66 | // // redirect to login page 67 | // if (authRequired && !loggedIn) { 68 | // next('/login'); 69 | // } else { 70 | // next(); 71 | // } 72 | // }); 73 | 74 | export default router; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "vuex"; 2 | import { auth } from "./auth.module"; 3 | 4 | const store = createStore({ 5 | modules: { 6 | auth, 7 | }, 8 | }); 9 | 10 | export default store; 11 | -------------------------------------------------------------------------------- /vue-3-refresh-token-axios-jwt-example-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bezkoder/vue-3-jwt-refresh-token/435adcd1e6f0ed8a9a27e75f68f59e9672f712f2/vue-3-refresh-token-axios-jwt-example-flow.png -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | port: 8081 4 | } 5 | } --------------------------------------------------------------------------------