Vue Axios local stored token is undefined - vue.js

In Vue I am building a small Userprofile page. It is build on token-authentication using Axios. When mounting this page the token is undefined.
with login a token is placed in the localStorage.
The Axios Get request is build outside the Vue component
Api.js
import axios from 'axios'
export default () => {
return axios.create({
baseURL: `http://localhost:8081/`
})
}
Get request
import Api from '#/services/Api'
let config = {
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem('token')
}
}
export default {
index () {
return Api().get('user/profile', config)
}
}
Vue
<script>
export default {
data: () => ({
user: {}
}),
mounted () {
this.user = UserProfileService.index
console.log(UserProfileService.config)
}
}
</script>
There are many advices and I tried them all. With tics, with commas etc. Who sees the big picture?

Use a request interceptor to set the Authorization header:
// Api.js
export default () => {
const instance = axios.create({
baseURL: `http://localhost:8081/`
})
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
return instance
}

I added the code from digital drifter, and solved the problem (with help) with changing the mounted function in Vue.
mounted () {
console.log('mounted')
UserProfileService.index()
.then((res) => {
this.user = res.data
})
}

Related

Mock .get() Function using Jest on VueJS

I am trying to mock a GET request to get some Posts using the ID. This is the code I am trying to mock:
getPost() {
this.refreshToken();
http
.get(`/posts/${this.$cookie.get('postid')}`, {
headers: {
"Authorization": `Bearer ${this.$cookie.get('token')}`,
"Content-type": "application/json",
},
})
.then((response) => {
this.post = response.data;
})
.catch((error) => {
console.log(error.response);
});
}
This is my attempt at a test:
import {getPost} from '#/views/Post.vue'
import axios from 'axios';
jest.mock('axios');
describe('get Post by ID', () => {
afterEach(() => {
jest.resetAllMocks();
});
it('should return empty when axios.get failed', async () => {
const getError = new Error('error');
axios.get = jest.fn().mockRejectedValue(getError);
const actualValue = await getPost();
expect(actualValue).toEqual(new Map());
expect(axios.get).toBeCalledWith('/posts/postid');
});
it('should return users', async () => {
const mockedUsers = [{ postID: 1 }];
axios.get = jest.fn().mockResolvedValue(mockedUsers);
const actualValue = await getPost(['1']);
expect(actualValue).toEqual(mockedUsers);
expect(axios.get).toBeCalledWith('/posts/postid');
});
})
The error I am getting is:
TypeError: (0 , _Post.getPost) is not a function
I am not sure what to do, and any help would be super appreciated. Thanks!
Assuming you have getPost() defined in the Post component's methods, you can't use named imports to access getPost. Instead, you'll have to mount the component, and use the wrapper's vm:
// Post.spec.js
import { shallowMount } from '#vue/test-utils'
import Post from '#/views/Post.vue'
it('...', () => {
const wrapper = shallowMount(Post)
await wrapper.vm.getPost()
expect(wrapper.vm.post).toEqual(...)
})
Also make sure to return the axios call in getPost() so that it could be awaited:
// Post.vue
export default {
methods: {
getPost() {
this.refreshToken();
👇
return http.get(/*...*/)
.then(/*...*/)
.catch(/*...*/);
}
}
}

Using axios instance with vuex

I am building a Vue frontend with Rails backend.
On frontend I am using Axios and I have set these interceptors for authentication:
import axios from 'axios'
const API_URL = 'http://localhost:3000'
const securedAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
const plainAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
securedAxiosInstance.interceptors.request.use(config => {
const method = config.method.toUpperCase()
if (method !== 'OPTIONS' && method !== 'GET') {
config.headers = {
...config.headers,
'X-CSRF-TOKEN': localStorage.csrf
}
}
return config
})
securedAxiosInstance.interceptors.response.use(null, error => {
if (error.response && error.response.config && error.response.status === 401) {
// If 401 by expired access cookie, we do a refresh request
return plainAxiosInstance.post('/refresh', {}, { headers: { 'X-CSRF-TOKEN': localStorage.csrf } })
.then(response => {
localStorage.csrf = response.data.csrf
localStorage.signedIn = true
// After another successfull refresh - repeat original request
let retryConfig = error.response.config
retryConfig.headers['X-CSRF-TOKEN'] = localStorage.csrf
return plainAxiosInstance.request(retryConfig)
}).catch(error => {
delete localStorage.csrf
delete localStorage.signedIn
// redirect to signin if refresh fails
location.replace('/')
return Promise.reject(error)
})
} else {
return Promise.reject(error)
}
})
export { securedAxiosInstance, plainAxiosInstance }
On main.js I am making them available this way:
import VueAxios from 'vue-axios'
import { securedAxiosInstance, plainAxiosInstance } from './axios'
Vue.use(VueAxios, {
secured: securedAxiosInstance,
plain: plainAxiosInstance
})
new Vue({
el: '#app',
router,
store,
securedAxiosInstance,
plainAxiosInstance,
render: h => h(App)
})
And in components I can successfully use them like:
this.$http.secured.get('/items')
The problem is that I am unable to use them in store where I get:
Cannot read property 'secured' of undefined"
I tried in store among others:
import { securedAxiosInstance, plainAxiosInstance } from '../axios'
const store = new Vuex.Store({
secured: securedAxiosInstance,
plain: plainAxiosInstance,
.....
What is the correct way to do it?
You can use this._vm inside the store which refers to the Vue instance of the current application.
So in your case:
this._vm.$http.secured.get('/items')
As alternative you can pass the Vue instance as payload to your mutation/action, like:
this.$store.commit('myMutation',this)

How to use $nuxt.$loading from axios interceptor

I would like to use $nuxt.$loading https://nuxtjs.org/api/configuration-loading/ outside of Vue component. I created central js for hitting APIs.
services/api-client.js
import axios from "axios";
import { state } from '../store/modules/sessions';
const axiosClient = axios.create({
baseURL: process.env.BASE_URL,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Api-Key': state().token
}
});
axiosClient.interceptors.request.use(function (config) {
// show nuxt loading here
return config;
}, function (error) {
return Promise.reject(error);
});
axiosClient.interceptors.response.use(function (response) {
// hide nuxt loading here
if (response.data.status.code != 200) {
throw { message: response.data.status.errorDetail };
} else {
return response;
}
}, function (error) {
// hide nuxt loading here
return Promise.reject(error);
});
export default {
all(path) {
return axiosClient.get(path);
},
show(path) {
return this.all(path);
},
create(path, params) {
return axiosClient.post(path, params);
},
update(path, params) {
return axiosClient.put(path, params);
}
};
and from my index.vue I'm dispatching the actions which trigger the Api Request.
<template>
<div>
<h1> Welcome </h1>
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('getInsiders', this);
}
}
</script>
The solution to your problem is this code below.
Please try this.
export default function({ $axios, store }: any) {
$axios.onRequest((config: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.start()
return config
})
})
$axios.onResponse((response: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return response
})
})
$axios.onError((error: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return Promise.reject(error)
})
})
}
Do you really need to declare own axios client?
Standard way how to do this is using nuxt's axios module and then customize it in your plugin.
nuxt.config.js
modules: ['#nuxtjs/axios'],
plugins: ['~/plugins/axios']
~/plugins/axios
export default ({ $axios, redirect }) => {
$axios.onError(error => {
// do anything you need
})
}
The axios module will manage loading status automatically.
Although you still can disable progress for individual requests
Eg from component/action
await this.$axios.$get('/myapi', { progress: false })

