Vue/Vuex Typescript example: JWT Authentication | vuex-class & vuex-module-decorators

In this tutorial, we’re gonna build a Vue/Vuex Typescript Application that supports JWT Authentication. I will show you:

  • JWT Authentication Flow for User Signup & User Login
  • Project Structure for Vue/Vuex Typescript Authentication
  • How to define Vuex Authentication module with vuex-module-decorators
  • Creating Vue Typescript Auth Components with vuex-class & VeeValidate
  • Vue Typescript Components for accessing protected Resources
  • How to add a dynamic Navigation Bar to Vue App

Let’s explore together.

Related Post:
In-depth Introduction to JWT-JSON Web Token
Vue Typescript example: Build a CRUD Application
Vue File Upload example using Axios

Fullstack:
Spring Boot + Vue.js: JWT Authentication & Authorization example
Node.js Express + Vue.js: JWT Authentication & Authorization example

Vuex Typescript example

Before working with Vuex using Typescript, we need to install vuex-module-decorators & vuex-class packages.

npm install vuex-module-decorators
npm install vuex-class

Define Vuex Module

First we create a module in store folder.
Instead of writing javascript code like this:

export default {
  namespaced: true,
  state: {
    title: ''
  },
  mutations: {
    setTitle(state, newTitle) {
      state.title = newTitle;
    }
  },
  actions: {
    updateTitle(context, newTitle) {
      context.commit('setTitle', newTitle);
    }
  },
  getters: {
    titleUpperCase: state => {
      return state.title.toUpperCase();
    }
  }
}

We can use Typescript with vuex-module-decorators:

import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators';

@Module({ namespaced: true })
class Tutorial extends VuexModule {
  public title: string = '';

  @Mutation
  public setTitle(newTitle: string): void {
    this.title = newTitle;
  }

  @Action
  public updateTitle(newTitle: string): void {
    this.context.commit('setTitle', newTitle);
  }

  get titleUpperCase(): string{
    return this.title.toUpperCase();
  }
}
export default Tutorial;

In index.ts, we can register the module as following code:

import Vue from "vue";
import Vuex from "vuex";

import Tutorial from "./modules/tutorial.module";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    Tutorial
  }
});

Use Vuex in Components

Now we need to use vuex-class library that provides decorators such as: State, Getter, and Action.

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { namespace } from 'vuex-class';
const Tutorial = namespace('Tutorial');

@Component
export default class Tutorial extends Vue {
  @Tutorial.State
  public title!: string;

  @Tutorial.Getter
  public titleUpperCase!: string;

  @Tutorial.Action
  public updateTitle!: (newTitle: string) => void;
}
</script>

Overview of Vue/Vuex Typescript JWT Authentication example

We will build a Vue Typescript application in that:

  • There are Login/Logout, Signup pages.
  • Form data will be validated by front-end before being sent to back-end.
  • Depending on User’s roles (admin, moderator, user), Navigation Bar changes its items automatically.

Here are the screenshots:

– Signup Page:

vuex-typescript-example-vue-jwt-authentication-vuex-class-register

– Form Validation:

vuex-typescript-example-vue-jwt-authentication-vuex-class-form-validation

– Login Page & Profile Page (for successful Login):

vuex-typescript-example-vue-jwt-authentication-vuex-class-login-profile

– Admin account with Navigation Bar changing:

vuex-typescript-example-vue-jwt-authentication-vuex-class-authorization-login

Flow for User Registration and User Login

For JWT Authentication, we’re gonna call 2 endpoints:

  • POST api/auth/signup for User Registration
  • POST api/auth/signin for User Login

You can take a look at following flow to have an overview of Requests and Responses that our Vue/Vuex Typescript Client will make or receive.

vuex-typescript-example-vue-jwt-authentication-vuex-class-flow

Vue.js Client must add a JWT to HTTP Authorization Header before sending request to protected resources.

You can find step by step to implement these back-end servers in following tutorial:

Or add refresh token:
Axios Interceptors tutorial with Refresh Token example

Vue Typescript Component Diagram with Vuex & Vue Router

Now look at the diagram below.

vuex-typescript-example-vue-jwt-authentication-vuex-class-component-diagram

Let’s think about it.

– The App component is a container with Router. It gets app state from Vuex store‘s Auth modules. Then the navbar now can display based on the state. App component also passes state to its child components.

Login & Register components have form for submission data (with support of vee-validate) that we’re gonna call login/register actions.

