Secure Your Website Auth by Implementing JWT Authentication

Learn how to implement robust JWT authentication in your Vue.js application. This comprehensive guide covers setup, token management, protected routes

In today's web development landscape, securing your application is paramount. JSON Web Tokens (JWT) have become a popular choice for implementing authentication in modern web applications. In this tutorial, we'll walk through the process of implementing JWT authentication in a Vue.js application, providing you with a robust and secure solution for user authentication.

Prerequisites

Before we begin, make sure you have the following:

  • - Node.js and npm installed on your machine
  • - Basic knowledge of Vue.js and JavaScript
  • - Familiarity with HTTP requests and RESTful APIs

Setting Up the Project

First, let's create a new Vue.js project using the Vue CLI:

vue create vue-jwt-auth
cd vue-jwt-auth

Next, we'll install the necessary dependencies:

npm install axios vuex vue-router jsonwebtoken

Project Structure

Our project structure will look like this:

src/
├── components/
│   ├── Login.vue
│   ├── Register.vue
│   └── Dashboard.vue
├── router/
│   └── index.js
├── store/
│   └── index.js
├── services/
│   └── auth.service.js
├── App.vue
└── main.js

Implementing the Vuex Store

Let's start by setting up our Vuex store to manage the authentication state:

import { createStore } from 'vuex'
import AuthService from '../services/auth.service'

export default createStore({
  state: {
    user: null,
    token: localStorage.getItem('token') || null,
  },
  mutations: {
    setUser(state, user) {
      state.user = user
    },
    setToken(state, token) {
      state.token = token
      localStorage.setItem('token', token)
    },
    logout(state) {
      state.user = null
      state.token = null
      localStorage.removeItem('token')
    },
  },
  actions: {
    async login({ commit }, credentials) {
      try {
        const response = await AuthService.login(credentials)
        commit('setUser', response.user)
        commit('setToken', response.token)
        return true
      } catch (error) {
        console.error('Login failed:', error)
        return false
      }
    },
    async register({ commit }, userData) {
      try {
        const response = await AuthService.register(userData)
        commit('setUser', response.user)
        commit('setToken', response.token)
        return true
      } catch (error) {
        console.error('Registration failed:', error)
        return false
      }
    },
    logout({ commit }) {
      commit('logout')
    },
  },
  getters: {
    isAuthenticated: state => !!state.token,
    currentUser: state => state.user,
  },
})

Creating the Authentication Service

Now, let's create the auth.service.js file to handle API calls:

import axios from 'axios'

const API_URL = 'https://your-api-url.com/api/auth/'

class AuthService {
  async login(credentials) {
    const response = await axios.post(API_URL + 'login', credentials)
    return response.data
  }

  async register(userData) {
    const response = await axios.post(API_URL + 'register', userData)
    return response.data
  }
}

export default new AuthService()

Implementing Vue Router

Let's set up our router with protected routes:

import { createRouter, createWebHistory } from 'vue-router'
import Login from '../components/Login.vue'
import Register from '../components/Register.vue'
import Dashboard from '../components/Dashboard.vue'
import store from '../store'

const routes = [
  { path: '/login', component: Login },
  { path: '/register', component: Register },
  { 
    path: '/dashboard', 
    component: Dashboard,
    meta: { requiresAuth: true }
  },
]

const router = createRouter({
  history: createWebHistory(),
  routes,
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!store.getters.isAuthenticated) {
      next('/login')
    } else {
      next()
    }
  } else {
    next()
  }
})

export default router

Creating Vue Components

Now, let's create our Vue components. We'll start with the Login component:

<template>
  <div class="login">
    <h2>Login</h2>
    <form @submit.prevent="handleSubmit">
      <div>
        <label for="email">Email:</label>
        <input type="email" id="email" v-model="email" required>
      </div>
      <div>
        <label for="password">Password:</label>
        <input type="password" id="password" v-model="password" required>
      </div>
      <button type="submit">Login</button>
    </form>
  </div>
</template>

<script>
import { ref } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'

