Vue.js tip: JWT Authentication in Vue 3 app using Axios Interceptors

Hi there,

this code snippet can help you implement JWT (JSON Web Token) authentication in the Vue.js 3 application.

The code creates an axiosBaseInstance instance of the Axios Javascript library to make HTTP requests to the API RESTfull backend server. (In my projects, I’ve recently been using the fantastic Django Ninja Rest framework with the Django Ninja JWT plugin.)

The axiosBaseInstance provides interceptors to change the outgoing HTTP request configuration by inserting the valid access token into  Content-Type header and handling 401 errors in incoming HTTP server responses.
The axiosBaseInstance object is configured with a few options like the baseURL, timeout, maxContentLength, maxBodyLength, and headers.

The Content-Type header is set to application/json in the headers object and says in what form the data in the request body should be sent.

The interceptors are used to add middleware functions to the Axios request/response pipeline.

The request interceptor  modifies the request configuration before it is sent to the server.
This is where is added the Authorization header to the request using the access token retrieved from the auth store (in my case the Pinia store for Vue.js).

The response interceptor is added to handle error responses from the server. In case of a 401 unauthorized error, it tries to get the new valid access token by calling the refreshAccessToken method of the auth store (from the Pinia store).

If the token is successfully refreshed, the Authorization header of the axiosBaseInstance object is updated by the new the access token and retry the original request.

That’s all for now.
If you have any questions or notes, just give me a shout in comments.
Hanz

The Pinia auth store example code

import Axios from 'axios'
import {ref} from "vue"
import {defineStore} from 'pinia' // Pinia store
import {router} from "../router"

// we want an extra dedicated Axios instance without interceptors to bypass 
// the catching 401 error in the main Axios instance
const axiosInstanceAuth = Axios.create({
    baseURL: URL_API_SERVER,
    timeout: 20 * 1000,  // 20 sec
    headers: {
        'Content-Type': 'application/json',
    }
})

export const useAuthStore = defineStore('auth', () => {
    let accessToken = ref(null)
    let refreshToken = ref(null)
    let loggedUser = ref(null)
    let isLogged = ref(false)

    const _cleanCredentials = () => {
        isLogged.value = false
        loggedUser.value = null
        accessToken.value = false
        refreshToken.value = false
    }
    
    async function refreshAccessToken() {

        try {
            const response = await axiosInstanceAuth.post(URL_REFRESH_TOKEN, {
                refresh: refreshToken.value
            })
            accessToken.value = response.data.access
            isLogged.value = true
        } catch (e) {
            _cleanCredentials()
            await router.push({name:'login_view'})
        }
    }

    async function loginUser(username, password) {
        try {
            const res = await axiosInstanceAuth.post(URL_ACCESS_TOKEN, {
                username: username,
                password: password
            })

            accessToken.value = res.data.access
            refreshToken.value = res.data.refresh
            loggedUser.value = res.data.username
            isLogged.value = true

            return true
        } catch (e) {
             _cleanCredentials()
            return false
        }
    }

    async function logoutUser() {
        _cleanCredentials()
        await router.push({name:'logour_view'})
    }

    return {accessToken, refreshToken, loggedUser, isLogged, refreshAccessToken, loginUser, logoutUser}
})

 

Example code of the Axios instance with request/response interceptors

import Axios from 'axios'
import {useAuthStore} from '../stores/auth' // Pinia store

// the main Axios instance with interceptors
const axiosBaseInstance = Axios.create({
    baseURL: URL_API_SERVER,
    timeout: 20 * 1000,  // 20 sec
    maxContentLength: 50 * 1024 ^ 3, // in Bytes, set 50MB
    maxBodyLength: 50 * 1024 ^ 3,    // in Bytes, set 50MB
    headers: {
        'Content-Type': 'application/json',
    }
})

axiosBaseInstance.interceptors.request.use(
    config => {
        // from Pinia store
        const store = useAuthStore()
        // set the Authorization header with the current access token
        config.headers.Authorization = `Bearer ${store.accessToken}`
        return config;
    },
    error => {
        return Promise.resolve(error)
    });

axiosBaseInstance.interceptors.response.use(
    response => response,
    async error => {
        const originalRequest = error.config

        // check if the error is due to an expired access token
        if (error.response.status === 401 && !originalRequest._retry) {
            originalRequest._retry = true

            // from Pinia store
            const store = useAuthStore()
            await store.refreshAccessToken()

            // update the Authorization header with the new access token
            axiosBaseInstance.defaults.headers.common['Authorization'] = `Bearer ${store.accessToken}`

            // retry the original request
            return axiosBaseInstance(originalRequest)
        }
        return Promise.reject(error)
    }
)

 

 

Leave a Comment