– Our Vuex actions call AuthService methods which use axios to make HTTP requests. We also store or get JWT from Browser Local Storage inside these methods.

Home component is public for all visitor.

Profile component get user state from Vuex store and display user information.

BoardUser, BoardModerator, BoardAdmin components will be displayed by Vuex state user.roles. In these components, we use UserService to get protected resources from API.

UserService uses auth-header() helper function to add JWT to HTTP Authorization header. auth-header() returns an object containing the JWT of the currently logged in user from Local Storage.

Technology

We will use these modules:

  • vue: 2.6.11
  • vue-router: 3.4.3
  • vuex: 3.5.1
  • axios: 0.20.0
  • vee-validate: 2.2.15
  • bootstrap: 4.5.2
  • vue-fontawesome: 2.0.0

For Vue Typescript:

  • vue-class-component: 7.2.3
  • vue-property-decorator: 8.4.2
  • vuex-module-decorators: 0.17.0
  • vuex-class: 0.3.2

Project Structure

This is folders & files structure for our Vue application:

vuex-typescript-example-vue-jwt-authentication-vuex-class-project-structure

With the explaination in diagram above, you can understand the project structure easily.

Setup Vue Typescript Project

Open cmd at the folder you want to save Project folder, run command:
vue create vuex-typescript-jwt-auth

You will see some options, choose Manually select features.
Select the following options:

vue-typescript-example-jwt-auth-create-app

Then configure the project like this:

? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package.json
? Save this as a preset for future projects? (y/N) No

Run following command to install necessary modules:

npm install vue-router
npm install vuex
npm install [email protected]
npm install axios
npm install bootstrap jquery popper.js
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome

We also need two popular third-party packages:

npm install vuex-module-decorators
npm install vuex-class

After the installation is done, you can check dependencies in package.json file.

"dependencies": {
  "@fortawesome/fontawesome-svg-core": "^1.2.30",
  "@fortawesome/free-solid-svg-icons": "^5.14.0",
  "@fortawesome/vue-fontawesome": "^2.0.0",
  "axios": "^0.20.0",
  "bootstrap": "^4.5.2",
  "core-js": "^3.6.5",
  "jquery": "^3.5.1",
  "popper.js": "^1.16.1",
  "vee-validate": "^2.2.15",
  "vue": "^2.6.11",
  "vue-class-component": "^7.2.3",
  "vue-property-decorator": "^8.4.2",
  "vue-router": "^3.4.3",
  "vuex": "^3.5.1",
  "vuex-class": "^0.3.2",
  "vuex-module-decorators": "^0.17.0"
},

Open src/main.ts, add code below:

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import VeeValidate from 'vee-validate';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import {
  faHome,
  faUser,
  faUserPlus,
  faSignInAlt,
  faSignOutAlt
} from '@fortawesome/free-solid-svg-icons';

library.add(faHome, faUser, faUserPlus, faSignInAlt, faSignOutAlt);

Vue.config.productionTip = false;

Vue.use(VeeValidate);
Vue.component('font-awesome-icon', FontAwesomeIcon);

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app');

You can see that we import and apply in Vue object:
store for Vuex (implemented later in src/store)
router for Vue Router (implemented later in src/router.ts)
bootstrap with CSS
vee-validate
vue-fontawesome for icons (used later in nav)

Create Services

We create two services in src/services folder:


services

auth-header.ts

AuthService.ts (Authentication service)

UserService.ts (Data service)


Authentication service

The service provides three important methods with the help of axios for HTTP requests & reponses:

  • login(): POST {username, password} & save JWT to Local Storage
  • logout(): remove JWT from Local Storage
  • register(): POST {username, email, password}
import axios from 'axios';

const API_URL = 'http://localhost:8080/api/auth/';

class AuthService {
  login(username: string, password: string) {
    return axios
      .post(API_URL + 'signin', {
        username,
        password
      })
      .then(response => {
        if (response.data.accessToken) {
          localStorage.setItem('user', JSON.stringify(response.data));
        }

        return response.data;
      });
  }

  logout() {
    localStorage.removeItem('user');
  }

  register(username: string, email: string, password: string) {
    return axios.post(API_URL + 'signup', {
      username,
      email,
      password
    });
  }
}

export default new AuthService();

Data service

We also have methods for retrieving data from server. In the case we access protected resources, the HTTP request needs Authorization header.

Let’s create a helper function called authHeader() inside auth-header.ts:

