How to direct error response with Axios Interceptor. I used axios interceptor for all http requests!
the expectation is that when the server error 500, 501 etc, will be redirected to route "/500"
export const request = axios.create({ baseURL: BASE_URL_API });
request.interceptors.request.use(
async (config: AxiosRequestConfig) => {
try {
const token = GetCookie('token');
config.headers = {
Authorization: `Bearer ${token}`,
};
return config;
} catch (errorConfig) {
return Promise.reject(errorConfig);
}
},
error => {
return Promise.reject(error);
},
);
// Add a response interceptor
request.interceptors.response.use(
response => {
return response;
},
async (error: AxiosError) => {
const status = error.response?.status;
try {
if (status === 401 && error.response?.data.error.message === "Full authentication is required to access this resource" ) {
RemoveCookie('token')
RemoveCookie('_currentUser')
window.location.href = '/';
} else {
return Promise.reject(error);
}
return Promise.reject(error);
} catch (errorValue) {
return Promise.reject(errorValue);
}
},
);
Related
I'm trying to make a global error handling in my vue application. I have a api.service.js file that includes my axios and, creates and my get,post functions:
/**
* Service to call HTTP request via Axios
*/
const ApiService = {
init(apiBaseUrl) {
Vue.use(VueAxios, axios);
Vue.axios.defaults.baseURL = apiBaseUrl;
},
/**
* Set the default HTTP request headers
*/
setHeader() {
Vue.axios.defaults.headers.common[
"Authorization"
] = `Bearer ${JwtService.getToken()}`;
},
setHeaderwToken(token) {
Vue.axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
},
/**
* Send the GET HTTP request
* #param resource
* #param slug
* #returns {*}
*/
get(resource, slug = "") {
var myBlob = new Blob([], {type:'text/plain'});
var init = { status: 200, statusText: "" };
var myResponse = new Response(myBlob, init);
return Vue.axios.get(`${resource}/${slug}`)
.catch((error) => {
if (error.response.status == 401) {
//401 response
if (resource != "CheckToken") {
// request isNot checktoken & 401 response, check if token is valid?
Vue.axios
.get("CheckToken")
.then((CheckTokenResponse) => {
console.log("CheckToken response");
if (CheckTokenResponse.data == "OK") {
//token valid + 401 response
init = { status: 401, statusText: "noAuthorityValid" };
myResponse = new Response(myBlob, init);
console.log(CheckTokenResponse);
console.log("//token valid + 401 response");
console.log(myResponse);
return myResponse;
} else {
init = { status: 401, statusText: "noTokenValid" };
myResponse = new Response(myBlob, init);
console.log(CheckTokenResponse);
console.log("//token NOT valid + 401 response");
return myResponse;
}
})
.catch(() => {
init = { status: 401, statusText: "noTokenValid" };
myResponse = new Response(myBlob, init);
return myResponse;
});
} else {
//request is CheckToken + 401 response
init = { status: 401, statusText: "noTokenValid" };
myResponse = new Response(myBlob, init);
console.log(error);
console.log("//request is CheckToken + 401 response");
return myResponse;
}
} else {
// != 401 response
console.log(error);
console.log("!= 401 response");
return error;
}
});
},
};
export default ApiService;
In my Vue component, I'm calling my ApiService:
ApiService.get("MyFunction")
.then((response) => {
console.log("MyFunction " + response);
.catch((error) => {
console.log("MyFunction " + error);
});
},
I tried to create a custom response (myResponse) and return it but it returns as undefined
(I guess that's a wrong approach)
What i want to achieve is,
when a function is called and return an error code from api,
(500, 401, 404..)
i want to catch it,
and if it's 401, then i want to call "CheckToken" function and then if, CheckToken returns "OK" i want to return "noAuthorityValid" (means token is valid but that function is unauthorized.), CheckToken is not OK, then i want to return noTokenValid and i want to do it in my vue component where i call my function:
ApiService.get("MyFunction")
.then((response) => {
console.log("MyFunction " + response);
// if (response.statusText == noAuthorityValid)
{
// show snackbar("you are not authorized for this function")
}
})
.catch((error) => {
console.log("MyFunction " + error);
});
},
I couldn't do it with api.service.js so i created a walk-around.
I imported axios in every component i need an axios call;
import axios from "axios";
then i used axios like this:
axios({
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
url: "MyFunction",
method: "get",
})
.then((response) => {....}
And then, in my top component's (top parent) created function, i used axios.interceptors.response like this:
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
this.handleError(error);
return Promise.reject(error);
}
);
and this is my handleError function:
handleError(error) {
if (error.response.status == 401) {
if (error.response.data.includes("expiredToken")) {
this.showSnackbar("Token is expired");
setTimeout(() => {
if (!window.location.href.includes("login")) {
this.$router.push({ name: "login" }).then(() => {
this.$store.dispatch(LOGOUT); //PURGES user data,
});
}
}, 2000);
} else if (
error.response.data.includes(
"UnauthorizedFunction"
)
) {
this.showSnackbar("You are not authorized for this function ");
} else {
this.showSnackbar("Error occured.");
}
} else {
this.showSnackbar("Error occured.");
}
}
This stupid problem took my 2.5 days..
This question already exists:
Vue router - beforeEach block causing 401?
Closed 2 years ago.
I have a Vue app using Vue router and I suddenly started getting a 401 on an axios.post /wp-json/jwt-auth/v1/and it only seems to be happening on OS X and iOS.
Thoughts on where to direct my debug hunt?
The error is:
{
"code": "rest_forbidden",
"message": "Sorry, you are not allowed to do that.",
"data": {
"status": 401
}
}
I post, it immediately fails with a 401, but only on Macs.
localClient config:
import axios from "axios";
import environment from "#/environments/environment";
import state from "../store";
import router from "../router";
const userData = JSON.parse(localStorage.getItem("userData"));
let instance = {};
if (userData) {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL,
headers: { Authorization: `Bearer ${userData.token}` }
});
} else {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL
});
}
instance.interceptors.request.use(
config => {
state.commit("setNetworkStatus", true);
return config;
},
error => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
state.commit("setNetworkStatus", false);
return response;
},
error => {
if ([401, 403].includes(error.response.status)) {
console.log(error);
state.commit("delUserData");
router.push("/login");
}
return Promise.reject(error);
}
);
export default {
get(path) {
return instance.get(instance.defaults.baseURL + path);
},
post(path, params) {
console.log(instance.defaults.baseURL + path, params);
return instance.post(instance.defaults.baseURL + path, params);
},
put(path, params) {
return instance.put(instance.defaults.baseURL + path, params);
},
delete(path, params) {
return instance.delete(instance.defaults.baseURL + path, params);
}
};
interceptor request success before response 401 failure:
interceptor request success=
{url: "https://panel.site.art/wp-json/jwt-auth/v1/site/transfer", method: "post", data: {…}, headers: {…}, baseURL: "https://panel.site.art/wp-json", …}
adapter: ƒ (t)
baseURL: "https://panel.site.art/wp-json"
data: "{"location_id":"rec140ttKVWJCDr8v","items":["recg1W9lQuLLRm8VS"]}"
headers:
Accept: "application/json, text/plain, */*"
Content-Type: "application/json;charset=utf-8"
__proto__: Object
maxContentLength: -1
method: "post"
timeout: 0
transformRequest: [ƒ]
transformResponse: [ƒ]
url: "https://panel.site.art/wp-json/jwt-auth/v1/site/transfer"
validateStatus: ƒ (t)
xsrfCookieName: "XSRF-TOKEN"
xsrfHeaderName: "X-XSRF-TOKEN"
__proto__: Object
A classic issue with safari & local-storage, there is a privacy config for safari which allows to disable localStorage (yeah, it is not works by the spec!)
Had the same issue in one of the companies I've worked for. Eventually, we wrote a specific flow for this case, with a product tradeoff.
It is better to save it inside a cookie in the matter explained here ReactJS - watch access token expiration
I finally figured this out by adding a billion console logs to the whole flow. What was happening is the login flow stores the user data (with token) in localStorage and Vuex store upon login. However, the way the local axios client was set up (I didn't build it) the axios instance that is used for the post was getting created WITHOUT a token, even though the token exists in state and localStorage. With a hard page refresh the axios instance is recreated with token.
So, I made the axios get, post, put, delete exports check the localStorage every time.
It's ugly as all get out, but it works. If anyone knows how to refactor this to be smaller, let me know.
import axios from "axios";
import environment from "#/environments/environment";
import state from "../store";
import router from "../router";
export default {
get(path) {
const userData = JSON.parse(localStorage.getItem("userData"));
let instance = {};
if (userData) {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL,
headers: { Authorization: `Bearer ${userData.token}` }
});
} else {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL
});
}
instance.interceptors.request.use(
config => {
state.commit("setNetworkStatus", true);
return config;
},
error => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
state.commit("setNetworkStatus", false);
return response;
},
error => {
if ([401, 403].includes(error.response.status)) {
state.commit("delUserData");
router.push("/login");
}
return Promise.reject(error);
}
);
return instance.get(instance.defaults.baseURL + path);
},
post(path, params) {
const userData = JSON.parse(localStorage.getItem("userData"));
let instance = {};
if (userData) {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL,
headers: { Authorization: `Bearer ${userData.token}` }
});
} else {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL
});
}
instance.interceptors.request.use(
config => {
state.commit("setNetworkStatus", true);
return config;
},
error => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
state.commit("setNetworkStatus", false);
return response;
},
error => {
if ([401, 403].includes(error.response.status)) {
state.commit("delUserData");
router.push("/login");
}
return Promise.reject(error);
}
);
return instance.post(instance.defaults.baseURL + path, params);
},
put(path, params) {
const userData = JSON.parse(localStorage.getItem("userData"));
let instance = {};
if (userData) {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL,
headers: { Authorization: `Bearer ${userData.token}` }
});
} else {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL
});
}
instance.interceptors.request.use(
config => {
state.commit("setNetworkStatus", true);
return config;
},
error => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
state.commit("setNetworkStatus", false);
return response;
},
error => {
if ([401, 403].includes(error.response.status)) {
state.commit("delUserData");
router.push("/login");
}
return Promise.reject(error);
}
);
return instance.put(instance.defaults.baseURL + path, params);
},
delete(path, params) {
const userData = JSON.parse(localStorage.getItem("userData"));
let instance = {};
if (userData) {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL,
headers: { Authorization: `Bearer ${userData.token}` }
});
} else {
instance = axios.create({
baseURL: environment.CUSTOM_BASE_URL
});
}
instance.interceptors.request.use(
config => {
state.commit("setNetworkStatus", true);
return config;
},
error => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
response => {
state.commit("setNetworkStatus", false);
return response;
},
error => {
if ([401, 403].includes(error.response.status)) {
state.commit("delUserData");
router.push("/login");
}
return Promise.reject(error);
}
);
return instance.delete(instance.defaults.baseURL + path, params);
}
};
I am working on login feature and have problem when refresh token.
When token expire making request to refresh token, remove the old token, and save the new token to AsyncStorage.
After login successfully have to function A and B. The function A is using the new token to make its request. the function B say that it need to refresh the token so make request to refresh token ( the request make successfully, token being refresh) but The token that request A is using now invalid - I think it happens due to asynchronous
This is my code that use to refresh token:
axiosInstance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (error.response.status === CODE_TOKEN_EXPIRED) {
try {
const token = await authenticationService.getRefreshToken();
const response = await authenticationService.refreshToken(token);
await authenticationService.removeToken();
await authenticationService.storeToken(response.data.params.access_token);
await authenticationService.storeRefreshToken(response.data.params.refresh_token);
error.config.headers.Authorization = 'Bearer ' + response.data.params.access_token;
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.params.access_token;
return axiosInstance(error.config);
} catch (err) {
console.log(2, err);
await authenticationService.removeToken();
navigationService.navigate('LoginForm');
}
}
return Promise.reject(error);
}
);
Anyone know how to handle which asynchronous call for refresh token?
First would be for you to check if you are changing token to the correct axios instance. It is necessary to change Authorization header on error.response config as you did, but also for main axios instance (if you have one) like so: axios.defaults.headers.common["Authorization"] = "Bearer " + access_token;
If it is multiple parallel requests going on that could possibly need to be postponed after token is refreshed issue and answer gets complex, but check this gist with full refresh logic with axios.
I have implemented the same scenario in fetch API. you can also do this same in axios API. Try this to avoid interceptor concept.
Api.ts
export const api = ({ method, url, body, isProtected = true }) => {
return new Promise((resolve, reject) => {
const payload = {
method,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
};
if (body !== null) {
(payload as any).body = JSON.stringify(body);
}
/**
* "isProtected" is used for API call without authToken
*/
if (isProtected) {
AsyncStorage.getItem(ACCESS_TOKEN).then(accessKey => {
(payload.headers as any).Authorization = `Bearer ${accessKey}`;
fetch(url, payload)
.then((response: any) => {
/*
* 419 status denotes the timeout of authToken
*/
if (response.status == 419) {
// refresh token
AsyncStorage.getItem(REFRESH_TOKEN).then(refreshKey => {
const payloadRef = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + refreshKey
}
};
/*
* This call refresh the authToken using refreshing call to renew the authToken
*/
fetch(URL.baseUrl + "/refresh", payloadRef)
.then((response: any) => response.json())
.then(response => {
/*
* if refresh token expired. redirect to login page
*/
if (response.status !== codes.SUCCESS) {
if (!User.sessionOver) {
User.sessionOver = true;
Alert.alert(
'Alert',
'Session Timeout',
[
{
text: 'Get back to Login',
onPress: () => {
// get to Login page
}
}
],
{ cancelable: false }
);
}
} else if (response.status == codes.SUCCESS) {
/*
* If refresh token got refreshed and set it as authToken and retry the api call.
*/
AsyncStorage.setItem(ACCESS_TOKEN, response.payload.access_key).then(() => {
(payload.headers as any).Authorization = 'Bearer ' + response.payload.access_key;
fetch(url, payload)
.then(response => response.json())
.then(response => {
if (response.status == codes.SUCCESS) {
resolve(response);
}
})
.catch(error => {
reject(error);
});
});
}
});
});
} else {
resolve(response.json());
}
})
.catch(error => {
reject(error);
});
});
} else {
fetch(url, payload)
.then((response: any) => {
response = response.json();
resolve(response);
})
.catch(error => {
reject(error);
});
}
});
};
MovieService.ts
import { api } from '../services/api';
import { URL } from '../config/UrlConfig';
const getMovies = () => {
const method = 'GET';
const url = URL.baseUrl + '/v1/top/movies';
const body = null;
const isProtected = true;
return api({ method, url, body, isProtected });
};
export { getMovies };
Maybe it will helps - https://gist.github.com/ModPhoenix/f1070f1696faeae52edf6ee616d0c1eb
import axios from "axios";
import { settings } from "../settings";
import { authAPI } from ".";
const request = axios.create({
baseURL: settings.apiV1,
});
request.interceptors.request.use(
(config) => {
// Get token and add it to header "Authorization"
const token = authAPI.getAccessToken();
if (token) {
config.headers.Authorization = token;
}
return config;
},
(error) => Promise.reject(error)
);
let loop = 0;
let isRefreshing = false;
let subscribers = [];
function subscribeTokenRefresh(cb) {
subscribers.push(cb);
}
function onRrefreshed(token) {
subscribers.map((cb) => cb(token));
}
request.interceptors.response.use(undefined, (err) => {
const {
config,
response: { status },
} = err;
const originalRequest = config;
if (status === 401 && loop < 1) {
loop++;
if (!isRefreshing) {
isRefreshing = true;
authAPI.refreshToken().then((respaonse) => {
const { data } = respaonse;
isRefreshing = false;
onRrefreshed(data.access_token);
authAPI.setAccessToken(data.access_token);
authAPI.setRefreshToken(data.refresh_token);
subscribers = [];
});
}
return new Promise((resolve) => {
subscribeTokenRefresh((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(axios(originalRequest));
});
});
}
return Promise.reject(err);
});
export default request;
so I have setup an interceptor for axios, and I am adding a custom header to my requests. However, when i check in my server code. The header is only received in OPTIONS request.
class ChainInterceptors {
constructor(config) {
this.cleanConfig = { ...config };
}
build() {
return this.cleanConfig;
}
}
export class ReqInterceptor extends ChainInterceptors {
constructor(config) {
super(config);
}
setLog() {
this.cleanConfig.headers['Respond-By'] = // some value;
return this;
}
}
And I am implementing it in the following manner:
export const request = {
init() {
const token = getItem('token');
const request = axios.create({
httpsAgent: new https.Agent({ keepAlive: true }),
baseURL: LOCAL_URL,
headers: {
authorization: `bearer ${token}`,
'Content-Type': 'application/json',
},
});
this.attachInterceptor(request);
return request;
},
attachInterceptor(request) {
request.interceptors.request.use(config => {
return new ReqInterceptor(config).setLog().build();
});
request.interceptors.response.use(
response => {
// response interceptors
},
error => {
// Do something with response error
return Promise.reject(error);
}
);
},
get() {
return this.init().get.apply(null, arguments);
},
// and other methods...
};
As I said above, the Respond-By header is only present in my OPTIONS request. Your guidance is much appreciated.
I use the following setting in the interceptor to pass token and the current language. The current language is the custom header, I need to detect the current language that the user selects from the Client side.
below config.headers["lang"] = ${lang}; is the custom header.
From the server-side, I get it by _httpContextAccessor.HttpContext.Request.Headers["lang"]
export function setupAxios(axios, store) {
axios.interceptors.request.use(
config => {
const {
auth: { authToken },
i18n: { lang }
} = store.getState();
// console.log("language => ", lang);
// console.log("setupAxios => ", store.getState());
if (authToken) {
config.headers.Authorization = `Bearer ${authToken}`;
config.headers["lang"] = `${lang}`;
}
store.dispatch(httpRequest.actions.startRequest(true));
return config;
},
err => {
store.dispatch(httpRequest.actions.endRequest(false));
return Promise.reject(err);
}
);
axios.interceptors.response.use(
response => {
store.dispatch(httpRequest.actions.endRequest(false));
return response;
},
err => {
store.dispatch(httpRequest.actions.endRequest(false));
return Promise.reject(err);
}
);
}
I have this code to add accessToken to all request:
axios.interceptors.request.use(
function(config) {
config.params = config.params || {};
config.params.access_token = localStorage.getItem("access_token");
return config;
},
function(error) {
// Do something with request error
return Promise.reject(error);
}
);
And I have interceptor to check all 401 errors
axios.interceptors.response.use(
function(response) {
return response;
},
function(error) {
if (error.response.status === 401) {
if (error.response.data.error === "invalid_token") {
history.push("/login");
} else if (error.response.data.error === "token_expired") {
//self.props.dispatch(OauthAction.tokenExpired());
}
}
return Promise.reject(error);
}
);
How can I get new access token by refreshToken, when token expired and resend all crash dispatch with new token?
For example, when this request is crash, I need to get new token and my function must again send request for update data on page:
export function getNews() {
return (dispatch) => {
axios.get(Config.DOMAIN + "news").then((response) => {
dispatch(setNews(response.data));
});
};
}