Problem when modifying axios default headers globally - Vue - javascript

In main.js
import axios from 'axios';
axios.defaults.headers.common = {
'Authorization': 'JWT ' + Vue.auth.getToken()
};
axios.defaults.baseURL = process.env.VUE_APP_BASE_URL; //TODO: append the trailing slash
// Add modified axios instance to Vue prototype so that to be available globally via Vue instance
Vue.prototype.$http = axios;
Everything works fine up to this point. (I am able to successfully login and store the token)
Now, I have another component that fetches a list of users from the server through an ajax call performed on component’s created() lifehook.
My problem is that when I am trying to access this.$http in component I get back a 401 error response from the server because Authorization header is not available to the request headers (although I have pre-configured axios.defaults.headers.common)
The strange thing is that if I hit the refresh button on my browser then the token is correctly attached to the request header and the list of users is successfully fetched**.**
Could anyone please explain to me why is that happening?

You can user axios request interceptors to add your headers globally
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
you can access your current request readers using config.header and you can set the headers to the request like
config.headers = {
'Authorization': 'JWT ' + Vue.auth.getToken()
}
https://github.com/axios/axios

You can create a class to add the headers of your choice globally.
import axios from 'axios';
/**
* A wrapper around an axios instance, preconfigured to have a base URL and auth headers
*/
class Axios {
constructor(baseUrl, bearerToken) {
return axios.create({
baseURL: baseUrl,
headers: {
Authorization: `Bearer ${bearerToken}`
}
});
}
}
export default Axios;
Then in your app.js
import { Axios } from 'my/class'
const myService = new Axios('baseURL', 'bearerToken');

Have you tried using asios.create?
http/index.js:
import axios from 'axios'
import env from '../config/env'
import store from '../store'
export default (() =>
axios.create({
baseURL: env.API_HOST,
headers: {
common: {
Authorization: store.getters['user/getAuthToken']
}
}
}))()
main.js:
import http from './http'
Vue.prototype.$http = http
Additionally I use a store action to update the axios client:
updateHTTPClientAuthToken () {
Vue.prototype.$http.defaults.headers.common.Authorization = this.getters.getAuthToken
}

Related

How to avoid code duplication when running NUXT.js and Axios?