export default function authHeader() {
  const storedUser = localStorage.getItem('user');
  let user = JSON.parse(storedUser ? storedUser : "");

  if (user && user.accessToken) {
    return { Authorization: 'Bearer ' + user.accessToken };
  } else {
    return {};
  }
}

It checks Local Storage for user item.
If there is a logged in user with accessToken (JWT), return HTTP Authorization header. Otherwise, return an empty object.


Note: For Node.js Express back-end, please use x-access-token header like this:

export default function authHeader() {
  const storedUser = localStorage.getItem('user');
  let user = JSON.parse(storedUser ? storedUser : "");

  if (user && user.accessToken) {
    return { 'x-access-token': user.accessToken };
  } else {
    return {};
  }
}

Now we define a service for accessing data in UserService.ts:

import axios from 'axios';
import authHeader from './auth-header';

const API_URL = 'http://localhost:8080/api/test/';

class UserService {
  getPublicContent() {
    return axios.get(API_URL + 'all');
  }

  getUserBoard() {
    return axios.get(API_URL + 'user', { headers: authHeader() });
  }

  getModeratorBoard() {
    return axios.get(API_URL + 'mod', { headers: authHeader() });
  }

  getAdminBoard() {
    return axios.get(API_URL + 'admin', { headers: authHeader() });
  }
}

export default new UserService();

You can see that we add a HTTP header with the help of authHeader() function when requesting authorized resource.

Define Vuex Typescript Authentication module

We put Vuex module for authentication in src/store folder.


store

modules

auth.module.ts (authentication module)

index.ts (Vuex Store that contains all modules)


Now open index.ts file, import auth.module to main Vuex Store here.

import Vue from "vue";
import Vuex from "vuex";

import Auth from "./modules/auth.module";

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    Auth
  }
});

Then we start to define Vuex Authentication module that contains:

  • state: { status, user }
  • actions: { login, logout, register }
  • mutations: { loginSuccess, loginFailure, logout, registerSuccess, registerFailure }
  • getters: { isLoggedIn }

We use AuthService which is defined above to make authentication requests.

modules/auth.module.ts

import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators';
import AuthService from '@/services/AuthService';

const storedUser = localStorage.getItem('user');

@Module({ namespaced: true })
class User extends VuexModule {
  public status = storedUser ? { loggedIn: true } : { loggedIn: false };
  public user = storedUser ? JSON.parse(storedUser) : null;

  @Mutation
  public loginSuccess(user: any): void {
    this.status.loggedIn = true;
    this.user = user;
  }

  @Mutation
  public loginFailure(): void {
    this.status.loggedIn = false;
    this.user = null;
  }

  @Mutation
  public logout(): void {
    this.status.loggedIn = false;
    this.user = null;
  }

  @Mutation
  public registerSuccess(): void {
    this.status.loggedIn = false;
  }

  @Mutation
  public registerFailure(): void {
    this.status.loggedIn = false;
  }

  @Action({ rawError: true })
  login(data: any): Promise<any> {
    return AuthService.login(data.username, data.password).then(
      user => {
        this.context.commit('loginSuccess', user);
        return Promise.resolve(user);
      },
      error => {
        this.context.commit('loginFailure');
        const message =
          (error.response && error.response.data && error.response.data.message) ||
          error.message ||
          error.toString();
        return Promise.reject(message);
      }
    );
  }

  @Action
  signOut(): void {
    AuthService.logout();
    this.context.commit('logout');
  }

  @Action({ rawError: true })
  register(data: any): Promise<any> {
    return AuthService.register(data.username, data.email, data.password).then(
      response => {
        this.context.commit('registerSuccess');
        return Promise.resolve(response.data);
      },
      error => {
        this.context.commit('registerFailure');
        const message =
          (error.response && error.response.data && error.response.data.message) ||
          error.message ||
          error.toString();
        return Promise.reject(message);
      }
    );
  }

  get isLoggedIn(): boolean {
    return this.status.loggedIn;
  }
}

export default User;

You can find more details about Vuex at Vuex Guide.

Create Vue Typescript Authentication Components

Instead of using axios or AuthService directly, these Components should work with Vuex Store using vuex-class decorators:
– binding state by using @State.
– getting status with @Getter.
– making request by call an @Action.


components

Login.vue

Register.vue

Profile.vue


Vue Typescript Login Page

