Axios interceptor in vue 2 JS using vuex

45,970

1.

First of all I'd use a Vuex Module as this Login/Session behavior seems to be ideal for a Session module. After that (which is totally optional) you can set up a Getter to avoid accessing the state itself from outside Vuex, you'd would end up with something like this:

state: {
  // bear in mind i'm not using a module here for the sake of simplicity
  session: {
    logged: false,
    token: ''
  } 
},
getters: {
  // could use only this getter and use it for both token and logged
  session: state => state.session,
  // or could have both getters separated
  logged: state => state.session.logged,
  token: state => state.session.token
},
mutations: {
  ...
}

With those getters set, you can get the values a bit easier from components. With either using this.$store.getters.logged (or the one you'd want to use) or using the mapGetters helper from Vuex [for more info about this you can check the getters docs]:

import { mapGetters } from 'vuex'
export default {
  // ...
  computed: {
    ...mapGetters([
      'logged',
      'token'
    ])
  }
}

2.

I like to run Axios' interceptors along with Vue instantation in main.js creating, importing and executing an interceptors.js helper. I'd leave an example so you get an idea, but, then again, this is my own preference:

main.js

import Vue from 'vue';
import store from 'Src/store';
import router from 'Src/router';
import App from 'Src/App';

// importing the helper
import interceptorsSetup from 'Src/helpers/interceptors'

// and running it somewhere here
interceptorsSetup()

/* eslint-disable no-new */
new Vue({
    el: '#app',
    router,
    store,
    template: '<App/>',
    components: { App }
});

interceptors.js

import axios from 'axios';
import store from 'your/store/path/store'

export default function setup() {
    axios.interceptors.request.use(function(config) {
        const token = store.getters.token;
        if(token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    }, function(err) {
        return Promise.reject(err);
    });
}

And there you'd end up having all the behavior cleanly encapsulated.

Share:
45,970
BT101
Author by

BT101

Updated on November 21, 2021

Comments

  • BT101
    BT101 over 2 years

    I store token after success login call in vuex store like this:

    axios.post('/api/auth/doLogin.php', params, axiosConfig)
        .then(res => {
            console.log(res.data); // token
            this.$store.commit('login', res.data);
        })
    

    axiosConfig is file where I only set baseURL export default { baseURL: 'http://localhost/obiezaca/v2' } and params is just data sent to backend.

    My vuex file looks is:

    import Vue from 'vue';
    import Vuex from 'vuex';
    
    Vue.use(Vuex);
    
    export const store = new Vuex.Store({
        state: {
            logged: false,
            token: ''
        },
        mutations: {
            login: (state, response) => {
                state.logged = true;
                state.token = response;
                console.log('state updated');
                console.log('state.logged flag is: '+state.logged);
                console.log('state.token: '+state.token);
            },
            logout: (state) => {
                state.logged = false;
                state.token = '';
            }
        }
    });
    

    It is working correctly, I can re-render some of content in my SPA basing on v-if="this.$store.state.logged" for logged user. I'm able to access this.$store.state.logged from any component in my entire app.

    Now I want to add my token to every request which call my rest API backend. I've created basic axios http interceptor which looks like this:

    import axios from 'axios';
    
    axios.interceptors.request.use(function(config) {
        const token = this.$store.state.token;
        if(token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    }, function(err) {
        return Promise.reject(err);
    });
    

    Now I have 2 problems/questions about it.

    1. I know that it is available to use this.$store.state.logged or this.$store.state.token across every component but can I use it same way in single javascript file?
    2. Where should I execute/start my interceptor javascript file? It is independent file which lays in my app main folder but I am not calling it anywhere, in angularJS which I was working before, I had to add $httpProvider.interceptors.push('authInterceptorService'); in config but I don't know how to do same thing in vue architecture. So where should I inject my interceptor?

    EDIT

    I followed GMaiolo tips I added

    import interceptor from './helpers/httpInterceptor.js';
    interceptor();
    

    to my main.js file and I refactor my interceptor to this:

    import axios from 'axios';
    import store from '../store/store';
    
    export default function execute() {
        axios.interceptors.request.use(function(config) {
            const token = this.$store.state.token;
            if(token) {
                config.headers.Authorization = `Bearer ${token}`;
            }
            return config;
        }, function(err) {
            return Promise.reject(err);
        });
    }
    

    Result of this changes is that every already existing backend calls ( GET ) which don't need token to work stopped working but it is logical because I didn't clarified to which request it should add token so it is trying to add it everywhere and in my interceptor something is still wrong and that is why every already exisitng request stopped working.

    When I try to do backend POST call in browser console I still get this error:

    TypeError: Cannot read property '$store' of undefined

    Although I import store to my interceptor file. Any ideas? I can provide some more information if any needed.

    I additionally add screenshot of this main, store and interceptor tree structure so you can see that I'm importing fron correct path:

    path

  • BT101
    BT101 over 6 years
    Line where I declare token costant cause this error: TypeError: Cannot read property '$store' of undefined at eval (httpInterceptor.js?c694:5). I'm not using Getter to gather it yet I tried to accomplish it with this.$store.state.token;. Should I import vuex also there? Or maybe I must use this getters in this case?
  • BT101
    BT101 over 6 years
    It looks like this.$store.state.token is not available in my interceptor.
  • GMaiolo
    GMaiolo over 6 years
    Yes! You'd have to import the store from within interceptors.js, my bad!
  • BT101
    BT101 over 6 years
    I added edit, please take a look. I am still fighting with this interceptor.
  • GMaiolo
    GMaiolo over 6 years
    @BT101 check my updated answer, you'll have to import the store directly from the interceptor script file
  • BT101
    BT101 over 6 years
    Oh I didn't noticed const token = store.state.token; this change. I'll try it on.
  • BT101
    BT101 over 6 years
    Please take one more look at vuex file where I export constant store beacuse now I have bundler warning export 'default' (imported as 'store') was not found in '../store/store'.
  • BT101
    BT101 over 6 years
    Ok. I had to import { store } from 'path/to/store'; not import store from 'path/to/store';. Now it is working thanks!
  • GMaiolo
    GMaiolo over 6 years
    @BT101 yes, my snippet was a bare example, if you export something without default you'll have to destructure it with brackets like you did: { something }. Glad you figured it out!
  • Sarvar N
    Sarvar N over 5 years
    as token is getter, you should use const token = store.getters.token or const token = store.state.session.token
  • Ruberandinda Patience
    Ruberandinda Patience almost 5 years
    This helped me even I can't figure out how but it assist me enough. Learning those new technology is big challenge