If similar code (as in the example) is duplicated in different components but with slight differences in the name of what I pass in the function parameters
What are the options to take the code somewhere separately and then introduce that into my components with custom parameters?
(individually for each component)
Should I do this through a plugin?
If so, then how can I implement the individually necessary parameters on the components + how to connect the plugin only on these components and nowhere else?
For axios calls, you can create a function or class and import it every time
Something like services/axios
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: process.env.VUE_APP_BASE_URL,
headers: {
'Access-Control-Allow-Origin': '*',
accept: 'Accept: application/json',
},
});
export default axiosInstance;
And then in utils/requests
import axiosInstance from '#/services/axios';
const apiRequest = async (axiosConfig) => {
try {
const response = await axiosInstance(axiosConfig);
return {
success: true,
data: response.data.data || response.data;
}
} catch (error) {
if (error.response) {
error = error.response
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
error = 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
console.log(error.request);
} else {
error = error.message
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
return {
success: false,
error
}
}
};
export const getRequest = async (url) =>
await apiRequest({
method: 'GET',
url,
});
export const postRequest = async(url, requestBody) =>
await apiRequest({
method: 'POST',
url,
data: requestBody
});
You then import the getRequest and postRequest methods in the components
component.vue
import { getRequest, postRequest } from '#/utils/requests';
const response = await postRequest('/url', requestBody);
if (response.success) {
// do stuff on success
} else {
// error message
}
We can do it by mixins. Make a js file inside mixins folder and put this function there and add this mixin file in vue files to use this function.
I used nuxt/axios.js for my nuxt app. You can use it as a plugin.
In plugins folder, make a file axios.js
import axios from 'axios'
export default axios.create({
baseURL: process.env.baseURL,
})
NOTE: I am storing the base url of the server to be called for APIs in environment variables using dotenv lib. And this way, the base url is set for all the calls to be made.
Then import it in nuxt.config.js file:
module.exports = {
....
{
modules: [
'#nuxtjs/axios'
],
axios: {
...
},
}
TIP: If you have to store a value in axios like "token" or "cookie" globally. Then you can use axios defaults method
axios.defaults.headers.common = { Authorization: `bearer ${token}` };

Axios - update headers on exported axios.create instance

I have one api.js which exports by default an axios.create() instance:
import axios from 'axios'
import Cookies from 'js-cookie'
const api = axios.create({
baseURL: process.env.VUE_APP_API_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${Cookies.get('Token')}`,
Organization: Cookies.get('Organization'),
Company: Cookies.get('Company')
}
})
export default api
Then I import this in multiple files like this:
//api/users.js
import api from './api.js'
const methods = {
postUser (params) {
return api.post('/users', params)
},
getUser (id) {
return api.get('/users/' + id)
}
}
export default methods
However there will be some functions that should update the Cookies Organization and Company and I was wondering if is possible to update the default api instance and automatically update it in all imports that use it. I know a simple page refresh would work but I'm building a SPA and I would like to prevent screen to be manually refreshed.
You can add the headers dynamically, that way the cookies will be read on every request.
import axios from 'axios'
import Cookies from 'js-cookie'
const api = axios.create({
baseURL: process.env.VUE_APP_API_URL,
timeout: 10000,
// Static headers
headers: {
'Content-Type': 'application/json',
},
transformRequest: [function (data, headers) {
// You may modify the headers object here
headers['Authorization'] = `Bearer ${Cookies.get('Token')}`
headers['Organization'] = Cookies.get('Organization')
headers['Company'] = Cookies.get('Company')
// Do not change data
return data;
}],
})
export default api
I would suggest to read about interceptor for axios. (https://github.com/axios/axios#interceptors)
A very basic example would be the following.
Lets assume your webservice would return a response http status 401 header.
You'd intercept the response with the following:
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// happy case its 2XX
return response;
}, async (error) => {
if (error.response.status === 401) {
// do some logic to retrieve a new JWT or Cookie.get()
const jwt = Cookies.get('Token');
const config = error.config;
config.headers['Authorization'] = `Bearer ${jwt}`;
}
return await axios.request(config);
});
The next request will then have an authorization header attached to the request header.

The token is undefined after the nuxt.js route

Using js-cookie after the parameter is routed, the token in the cookie becomes undefined in the background debug and the token parsing fails.
<router-link :to="'/qa/item/'+item.id" target="_blank">{{item.title}}</router-link>
This is my request tool class
import axios from 'axios'
import {getUser} from '#/utils/auth'
// create
const service = axios.create({
baseURL: 'http://127.0.0.1:9012',
// base_url
timeout: 30000, // time
headers: { 'Authorization': 'Bearer '+ getUser().token }
})
export default service
This is my auth tool class
import Cookies from 'js-cookie'
const TokenKey = 'User-Token'
const NameKey = 'User-Name'
const AvatarKey = 'User-Avatar'
export function setUser(token,name,avatar) {
Cookies.set(NameKey, name)
Cookies.set(AvatarKey, avatar)
Cookies.set(TokenKey, token)
}
export function getUser() {
return {
token:Cookies.get(TokenKey),
name:Cookies.get(NameKey),
avatar:Cookies.get(AvatarKey)
}
}
export function removeUser() {
Cookies.remove(TokenKey)
Cookies.remove(NameKey)
Cookies.remove(AvatarKey)
}
This is the request method when the page is loaded.
findById(id) {
return request({
url: `/${group_name}/${api_name}/${id}`,
method: 'get'
})
},
The request tokens sent by other pages and this page are not undefined. When the click of this dynamic routing page is loaded, the tokens are invalid.

keep token updated on axios action

I want to consume an Express REST API that requires a valid json web token for some routes. Due to the fact I have to pass the token from the localstorage everytime I wanted to create an "Axios config file".
My file http.js contains the following code
import Vue from "vue";
import axios from "axios";
const devInstance = createInstance("http://localhost:3000");
devInstance.interceptors.request.use(config => {
console.log(config);
return config;
}, err => {
console.log(err);
return Promise.reject(err);
});
devInstance.interceptors.response.use(res => {
console.log(res);
return res;
}, err => {
console.log(err);
return Promise.reject(err);
});
const productionInstance = createInstance("http://www.myApi.com");
function createInstance(baseURL){
return axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.token}`
}
});
}
Vue.prototype.$http = devInstance; // Check debug/build mode
within my main.js I import this instance once
import http from "./http.js";
and from now on I can use this.$http to get the global axios instance without importing it.
When consuming the API localStorage.token returns undefined because it is not set when creating the instance.
How can I keep this Authorization attribute updated without passing in the token manually each time?
I think you should write a request interceptor that adds the Authorization header to all requests.
devInstance.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${localStorage.token}`;
return config;
}, (error) => Promise.reject(error));
This way you will always get the currently stored token.
You should create a timer and an async update for that Authorization field. Either a separate component that handles that, or a timer and async method on that component (not sure if you can do this with Vue though).
Hope it helps.
Cheers

Vuex dynamic values in actions

I'm using some classes that require something from localStorage as params. And I try to use these classes inside vuex actions. But when the script is loaded the value is set and even if localStorage updated the value in vuex action is not changed.
For example.
someService.js
export default class {
constructor (token) {
this.headers = {headers: { Authorization: token }}
}
}
actions.js
import someService from '#/services/someService'
const myService = new someService(localStorage.getItem('token'))
const actions = {
myService.stuff()
// do stuff
}
This way when you call someService even after login the token always is always null because the class is instantiated only at the beginning.
So the question how can I get the current value of "token" after its changed. Do I need to reinstantiate for each action?
Any ideas?
I've had to deal with this too, and I've ended up re-instantiating the object.
I'm using axios, but it would be similar in most other cases.
const someService = () => {
return axios.create({
baseURL: myServerUrl,
headers: {'Authorization': 'Bearer ' + localStorage.getItem('token')}
})
}
const actions = {
someService().get('items')
}
axios also allows changing the default this way, and then you wouldn't need to include the authorization
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
but that wouldn't really solve your problem either, as when you set
const myService = new someService(localStorage.getItem('token'))
you place the token into it at initialization time. so another option would be...
someService.js
export default class {
constructor (token) {
this.headers = {headers: { Authorization: token }}
},
updateToken (token) {
this.headers.headers.Authorization = token
}
}
actions.js
import someService from '#/services/someService'
const myService = new someService(localStorage.getItem('token'))
const actions = {
myService.stuff()
// do stuff
// log in
// get new token
myService.updateToken(localStorage.getItem('token'))
// do stuff with new token
myService.stuff()
}

Categories