In src/components folder, create Login.vue file with following code:

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleLogin">
        <div class="form-group">
          <label for="username">Username</label>
          <input
            v-model="user.username"
            v-validate="'required'"
            type="text"
            class="form-control"
            name="username"
          />
          <div
            v-if="errors.has('username')"
            class="alert alert-danger"
            role="alert"
          >
            Username is required!
          </div>
        </div>
        <div class="form-group">
          <label for="password">Password</label>
          <input
            v-model="user.password"
            v-validate="'required'"
            type="password"
            class="form-control"
            name="password"
          />
          <div
            v-if="errors.has('password')"
            class="alert alert-danger"
            role="alert"
          >
            Password is required!
          </div>
        </div>
        <div class="form-group">
          <button class="btn btn-primary btn-block" :disabled="loading">
            <span
              v-show="loading"
              class="spinner-border spinner-border-sm"
            ></span>
            <span>Login</span>
          </button>
        </div>
        <div class="form-group">
          <div v-if="message" class="alert alert-danger" role="alert">
            {{ message }}
          </div>
        </div>
      </form>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { namespace } from "vuex-class";
const Auth = namespace("Auth");

@Component
export default class Login extends Vue {
  private user: any = { username: "", password: "" };
  private loading: boolean = false;
  private message: string = "";

  @Auth.Getter
  private isLoggedIn!: boolean;

  @Auth.Action
  private login!: (data: any) => Promise<any>;

  created() {
    if (this.isLoggedIn) {
      this.$router.push("/profile");
    }
  }

  handleLogin() {
    this.loading = true;
    this.$validator.validateAll().then((isValid) => {
      if (!isValid) {
        this.loading = false;
        return;
      }

      if (this.user.username && this.user.password) {
        this.login(this.user).then(
          (data) => {
            this.$router.push("/profile");
          },
          (error) => {
            this.loading = false;
            this.message = error;
          }
        );
      }
    });
  }
}
</script>

<style scoped>
  ...
</style>

First we import { namespace } from vuex-class. It is a binding helper that provides way to get Vuex module conveniently: const Auth = namespace("Auth").

This page has a Form with username & password. We use VeeValidate 2.x to validate input before submitting the form. If there is an invalid field, we show the error message.

We check user logged in status using getters: @Auth.Getter isLoggedIn. If the status is true, we use Vue Router to direct user to Profile Page:

created() {
  if (this.isLoggedIn) {
    this.$router.push('/profile');
  }
},

In the handleLogin() function, we call login Action. If the login is successful, go to Profile Page, otherwise, show error message.

Vue Typescript Register Page

This page is similar to Login Page.

For form validation, we have some more details:

  • username: required|min:3|max:20
  • email: required|email|max:50
  • password: required|min:6|max:40

For form submission, we call register Vuex Action with @Auth.Action for binding.

src/components/Register.vue

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleRegister">
        <div v-if="!successful">
          <div class="form-group">
            <label for="username">Username</label>
            <input
              v-model="user.username"
              v-validate="'required|min:3|max:20'"
              type="text"
              class="form-control"
              name="username"
            />
            <div
              v-if="submitted && errors.has('username')"
              class="alert-danger"
            >
              {{ errors.first("username") }}
            </div>
          </div>
          <div class="form-group">
            <label for="email">Email</label>
            <input
              v-model="user.email"
              v-validate="'required|email|max:50'"
              type="email"
              class="form-control"
              name="email"
            />
            <div v-if="submitted && errors.has('email')" class="alert-danger">
              {{ errors.first("email") }}
            </div>
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              v-model="user.password"
              v-validate="'required|min:6|max:40'"
              type="password"
              class="form-control"
              name="password"
            />
            <div
              v-if="submitted && errors.has('password')"
              class="alert-danger"
            >
              {{ errors.first("password") }}
            </div>
          </div>
          <div class="form-group">
            <button class="btn btn-primary btn-block">Sign Up</button>
          </div>
        </div>
      </form>

      <div
        v-if="message"
        class="alert"
        :class="successful ? 'alert-success' : 'alert-danger'"
      >
        {{ message }}
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { namespace } from "vuex-class";
const Auth = namespace("Auth");

@Component
export default class Register extends Vue {
  private user: any = { username: "", email: "", password: "" };

  private submitted: boolean = false;
  private successful: boolean = false;
  private message: string = "";

  @Auth.Getter
  private isLoggedIn!: boolean;

  @Auth.Action
  private register!: (data: any) => Promise<any>;

  mounted() {
    if (this.isLoggedIn) {
      this.$router.push("/profile");
    }
  }

