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
Related
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}` };
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.
I managed to get Reddit OAuth to work and it is getting redirected to my homepage.
And I am getting a URL like http://localhost:3000/?state=XEA12&code=6p4pAyle2EWGVwIBlFJ6ERXjxKg
Now, when I try other APIs like /api/me. I am not able to get it working. I have also set the scope of my app to identity.
Here is the code snippet that I wrote:
import axios from "axios";
import useSWR from "swr";
const link = "https://www.reddit.com/api/v1/me";
const config = {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "bearer 6p4pAyle2EWGVwIBlFJ6ERXjxKg",
},
};
const fetcher = (url) => axios.get(url, config).then((res) => res);
export default function Home() {
const { data, error } = useSWR(link, fetcher);
if (error) return <div>failed to load </div>;
if (!data) return <div>loading...</div>;
return <div>{JSON.stringify(data)}</div>;
}
Can you please tell me what I am doing wrong? Is it because of the headers or do I need to pass something else?
According to the documentation you need to retrieve access_token first, by using code param. Here is how to do that: https://github.com/reddit-archive/reddit/wiki/OAuth2#retrieving-the-access-token
Then you can use that access_token in a bearer auth. There is another note: API requests with a bearer token should be made to https://oauth.reddit.com, NOT www.reddit.com. Hope it will help
I might need some help how to use axios interceptors here. This is a part of the login screen. I want to have the interceptor refresh the token when its missing or its expired. I am not even sure it is the right place to have this code as token refresh will needs to be happening after the login too.
I have two axios instances. One for the API and one for auth0. I would need a way to connect them so they work together and reliable.
import token from "../../network/axios-auth0";
import client from "../../network/axios-client";
//Gets token and updates the redux token value
getToken() {
token.post("token", {
client_id: config.clientId,
client_secret: config.clientSecret,
audience: config.clientAudience,
grant_type: "client_credentials"
})
.then(response => {
this.props.onUpdateToken(response.data.access_token);
this.getGroup(response.data.access_token)
})
.catch(function (error) {
console.log(error);
})
}
//Gets token from redux and gets data
getGroup() {
client.interceptors.request.use(function (config) {
//call getToken() for new token or check old token
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
client.get("groups/5ca21cd1cb9134222e397f14", {data: {}, headers: {"Authorization":"Bearer " + this.props.bearerToken}})
.then(response => {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
}
onLoginPress(){
this.getToken();
}
render() {
return (
<View>
<TextInput placeholder={"user"}/>
<TextInput placeholder={"pass"}/>
<Button title={"login"} onPress={() => this.onLoginPress()}/>
</View>
)
}
const mapStateToProps = state => {
return {
token: state.values.token
};
};
const mapDispatchToProps = dispatch => {
return {
onUpdateToken: (value: string) => dispatch(updateToken(value))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
I mean the interceptor will be needed in every axios call (to prevent a request getting stuck because of old token) So it would be best to attach it to the instance. But then I don't know how to update the token in redux from the file where the instance is defined. It's probably not the best practice either. Here is one of the 2 instances defined.
import axios from "axios"
import config from "../config"
const client = axios.create({
baseURL: config.apiUrl,
headers: {
"Content-type": "application/json"
}
});
client.defaults.headers.get["Content-Type"] = "application/json";
export default client;
How would one do this professionally. I don't want to copy the interceptor for each call in the app.
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
}