Axios interceptors - Not using instance until AsyncStorage resolved? - react-native

I've an Axios Interceptor setup to manage responses and cut down on re-writing code everywhere. Currently, I need to add the Authorization header using the { config } object in each call like below.
apiCall = () => {
const token = await AsyncStorage.getItem('JWT_BEARER_TOKEN');
const config = {
headers: {
'Authorization': 'Bearer ' + token,
}
}
const attendance = await axiosInstance.post('/team/matchday', data, config);
// ... do something with attendance
}
I'd like to do it in the axiosInstance I've create as below, but I'm getting a promise rejected error. I presume this is because token is still an incomplete promise when it is returned.
Any ideas how to handle this config correctly?
import { AsyncStorage, Alert } from 'react-native';
import axios from 'axios';
const ReturnAxiosInstance = async (token) => {
const AxiosInstance = axios.create({
baseURL: 'http://localhost:4000',
timeout: 3000,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + await AsyncStorage.getItem('JWT_BEARER_TOKEN'),
},
});
AxiosInstance.interceptors.response.use(
response => response,
(error) => {
if (!error.response) {
Alert.alert('Network Error!');
return console.log(error);
// return dispatch({ type: 'NETWORK_FAILURE' });
} else if (error.response.status === 500) {
Alert.alert('Server Error!');
} else if (error.response.status === 404) {
Alert.alert('Endpoint doesn\'t exist!');
}
// handle the errors due to the status code here
return error.response;
},
);
return AxiosInstance;
};
export default ReturnAxiosInstance();

You need to add in the request interceptor for your Axios instance.
// ...
axiosInstance.interceptors.request.use(
async (config) => {
config.headers.authorization = await AsyncStorage.getItem('JWT_BEARER_TOKEN');
return config;
},
error => Promise.reject(error)
);
// ...

Related

how can i add headers in vue js using async/await

i'm trying to send a request to the backend which uses headers, please how can i add the headers
this is my script tag
<script>
import axios from "axios";
export default {
data: () => ({
fullName: "",
streetAddress1: ""
}),
created() {
//user is not authorized
if (localStorage.getItem("token") === null) {
this.$router.push("/login");
}
},
methods: {
async onAddAddress() {
const token = localStorage.getItem("token");
headers: {
"Content-Type": "application/json",
Authorization: "Bearer" + token,
"x-access-token": token
}
try {
let data = {
fullName: this.fullName,
streetAddress: this.streetAddress1
};
const response = axios
.post("http://localhost:5000/api/addresses", data)
.then(res => {
console.log(res);
});
console.log(response);
} catch (error) {
console.error("error >>", error);
}
}
}
}
this code gives me an error, please how can i go about this
There are a few problems with your code. For instance you do not define headers as a variable and you do not add it to your axios request as a third argument. I think you need something like this:
async onAddAddress() {
const token = localStorage.getItem("token");
/// define headers variable
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer" + token,
"x-access-token": token
};
let data = {
fullName: this.fullName,
streetAddress: this.streetAddress1
};
try {
/// config as the third argument.
conts result = await axios.post("http://localhost:5000/api/addresses", data, { headers });
console.log(result);
}
catch (error) {
console.error("error >>", error)
}
}
For async/await to work, you need to add await in front of the axios call.
Hope this helps.

how to intercept 401 in App.vue on mounted hook Vuejs (Quasar)