  handleRegister() {
    this.message = "";
    this.submitted = true;

    this.$validator.validate().then((isValid) => {
      if (isValid) {
        this.register(this.user).then(
          (data) => {
            this.message = data.message;
            this.successful = true;
          },
          (error) => {
            this.message = error;
            this.successful = false;
          }
        );
      }
    });
  }
}
</script>

<style scoped>
  ...
</style>

Profile Page

This page gets current User state from Vuex Store and show information. If the User is not logged in, it directs to Login Page.

src/components/Profile.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>
        <strong>{{ currentUser.username }}</strong> Profile
      </h3>
    </header>
    <p>
      <strong>Token:</strong>
      {{ currentUser.accessToken.substring(0, 20) }} ...
      {{ currentUser.accessToken.substr(currentUser.accessToken.length - 20) }}
    </p>
    <p>
      <strong>Id:</strong>
      {{ currentUser.id }}
    </p>
    <p>
      <strong>Email:</strong>
      {{ currentUser.email }}
    </p>
    <strong>Authorities:</strong>
    <ul>
      <li v-for="(role, index) in currentUser.roles" :key="index">
        {{ role }}
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { namespace } from "vuex-class";
const Auth = namespace("Auth");

@Component
export default class Profile extends Vue {
  @Auth.State("user")
  private currentUser!: any;

  mounted() {
    if (!this.currentUser) {
      this.$router.push("/login");
    }
  }
}
</script>

Create Vue Typescript Components for accessing Resources

These components will use UserService to request data.


components

Home.vue

BoardAdmin.vue

BoardModerator.vue

BoardUser.vue


Home Page

This is a public page.

src/components/Home.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{ content }}</h3>
    </header>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import UserService from "@/services/UserService";

@Component
export default class Home extends Vue {
  private content = "";

  mounted() {
    UserService.getPublicContent().then(
      (response) => {
        this.content = response.data;
      },
      (error) => {
        this.content =
          (error.response && error.response.data) ||
          error.message ||
          error.toString();
      }
    );
  }
}
</script>

Role-based Pages

We have 3 pages for accessing protected data:

  • BoardUser page calls UserService.getUserBoard()
  • BoardModerator page calls UserService.getModeratorBoard()
  • BoardAdmin page calls UserService.getAdminBoard()

This is an example, other Page are similar to this Page.

src/components/BoardUser.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{ content }}</h3>
    </header>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import UserService from "@/services/UserService";

@Component
export default class UserBoard extends Vue {
  private content = "";

  mounted() {
    UserService.getUserBoard().then(
      (response) => {
        this.content = response.data;
      },
      (error) => {
        this.content =
          (error.response && error.response.data && error.response.data.message) ||
          error.message ||
          error.toString();
      }
    );
  }
}
</script>

Define Routes for Vue Router

Now we define all routes for our Vue Application.

src/router.ts

import Vue from 'vue';
import VueRouter, { RouteConfig } from "vue-router";
import Home from '@/components/Home.vue';
import Login from '@/components/Login.vue';
import Register from '@/components/Register.vue';

Vue.use(VueRouter);

const routes: Array<RouteConfig> = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/home',
    component: Home
  },
  {
    path: '/login',
    component: Login
  },
  {
    path: '/register',
    component: Register
  },
  {
    path: '/profile',
    name: 'profile',
    // lazy-loaded
    component: () => import('./components/Profile.vue')
  },
  {
    path: '/admin',
    name: 'admin',
    component: () => import('./components/BoardAdmin.vue')
  },
  {
    path: '/mod',
    name: 'moderator',
    component: () => import('./components/BoardModerator.vue')
  },
  {
    path: '/user',
    name: 'user',
    component: () => import('./components/BoardUser.vue')
  }
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

Add Navigation Bar to Vue App

This is the root container for our application that contains navigation bar. We will add router-view here.

src/App.vue

