Search code examples
laravelvue.jstokenvuexlaravel-passport

Vuejs Laravel Passport - what should I do if access token is expired?


I am using Vuejs SPA with Laravel API as backend. I successfully got the personal access token and store in localStorage and Vuex state like below.

token: localStorage.getItem('token') || '',
expiresAt: localStorage.getItem('expiresAt') || '',

I use the access token every time I send axios request to laravel api. Every thing works well. However, initially the token was set to 1 year expiration so when I develop I didn't care about token being expired and today suddenly I thought what is going to happen if token expired. So I set token expiry to 10 seconds in laravel AuthServiceProvier.php.

Passport::personalAccessTokensExpireIn(Carbon::now()->addSecond(10));

and then I logged in and after 10 seconds, every requests stopped working because the token was expired and got 401 unauthorised error.

In this case, how can I know if the token is expired? I would like to redirect the user to login page if token is expired when the user is using the website.


Solution

  • This is what I do. Axios will throw error if the response code is 4xx or 5xx, and then I add an if to check if response status is 401, then redirect to login page.

    export default {
        methods: {
            loadData () {
                axios
                    .request({
                        method: 'get',
                        url: 'https://mysite/api/route',
                    })
                    .then(response => {
                        // assign response.data to a variable
                    })
                    .catch(error => {
                        if (error.response.status === 401) {
                            this.$router.replace({name: 'login'})
                        }
                    })
            }
        }
    }
    

    But if you do it like this, you have to copy paste the catch on all axios call inside your programs. The way I did it is to put the code above to a javascript files api.js, import the class to main.js, and assign it to Vue.prototype.$api

    import api from './api'
    Object.defineProperty(Vue.prototype, '$api', { value: api })
    

    So that in my component, I just call the axios like this.

    this.$api.GET(url, params)
        .then(response => {
            // do something
        })
    

    The error is handled on api.js. This is my full api.js

    import Vue from 'vue'
    import axios from 'axios'
    import router from '@/router'
    
    let config = {
      baseURL : process.env.VUE_APP_BASE_API,
      timeout : 30000,
      headers : {
        Accept         : 'application/json',
        'Content-Type' : 'application/json',
      },
    }
    
    const GET = (url, params) => REQUEST({ method: 'get', url, params })
    const POST = (url, data) => REQUEST({ method: 'post', url, data })
    const PUT = (url, data) => REQUEST({ method: 'put', url, data })
    const PATCH = (url, data) => REQUEST({ method: 'patch', url, data })
    const DELETE = url => REQUEST({ method: 'delete', url })
    
    const REQUEST = conf => {
      conf = { ...conf, ...config }
      conf = setAccessTokenHeader(conf)
    
      return new Promise((resolve, reject) => {
        axios
          .request(conf)
          .then(response => {
            resolve(response.data)
          })
          .catch(error => {
            outputError(error)
            reject(error)
          })
      })
    }
    
    function setAccessTokenHeader (config) {
      const access_token = Vue.cookie.get('access_token')
      if (access_token) {
        config.headers.Authorization = 'Bearer ' + access_token
      }
    
      return config
    }
    
    /* https://github.com/axios/axios#handling-errors */
    function outputError (error) {
      if (error.response) {
        /**
         * The request was made and the server responded with a
         * status code that falls out of the range of 2xx
         */
        if (error.response.status === 401) {
          router.replace({ name: 'login' })
          return
        }
        else {
          /* other response status such as 403, 404, 422, etc */
        }
      }
      else if (error.request) {
        /**
         * The request was made but no response was received
         * `error.request` is an instance of XMLHttpRequest in the browser
         * and an instance of http.ClientRequest in node.js
         */
      }
      else {
        /* Something happened in setting up the request that triggered an Error */
      }
    }
    
    export default {
      GET,
      POST,
      DELETE,
      PUT,
      PATCH,
      REQUEST,
    }