I have a serviceRoot file that construct the axios instance. I'm using different services also to trigger the API depending of the data. (I will not speak about these services here).
My problem is, I want to kick people when they come back on the website after their tkn is expired.
So I would like to put a code in the App.vue file on the mounted hook, because it's for me the best moment to check if the user gets a 401 error when he tries to load the website directly on the /profil url.
import VueCookies from 'vue-cookies'
import Conf from '../../conf.js'
export default class HttpClient {
headers = {
Authorization: '',
'Content-Type': '',
Accept: ''
}
service = {}
method = ''
userId = ''
constructor() {
const tkn = VueCookies.get('tkn')
const token = `Bearer ${tkn}`
// eslint-disable-next-line prettier/prettier
this.headers.Authorization = token
this.headers['Content-Type'] = 'application/json;charset=utf-8'
this.headers.Accept = 'application/json'
this.service = Axios.create({
baseURL: Conf.api_url,
headers: this.headers,
data: this.data
})
this.service.interceptors.response.use(this.handleSuccess, this.handleError)
console.log(this.handleSuccess, this.handleError)
}
handleSuccess(response) {
return response
}
handleError(error) {
return Promise.reject(error)
}
request = async (axios, config) => {
if (
config.method === 'POST' ||
config.method === 'PUT' ||
config.method === 'DELETE' ||
config.method === 'GET'
) {
console.log('trigger api')
}
const request = {
method: config.method || this.method,
url: config.url,
data: config.data,
headers: config.headers ? config.headers : this.headers,
params: config.params,
responseType: config.responseType
}
if (config.headers) {
request.headers = {
...request.headers,
...config.headers
}
}
axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'
try {
const response = await axios(request)
return response.data
} catch (error) {
throw error
}
}
}
But with that kind of serviceRoot I really don't know how to do that in the app.vue, I tried this:
<script>
import { defineComponent } from '#vue/composition-api'
import Axios from 'axios'
export default defineComponent({
name: 'App',
data() {
return {
layout: 'div'
}
},
created() {
console.log('CREATED')
Axios.interceptors.response.use(undefined, function(err) {
return new Promise(function(resolve, reject) {
if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
console.log('DISPATCH')
// this.$store.dispatch(logout)
}
throw err
})
})
}
})
</script>
But in that case, the Axios.interceptors doen't work with the first file so I wont work.
I tried to import HttpClient from './services/serviceRoot' but the browser says:
Uncaught TypeError: Super expression must either be null or a function.
I can kick directly on serviceRoot after the throw error, but I can't diferency 401's errors (because there are diffents kind of 401s and I wanna only kick people with wront token on the load of the app.
Thanks
Interceptor should go in main.js file
axios.interceptors.response.use(response => {
return response;
}, error => {
if (error && error.message === 'Request failed with status code 401') {
localStorage.removeItem('user');
router.push({name: loginPageName});
}

react native fetch hook and refresh jwt token

i have made a custom hook to fetch data from api, but the token only valid for 5 second.
so i made this hook
the problem is when i call the hooks from my page it called many time and the refresh token already expired
when i access the api i will check the response first if the token invalid i tried to refresh my token using handleRefreshToken
nb : im using useContext for my state management
import React, {useEffect, useState, useContext} from 'react';
import {View, StyleSheet} from 'react-native';
import {AuthContext} from '../Auth/Context';
import AsyncStorage from '#react-native-community/async-storage';
import {urlLogin, URLREFRESHTOKEN} from '../Configs/GlobaUrl';
const FetchData = () => {
const {loginState, authContext} = useContext(AuthContext);
const [data, setData] = useState([]);
const [message, setMessage] = useState('');
const [loading, setIsLoading] = useState(false);
const {dispatchRefreshToken} = authContext;
const handleRefreshToken = async (callbackUrl, callbackBody) => {
const refBody = {
client_id: loginState.ipAddress,
ipAddress: loginState.ipAddress,
employee_id: loginState.userData.Pegawai_Id,
jwttoken: loginState.userToken,
refresh_tokenn: loginState.refreshToken,
};
console.log('======refreshtokencalled==========');
console.log(refBody.refresh_tokenn, '<=refresh token');
console.log(refBody.jwttoken, '<=jwt token');
let response = await fetch(URLREFRESHTOKEN, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(refBody),
redirect: 'follow',
});
let result = await response.json();
console.log(result, ' ini result');
if (
result.item3 !== 'refresh token gagal' &&
result.item3 !== 'refresh token sudah tidak berlaku'
) {
let refresh = result.item2;
let token = result.item1;
// the backend doesnt send any succes / error code only item1 for token, //item2 refresh token and item3 for error
dispatchRefreshToken(token, refresh);
await AsyncStorage.setItem('refreshToken', refresh);
await AsyncStorage.setItem('token', token);
return getData(callbackUrl, callbackBody);
} else {
return null;
}
};
const getData = async (url, body) => {
setIsLoading(true);
let result;
try {
let response = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${loginState.userToken}`,
},
body: JSON.stringify(body),
redirect: 'follow',
});
if (response.status == '401') {
let refreshResult = await handleRefreshToken(url, body);
console.log(refreshResult);
} else {
result = await response.json();
console.log(result);
console.log(loginState.refreshToken);
if (result.code == '1') {
setData(result.data);
setIsLoading(false);
} else {
throw result;
}
}
} catch (err) {
setData([]);
console.log(err, 'masuk error usefetchbybutton');
console.log(err.message, err.code);
setIsLoading(false);
setMessage(err);
}
};
return {
data: data,
message: message,
loading: loading,
getData: getData,
};
};
export default FetchData;
this is my dispatch refresh token
const authContext = useMemo(
() => ({
logIn: async (token, userData, refreshToken) => {
console.log(token, '<>', refreshToken, 'ini memoisa');
dispatch({
type: 'LOGIN',
token: token,
userData: userData,
refreshToken: refreshToken,
});
},
logOut: () => {
AsyncStorage.clear((error) => {
console.log(error);
});
dispatch({type: 'LOGOUT'});
},
dispatchRefreshToken: (userToken, refreshToken) => {
console.log(refreshToken, '=refresh dispatch=');
console.log(userToken, '=userToken dispatch=');
dispatch({
type: 'REFRESHTOKEN',
userToken: userToken,
refreshToken: refreshToken,
});
},
}),
[],
);
my reducer function
const loginReducer = (prevState, action) => {
switch (action.type) {
some case ...
case 'REFRESHTOKEN':
return {
...prevState,
userToken: action.userToken,
refreshToken: action.refreshToken,
};
}
};
Use recursion. The pseudo code is as follows
const getData = async (args, times) => {
// try to fetch data
const data = await Api.fetch(args);
// if token need to be refreshed.
if (check401(data)) {
// Use variable times to prevent stack overflow.
if (times > 0) {
// refresh the token
await refreshToken()
// try again
return getData(args, times - 1);
} else {
throw new Error("The appropriate error message")
}
}
return dealWith(data)
}
The logical above can be encapsulated to all your api. Like this
const wrapApi = (api) => {
const wrappedApi = async (args, times) => {
const data = await api(args);
// if token need to be refreshed.
if (check401(data)) {
// Use variable times to prevent stack overflow.
if (times > 0) {
// refresh the token
await refreshToken()
// try again
return wrappedApi(args, times - 1);
} else {
throw new Error("The appropriate error message")
}
}
return dealWith(data)
}
return wrappedApi;
}

How to set authorization header coorectly?

Problem:
In my react native app in order to remove repeated calls I have developed a general POST GET methods in httpClient file. It code is look likes this.
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
axios.defaults.headers.post['Content-Type'] = 'application/json';
var instance = null;
const setAuthorisationHeder = async () => {
const token = JSON.parse(await AsyncStorage.getItem('auth_data'));
if (token) {
console.log('>>>>>> instance', instance);
Object.assign(instance.headers, {
Authorization: 'Bearer' + token.accessToken,
});
} else {
console.log('>>>>>> instance', instance);
Object.assign(instance.headers, {
Authorization: '',
});
}
};
export const setHeader = () => {
console.log('>>>>>>>> HIIII');
instance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
'Content-Type': 'application/json',
},
});
instance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (error.response.status) {
if (error.response.status === 401) {
AsyncStorage.removeItem('auth_data');
} else {
throw error;
}
} else {
console.log(error);
}
},
);
};
export const Get = (route, data) => {
function getData() {
return instance.get(
route,
data == null ? {data: {}} : {data: JSON.stringify(data)},
);
}
if (instance) {
console.log('>>>>>> HIIIIii');
// setAuthorisationHeder();
return getData();
}
return setHeader().then(getData);
};
export const Post = (route, data) => {
console.log('>>>>>> route', route);
function postData() {
return instance.post(route, JSON.stringify(data));
}
if (instance) {
console.log('>>>>>> HIIIIii');
// setAuthorisationHeder();
// setAuthorisationHeder();
return postData();
}
return setHeader().then(postData);
};
Can some tell me a way to add an authorization header to this instance? My token is storing the Asyncstorage in the middle of some actions so at the beginning called I don't have the token. As my code setHeader is running only one time so I created a method call setAuthorisationHeder() function. But it is giving me can not find property .then error when I am putting a request. Can someone help me to solve this issue? Thank you?
you can define global headers once and use it in every network call.
https://github.com/axios/axios#global-axios-defaults
Create a global auth variable where you'll store the auth data from storage. Before making a request get the auth data and use interceptor to set the bearer token.
let authToken = '';
const getAuthToken = async () => {
// asumming auth token was saved as string
authToken = await AsyncStorage.getItem('auth_data');
};
Interceptor
// request interceptor
axiosInstance.interceptors.request.use(
function (config) {
// Do something before request is sent
config.headers.Authorization = `Bearer ${authToken}`;
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
complete code
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
let authToken = '';
const axiosInstance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
'Content-Type': 'application/json',
},
});
// request interceptor
axiosInstance.interceptors.request.use(
function (config) {
// Do something before request is sent
config.headers.Authorization = `Bearer ${authToken}`;
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
const getAuthToken = async () => {
// asumming auth token was saved as string
authToken = await AsyncStorage.getItem('auth_data');
};
export const Get = async (route, data = {}) => {
// get and set auth token
await getAuthToken();
// route = /user?id=787878 or /user/787878
return await axiosInstance.get(route);
};
export const Post = async (route, data = {}) => {
await getAuthToken();
return await axiosInstance.post(route, data);
};

Cannot get correct error from Axios

I have a doFetch function that handles all my api calls:
const doFetch = function(params){
...
// Make request using Axios. Axios is promise based.
return axios({
method: method,
url: baseUrl + url,
data: queryParams,
timeout: timeout,
headers: {
'Content-Type': contentType,
'Authorization': `bearer ${Auth.getToken()}` // set the authorization HTTP header
},
responseType: responseType
}).then((response) => {
if(typeof params.callback === "function"){
params.callback(response);
}
else {
return response;
}
}).catch((err) => {
if(typeof params.error === "function") {
if (err.response) {
params.error(err.response.data);
}
}
else{
if (err.response) {
return err.response.data;
}
else{
return err;
}
}
});
};
One such api call is returning a custom error like so (express server):
return res.status(400).json("There was an error on the server.");
The function that calls doFetch is saveUser:
saveUser(userObj).then((response) => {
console.log("No Error");
}).catch((error) => {
console.log("Error:", error);
});
The problem is that I am seeing No Error in the terminal, when I should only be expecting the error message to show. Any ideas?
I like to return promise exactly, to be sure that it does/returns what I want.
I don't like to rely on "promise"-s of 3rd parties.
So I would recommend You to wrap it inside of promise and resolve/reject responses/errors manually:
const doFetch = params => {
...
// Make request using Axios. Axios is promise based.
return new Promise((resolve, reject) => {
axios({
method: method,
url: baseUrl + url,
data: queryParams,
timeout: timeout,
headers: {
'Content-Type': contentType,
'Authorization': `Bearer ${Auth.getToken()}` // set the authorization HTTP header
},
responseType: responseType
})
.then((response) => {
console.info('doFetch:', response); // for debug purposes
if(typeof params.callback === "function"){
params.callback(response);
}
resolve(response);
})
.catch((err) => {
console.error('doFetch:', err); // for debug purposes
const error = (err.response) ? err.response.data : err;
if(typeof params.error === "function") {
params.error(error);
}
reject(error);
});
};
};