<template>
  <div id="app">
    <nav class="navbar navbar-expand navbar-dark bg-dark">
      <a href class="navbar-brand" @click.prevent>bezKoder</a>
      <div class="navbar-nav mr-auto">
        <li class="nav-item">
          <router-link to="/home" class="nav-link">
            <font-awesome-icon icon="home" /> Home
          </router-link>
        </li>
        <li v-if="showAdminBoard" class="nav-item">
          <router-link to="/admin" class="nav-link">Admin Board</router-link>
        </li>
        <li v-if="showModeratorBoard" class="nav-item">
          <router-link to="/mod" class="nav-link">Moderator Board</router-link>
        </li>
        <li class="nav-item">
          <router-link v-if="currentUser" to="/user" class="nav-link"
            >User</router-link
          >
        </li>
      </div>

      <div v-if="!currentUser" class="navbar-nav ml-auto">
        <li class="nav-item">
          <router-link to="/register" class="nav-link">
            <font-awesome-icon icon="user-plus" /> Sign Up
          </router-link>
        </li>
        <li class="nav-item">
          <router-link to="/login" class="nav-link">
            <font-awesome-icon icon="sign-in-alt" /> Login
          </router-link>
        </li>
      </div>

      <div v-if="currentUser" class="navbar-nav ml-auto">
        <li class="nav-item">
          <router-link to="/profile" class="nav-link">
            <font-awesome-icon icon="user" />
            {{ currentUser.username }}
          </router-link>
        </li>
        <li class="nav-item">
          <a class="nav-link" href @click.prevent="logOut">
            <font-awesome-icon icon="sign-out-alt" /> LogOut
          </a>
        </li>
      </div>
    </nav>

    <div class="container">
      <router-view />
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { namespace } from "vuex-class";
const Auth = namespace("Auth");

@Component
export default class App extends Vue {
  @Auth.State("user")
  private currentUser!: any;

  @Auth.Action
  private signOut!: () => void;

  get showAdminBoard(): boolean {
    if (this.currentUser && this.currentUser.roles) {
      return this.currentUser.roles.includes("ROLE_ADMIN");
    }

    return false;
  }

  get showModeratorBoard(): boolean {
    if (this.currentUser && this.currentUser.roles) {
      return this.currentUser.roles.includes("ROLE_MODERATOR");
    }

    return false;
  }

  logOut() {
    this.signOut();
    this.$router.push("/login");
  }
}
</script>

Our navbar looks more professional when using font-awesome-icon.
We also make the navbar dynamically change by current User’s roles which are retrieved from Vuex Store state.

Handle Unauthorized Access

If you want to check Authorized status everytime a navigating action is trigger, just add router.beforeEach() at the end of src/router.ts like this:

router.beforeEach((to, from, next) => {
  const publicPages = ['/login', '/register', '/home'];
  const authRequired = !publicPages.includes(to.path);
  const loggedIn = localStorage.getItem('user');

  // trying to access a restricted page + not logged in
  // redirect to login page
  if (authRequired && !loggedIn) {
    next('/login');
  } else {
    next();
  }
});

Configure Port for Vue App

Because most of HTTP Server use CORS configuration that accepts resource sharing restricted to some sites or ports, so we also need to configure port for our App.

In project root folder, create vue.config.js file with following content:

module.exports = {
  devServer: {
    port: 8081
  }
}

We’ve set our app running at port 8081.

Conclusion

Congratulation!

Today we’ve done so many interesting things. I hope you understand the overall layers of our Vue Typescript application, and apply it in your project at ease. Now you can build a front-end app that supports JWT Authentication with Vue.js, Vuex Typescript and Vue Router.

You will need to make this client work with one of following Servers:

Or add refresh token:
Axios Interceptors tutorial with Refresh Token example

Happy learning, see you again!

Further Reading

Fullstack CRUD App:

Source Code

You can find the complete source code for this tutorial on Github.

3 thoughts to “Vue/Vuex Typescript example: JWT Authentication | vuex-class & vuex-module-decorators”

  1. For your info I use “typescript”: “~3.9.3”:

    and

    “dependencies”: {
    “@fortawesome/fontawesome-svg-core”: “^1.2.32”,
    “@fortawesome/free-solid-svg-icons”: “^5.15.1”,
    “@fortawesome/vue-fontawesome”: “^2.0.0”,
    “@types/lodash”: “^4.14.164”,
    “@vue/composition-api”: “^1.0.0-beta.18”,
    “axios”: “^0.21.0”,
    “core-js”: “^3.6.5”,
    “es6-promise”: “^4.2.8”,
    “lodash.upperfirst”: “^4.3.1”,
    “register-service-worker”: “^1.7.1”,
    “vue”: “^3.0.0”,
    “vue-class-component”: “^8.0.0-0”,
    “vue-property-decorator”: “^9.0.2”,
    “vue-router”: “^4.0.0-0”,
    “vuejs-progress-bar”: “^1.2.3”,
    “vuex”: “^4.0.0-0”,
    “vuex-class”: “^0.3.2”,
    “vuex-module-decorators”: “^1.0.1”
    },

  2. Thank you, I was struggling for using vuex with typescript, Where did you get how to implement it in this way?

Leave a Reply

Your email address will not be published. Required fields are marked *