├── .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 | 
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 |
2 |
3 |
49 |
50 |
51 |
52 |
53 |
54 |
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 |
2 |
3 |
6 |
7 |
8 |
9 |
41 |
--------------------------------------------------------------------------------
/src/components/BoardModerator.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
41 |
--------------------------------------------------------------------------------
/src/components/BoardUser.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
41 |
--------------------------------------------------------------------------------
/src/components/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
36 |
--------------------------------------------------------------------------------
/src/components/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

9 |
37 |
38 |
39 |
40 |
41 |
96 |
97 |
135 |
--------------------------------------------------------------------------------
/src/components/Profile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{currentUser.username}} Profile
6 |
7 |
8 |
9 | Token:
10 | {{currentUser.accessToken.substring(0, 20)}} ... {{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}}
11 |
12 |
13 | Id:
14 | {{currentUser.id}}
15 |
16 |
17 | Email:
18 | {{currentUser.email}}
19 |
20 |
Authorities:
21 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

9 |
38 |
39 |
44 | {{ message }}
45 |
46 |
47 |
48 |
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 | }
--------------------------------------------------------------------------------