Check if token still valid before request

I am using an index.js file to make api calls in a vue app. Is there a way to add a catch or a before each call to see if my token is still good and have the user redirected to login if it isnt?
import axios from 'axios'
const client = axios.create({
baseURL : 'http://myapi.com/api/',
json: true
})
export default {
async execute(method, resource, data) {
const token = localStorage.getItem('token')
return client({
method,
url: resource,
data,
crossdomain: true ,
headers: { "Authorization": `Bearer ${token}` }
}).then(req => {
return req.data
})
},
getResponses() {
return this.execute('get', 'GetResponses')
},
getAll(){
return this.execute('get', 'GetAll')
},
You can use an interceptor, where you can pass a function to be called before each request:
const client = axios.create({ baseURL: 'http://myapi.com/api/', json: true });
client.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (isTokenGood(token)) {
return config;
} else {
logout();
}
});
If anyone is interested i ended up going with an interceptor as per #thanksd
My code index.js file in my api folder now looks like this
import axios from 'axios'
import router from '../router'
const client = axios.create({
baseURL : 'http://myapi.com/api/',
json: true
})
client.interceptors.response.use(function (response) {
return response
}, function (error) {
if (error.response.status === 401) {
router.push('/Login')
console.info('auth token expired')
localStorage.clear()
sessionStorage.clear()
} else if (error.response.status === 403) {
router.push('/Login')
} else {
return Promise.reject(error)
}
})
export default {
async execute(method, resource, data) {
const token = localStorage.getItem('token')
return client({
method,
url: resource,
data,
crossdomain: true ,
headers: { "Authorization": `Bearer ${token}` }
}).then(req => {
return req.data
})
},
getResponses() {
return this.execute('get', 'GetResponses')
},
getAll(){
return this.execute('get', 'GetAll')
},

Set axios authorization header depends on store changes

I am new to Vue (I am a react person) and I am having this problem.
axios.js
import store from '../../store/index';
import axios from 'axios'
const API_URL = process.env.API_URL;
const token = store.getters.auth.token;
export default axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
store/index.js
import auth from './modules/auth'
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
state: {},
getters : {},
mutations: {},
actions:{},
modules: {
auth
},
strict: debug,
})
modules/auth
import { AUTH_SUCCESS, AUTH_GUEST } from '../actions/auth'
import axios from '../../util/axios/axios'
import Vue from "vue";
const state = {
token: localStorage.token || '',
};
const getters = {
token: state => state.token
};
const actions = {
[AUTH_GUEST]: async ({commit}) => {
await axios.post('auth/register',)
.then(response => {
commit(AUTH_SUCCESS, response);
})
.catch(error => {
console.log(error);
});
},
};
const mutations = {
[AUTH_SUCCESS]: (state, resp) => {
state.token = resp.data.token;
},
}
export default {
state,
getters,
actions,
mutations,
}
when trying to get the store from store/index it returns undefined.
probably the axios has been called before the store has been initialized.
but how can I deal with it?
the flow of the app is.
user register->get token->update store with this token->add to the axios header.
so for now on, all calls to the api will have the token provided.
First of all, you should be careful with Vue's reactivity caveats which affect Vuex aswell. In your case, you're adding a new property inside an object in a mutation.
Back to the main issue, your axios.js file is being executed before the Store instance is built, that's why you cannot access to it and you get undefined.
What I'd do is:
axios.js
import axios from 'axios';
const API_URL = process.env.API_URL;
export default (store) => axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${store.getters.auth.token}`
}
});
and then in your main file, where you have the main Vue instantiation I'd just run the function there, exporting the return of that function.