export default {
  setup() {
    const store = useStore()
    const router = useRouter()
    const email = ref('')
    const password = ref('')

    const handleSubmit = async () => {
      const success = await store.dispatch('login', {
        email: email.value,
        password: password.value
      })
      if (success) {
        router.push('/dashboard')
      } else {
        alert('Login failed. Please check your credentials.')
      }
    }

    return {
      email,
      password,
      handleSubmit
    }
  }
}
</script>

Next, let's create the Register component:

<template>
  <div class="register">
    <h2>Register</h2>
    <form @submit.prevent="handleSubmit">
      <div>
        <label for="name">Name:</label>
        <input type="text" id="name" v-model="name" required>
      </div>
      <div>
        <label for="email">Email:</label>
        <input type="email" id="email" v-model="email" required>
      </div>
      <div>
        <label for="password">Password:</label>
        <input type="password" id="password" v-model="password" required>
      </div>
      <button type="submit">Register</button>
    </form>
  </div>
</template>

<script>
import { ref } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'

export default {
  setup() {
    const store = useStore()
    const router = useRouter()
    const name = ref('')
    const email = ref('')
    const password = ref('')

    const handleSubmit = async () => {
      const success = await store.dispatch('register', {
        name: name.value,
        email: email.value,
        password: password.value
      })
      if (success) {
        router.push('/dashboard')
      } else {
        alert('Registration failed. Please try again.')
      }
    }

    return {
      name,
      email,
      password,
      handleSubmit
    }
  }
}
</script>

Finally, let's create a simple Dashboard component:

<template>
  <div class="dashboard">
    <h2>Welcome, {{ user.name }}!</h2>
    <p>This is your secure dashboard.</p>
    <button @click="handleLogout">Logout</button>
  </div>
</template>

<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'

export default {
  setup() {
    const store = useStore()
    const router = useRouter()
    const user = computed(() => store.getters.currentUser)

    const handleLogout = () => {
      store.dispatch('logout')
      router.push('/login')
    }

    return {
      user,
      handleLogout
    }
  }
}
</script>

Updating App.vue

Let's update our App.vue to include navigation and router view:

<template>
  <div id="app">
    <nav>
      <router-link to="/login" v-if="!isAuthenticated">Login</router-link>
      <router-link to="/register" v-if="!isAuthenticated">Register</router-link>
      <router-link to="/dashboard" v-if="isAuthenticated">Dashboard</router-link>
    </nav>
    <router-view></router-view>
  </div>
</template>

<script>
import { computed } from 'vue'
import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    const isAuthenticated = computed(() => store.getters.isAuthenticated)

    return {
      isAuthenticated
    }
  }
}
</script>

Implementing JWT Interceptors

To ensure our JWT is sent with every request, we'll set up an Axios interceptor. Create a new file src/services/api.js:

import axios from 'axios'
import store from '../store'

const api = axios.create({
  baseURL: 'https://your-api-url.com/api',
})

api.interceptors.request.use(
  (config) => {
    const token = store.state.token
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response.status === 401) {
      store.dispatch('logout')
      router.push('/login')
    }
    return Promise.reject(error)
  }
)

export default api

Conclusion

Congratulations! You've successfully implemented JWT authentication in your Vue.js application. This setup provides a solid foundation for securing your app, managing user sessions, and protecting routes.

Remember to always follow security best practices:

  1. 1. Store JWTs securely (preferably in memory or secure HTTP-only cookies)
  2. 2. Implement token refresh mechanisms for long-lived sessions
  3. 3. Use HTTPS to encrypt data in transit
  4. 4. Validate and sanitize all user inputs
  5. 5. Regularly update your dependencies to patch any security vulnerabilities

By following these steps and best practices, you've taken a significant step towards creating a secure and robust Vue.js application with JWT authentication.

Happy coding!

About the author

🚀 | Exploring the realms of creativity and curiosity in 280 characters or less. Turning ideas into reality, one keystroke at a time. =» Ctrl + Alt + Believe

Post a Comment

-
Cookie Consent
We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.
Oops!
It seems there is something wrong with your internet connection. Please connect to the internet and start browsing again.