Refresh token is working, but showing error message- React Native - reactjs

I am struggling to find out the issue with my refresh token mechanism.
I have a some problem that I can't fix. Refresh token is working, but it showing an alert 'somthingWentWrong' but the application is still in a good state and working properly.
Here is my code, please tell me where is my mistake?
import axios from 'axios';
import config from './env';
import { Alert } from 'react-native';
import { Constants, storeData } from '#utils';
import { store } from '#data/store';
import { t } from 'i18next';
enum StatusCode {
BadRequest = 400,
Unauthorized = 401,
Fobbiden = 403,
NotFound = 404,
NotAllowed = 405,
Conflict = 409,
ServerError = 500,
}
const getErrorMessage = (error: any): string => {
if (error?.request?.responseURL?.includes('Authentication/signin') && error?.request === StatusCode.Fobbiden) {
return t('invalidEmailOrPassword');
} else if (error?.response?.data) {
return error.response.data;
} else if (error?.response?.data?.length) {
return error.response.data;
} else {
return t('somethingWentWrong');
}
};
export const createAxios = () => {
const baseURL = config.apiUrl;
const token = store.getState()?.auth?.token;
const refreshToken = store.getState()?.auth?.refreshToken;
let headers: any = { 'Content-Type': 'application/json' };
if (token) {
headers = { ...headers, Authorization: `Bearer ${token}` };
}
const instance = axios.create({ baseURL, headers });
instance.interceptors.response.use(
(res) => Promise.resolve(res.data),
async (error) => {
if (error?.request?.status === StatusCode.Unauthorized) {
try {
const body = { token: token, refreshToken: refreshToken };
const _instance = axios.create({ baseURL: config.apiUrl, headers: headers });
const tokenResponse = await _instance.post(config.token_url, body);
if (tokenResponse) {
await store.dispatch({ type: '[AUTH] SET_TOKEN', payload: tokenResponse?.data?.accessToken });
await store.dispatch({ type: '[AUTH] SET_REFRESH_TOKEN', payload: tokenResponse?.data?.refreshToken });
await storeData(Constants.asyncKeys.AUTH_TOKEN, tokenResponse?.data?.accessToken);
await storeData(Constants.asyncKeys.REFRESH_TOKEN, tokenResponse?.data?.refreshToken);
headers.Authorization = 'Bearer ' + tokenResponse?.data?.accessToken;
error.config.headers.Authorization = 'Bearer ' + tokenResponse?.data?.accessToken;
const failedResponse = await axios.request(error.config);
if (failedResponse) {
return failedResponse.data;
}
}
// eslint-disable-next-line no-shadow
} catch (error: any) {
error.request.status && Alert.alert(t('error'), getErrorMessage(error));
}
return Promise.reject(error?.response);
}
return Promise.reject(error?.response);
},
);
return instance;
};

Related

Infinite loop for post and put requests when trying to refresh-token

I'm in an application with NEXTjs and whenever the token expires and you try to make a post request it goes into a loop, the POST request returns 401 then it does the refresh-token returns 200 after trying to make the post and it returns 401 and the loop continues.
The code api.ts where did the refresh-token
import { GetServerSidePropsContext } from 'next'
import axios, { AxiosError } from 'axios'
import { setCookie, parseCookies, destroyCookie } from 'nookies'
import { error as notifyError } from 'helpers/notify/error'
import { STORAGE } from 'constants/storage'
import { logOut } from 'helpers/auth/logOut'
import { ErrorResponse } from 'shared/types'
let isRefreshing = false
let failedRequestsQueue: {
onSuccess(token: string): void
onFailure(err: AxiosError): void
}[] = []
export function baseInstance(
ctx: GetServerSidePropsContext | undefined = undefined
) {
const cookies = parseCookies(ctx)
const token = cookies[STORAGE.TOKEN_KEY]
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL
})
api.defaults.headers.common.Authorization = `Bearer ${token}`
api.interceptors.response.use(
response => response,
(error: AxiosError) => {
const expectedError =
error.response &&
error.response.status >= 400 &&
error.response.status < 500
if (!expectedError) {
notifyError('Encontramos um problema por aqui.')
}
return Promise.reject(error)
}
)
return api
}
export function setupAPIClient(
ctx: GetServerSidePropsContext | undefined = undefined
) {
const cookies = parseCookies(ctx)
const token = cookies[STORAGE.TOKEN_KEY]
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL
})
api.defaults.headers.common.Authorization = `Bearer ${token}`
axios.interceptors.request.use(
config => {
const cookies = parseCookies(ctx)
const token = cookies[STORAGE.TOKEN_KEY]
// eslint-disable-next-line no-param-reassign
config.headers.authorization = `Bearer ${token}`
api.defaults.headers.common.Authorization = `Bearer ${token}`
return config
},
error => {
Promise.reject(error)
}
)
api.interceptors.response.use(
response => response,
(error: AxiosError<ErrorResponse>) => {
const expectedError =
error.response &&
error.response.status >= 400 &&
error.response.status < 500
if (!expectedError && error.response.status !== 401) {
notifyError('Encontramos um problema por aqui.')
}
if (error.response.status === 401) {
if (error.response.data.message === 'Refresh token not found') {
logOut()
}
const cookies = parseCookies(ctx)
const { '#mf_rt_key': refresh_token } = cookies
const originalConfig = error.config
if (!isRefreshing) {
isRefreshing = true
api
.put('/refresh-tokens', {
refresh_token
})
.then(response => {
const { token, refresh_token } = response.data
setCookie(ctx, STORAGE.TOKEN_KEY, token, {
maxAge: 30 * 24 * 60 * 60, // 30 days
path: '/'
})
setCookie(ctx, STORAGE.REFRESH_TOKEN_KEY, refresh_token, {
maxAge: 30 * 24 * 60 * 60, // 30 days
path: '/'
})
api.defaults.headers.common.Authorization = `Bearer ${token}`
failedRequestsQueue.forEach(request => request.onSuccess(token))
failedRequestsQueue = []
})
.catch((error: AxiosError) => {
failedRequestsQueue.forEach(request => request.onFailure(error))
failedRequestsQueue = []
if (process.browser) {
logOut()
}
})
.finally(() => {
isRefreshing = false
})
}
return new Promise((resolve, reject) => {
failedRequestsQueue.push({
onSuccess: (token: string) => {
originalConfig.headers.Authorization = `Bearer ${token}`
resolve(api(originalConfig))
},
onFailure: (error: AxiosError) => {
reject(error)
}
})
})
}
return Promise.reject(error)
}
)
return api
}
I tried everything but nothing works.
I just update the axios version for 1.3.3 and works for me!
change_axios_version

ReactJs how to add interceptor in axios

I've been working on this for hours, and I have no idea where did it go wrong.
I want to have an axios interceptor for my ReactJs
this is my interceptor axiosHandler.js
import axios from "axios";
const axiosHandler = axios.create({
baseURL: process.env.REACT_APP_BASE_URL,
headers: {
Accept: "application/json",
},
});
axiosHandler.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = "Bearer " + token;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
//axiosHandler.interceptors.response
export default axiosHandler;
And here is how I use the handler in my other component
import axiosHandler from "../services/axiosHandler";
const getData = async () => {
await axiosHandler
.get(`/path`)
.then((response) => {
//do something
})
};
And I get an error of below
services_axiosHandler__WEBPACK_IMPORTED_MODULE_0_.get is not a function
I've read many other solutions, but I can't find the difference as how it leads to the error of mine.
Where do I put it wrong?
Thank you
inside axios.index
import axios from "axios";
import { API_URL } from "../config/config";
const axiosHttp = axios.create({
baseURL: `${API_URL}`,
});
axiosHttp.interceptors.request.use(
(config) => {
const token = "Your Token here"
return {
...config,
headers: {
...(token !== null && { Authorization: `${token}` }),
...config.headers,
},
};
},
(error) => {
return Promise.reject(error);
}
);
axiosHttp.interceptors.response.use(
(response) => {
//const url = response.config.url;
//setLocalStorageToken(token);
return response;
},
(error) => {
if (error.response.status === 401) {
//(`unauthorized :)`);
//localStorage.removeItem("persist:root");
//removeLocalStorageToken
//window.location.href = "/login";
}
return Promise.reject(error);
}
);
export default axiosHttp;
Then inside your API function use it like below
import axiosHttp from "./utils/axios";
const getData = async ()=>{
try{
const response = await axiosHttp.get('/path')
return resposne;
}
catch(error){
//handle error here...
}
}
Last but not least, you shouldn't use await when using callback (then/catch)

I miss response .status when HTTP response status 400,

my problem is when i use custom axios aka axiosInstance, i only catch res.status == 200, when response is status 400, i can not take res.status and use it.
My config custom axios:
import axios from "axios";
import { API_AUTH } from "./urlConfig";
import store from "../store";
import { authConstants } from "../store/actions/constants";
const token = localStorage.getItem("token") ? localStorage.getItem("token") : sessionStorage.getItem("token");
const axiosInstance = axios.create({
baseURL: API_AUTH,
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
});
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
(err) => {
const { status } = err.response;
if (status === 400) {
localStorage.clear();
store.dispatch({ type: authConstants.LOGOUT_SUCCESS });
}
return err;
}
);
export default axiosInstance;
and this code when i use axios
import { authConstants } from "./constants";
import { LOGIN } from "../../helpers/urlConfig";
import authAPI from "../../helpers/authAPI";
export const login = (input) => {
const { isRemember, ...inputUser } = input;
return async (dispatch) => {
dispatch({
type: authConstants.LOGIN_REQUEST,
});
const res = await authAPI.post(LOGIN, {
...inputUser,
});
if (res.status === 200) {
//do something OK when status code = 200.
}
if (res.status === 400) {
//can not take res and check res.status
console.log("res", res);
}
};
};
Thanks for all help.
You probably need to handle the error using catch.
Usually I am doing something like this:
const res = await authAPI.post(LOGIN, {
...inputUser
}).catch(error => ({
status: error.response.status,
data: error.response.data
}))
then you should be able to get res.status properly.

Resending a request that was made with an expired token is leading to status pending in developer tools

I have a react application where I am trying to implement JWT.
I am using the axios interceptor where I catch status 401 returned by the server due to expired token, send the refresh token to server, receive the new access token in the client and then resend the original failed request.
The problem I am facing is that, when I resend the original failed request, the status appears as pending forever in the developer tools, network tab. The original failed request is a POST request, when I checked the database it was updated. So why is it showing pending status in the developer tools ?
Here is my axios interceptor code
import axios from 'axios'
// import refreshToken from '../src/Store/refreshToken'
import { store } from '../src/index'
import { removeAuth } from '../src/Store/actions/authAction'
const api = axios.create({
baseURL: process.env.REACT_APP_SERVER
})
function createAxiosResponseInterceptor(axiosInstance) {
axiosInstance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if (token){
config.headers.Authorization = token;
}
return config
}
)
axiosInstance.interceptors.response.use(
response => {
return response;
},
error => {
var errorStatus = error.response.status;
if (errorStatus === 401){ // status 401 is used when token is expired
let cookies = document.cookie
let refresh = cookies.split("refresh=")[1].split(';')[0]
if(!sendRefreshToken(refresh, error)) {
store.dispatch(removeAuth({isLoggedIn: false}));
localStorage.setItem('token', '');
document.cookie = "refresh=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}
}
return error
}
);
}
function sendRefreshToken(refreshToken, error) {
let result = api.post('/refresh', {
refreshToken: refreshToken
})
.then(response => {
if (response.data.success && response.data.message === "new access token set") {
localStorage.setItem('token', response.data.newToken)
api({ // Here I am resending the failed request.
method: error.response.config.method,
url: error.response.config.url,
data: JSON.parse(error.response.config.data)
}).then(response => {
console.log(response)
return true
})
.catch(error => {
console.log(error)
return false
})
}
})
.catch(error => {
console.log(error)
return false
})
return result
}
createAxiosResponseInterceptor(api);
export default api;
Please let me know if you find anything wrong with the code. Let me know if this is the right way to do it. Open to offer more bounty points.
Consider this article for reference.
https://medium.com/swlh/handling-access-and-refresh-tokens-using-axios-interceptors-3970b601a5da
import axios from 'axios'
// import refreshToken from '../src/Store/refreshToken'
import { store } from '../src/index'
import { removeAuth } from '../src/Store/actions/authAction'
const api = axios.create({
baseURL: process.env.REACT_APP_SERVER
})
function createAxiosResponseInterceptor(axiosInstance) {
axiosInstance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if (token){
config.headers.Authorization = token;
}
return config
}
)
axiosInstance.interceptors.response.use(
response => {
return response;
},
error => {
var errorStatus = error.response.status;
const originalRequest = error.config;
if (
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
return api
.post('/refresh', {
refreshToken: getRefreshToken()
})
.then((jsonRefreshResponse) => {
if (jsonRefreshResponse.status === 200) {
// 1) put token to LocalStorage
saveRefreshToken(
jsonRefreshResponse.data.refreshToken
);
// 2) Change Authorization header
const newAccessToken = getJwtToken();
setAuthHeader(newAccessToken);
// 3) return originalRequest object with Axios.
// error.response.config.headers[
// "Authorization"
// ] = `Bearer ${newAccessToken}`;
setAuthHeader(newAccessToken)
return axios(error.response.config);
}
})
.catch((err) => {
console.warn(err);
})
}
if (error.config) {
console.log(error.config);
return Promise.reject();
}
}
);
}
export const setAuthHeader = (token) => {
api.defaults.headers.common["Authorization"] = `Bearer ${token}`;
};
createAxiosResponseInterceptor(api);
export default api;
//These methods could be in separate service class
const getJwtToken=()=> {
return localStorage.getItem("token");
}
const getRefreshToken=() =>{
return localStorage.getItem("refreshToken");
}
const saveJwtToken=(token)=> {
localStorage.removeItem("token");
localStorage.setItem("token", token);
}
const saveRefreshToken=(refreshToken)=> {
localStorage.setItem("refreshToken", refreshToken);
}

How do I create a Generic postToAPI(route, package2send) function in React?

In our project we are using the MERN stack
I want to create a generic function whose input is the path to any api endpoint in our server and the JSON package to POST to the server. I want it to return the JSON sent back from the server.
That way when we are developing our mobile app and web app, we can simply use this function for all of our api endpoint POSTs.
I'm very new to using React/React-Native so I'm sure that I'm not understanding some sort of key concept.
Here is what I have so far:
import React from 'react';
// returns whatever the respective apiEndpoint is suppose to return
function postToAPI(route, package2send)
{
async() =>
{
try
{
const payload = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: package2send
}
const res = await fetch(route, payload);
console.log(res);
const data = await response.json();
console.log(data);
return data;
}
catch(error)
{
console.error(error);
}
}
}
export default postToAPI;
Whenever I call this function from my Login.js after I
import { postToAPI } from './postToAPI'
I get this error: 'TypeError: Object(...) is not a function'
I'm sure there are multiple things wrong with this code, so if someone could steer me in the right direction, it would be greatly appreciated.
If you export the function as default, you must import without bracket like that.
import postToAPI from './postToAPI';
If you would like to write a generic API call class, I advise you this class which I wrote before.
import { BASE_URL } from "../config";
import { Actions } from "react-native-router-flux";
import { deleteUserInfo } from "./SessionHelper";
const API_URL = BASE_URL;
class ApiHelper {
private accessToken?: string;
constructor() {
this.accessToken = undefined;
}
setAccessToken = (accessToken: string) => {
this.accessToken = accessToken;
};
getAccessToken = () => {
return this.accessToken;
};
getRequest = async (endpoint: string) => {
try {
const response = await fetch(`${API_URL}${endpoint}`, {
method: "GET",
headers: {
"x-access-token": `${this.accessToken}`
}
});
const responseJson = await response.json();
return responseJson;
} catch (error) {
console.error(error);
}
};
postRequest = async (endpoint: string, body: any) => {
try {
const response = await fetch(`${API_URL}${endpoint}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-access-token": `${this.accessToken}`
},
body: JSON.stringify(body)
});
const responseJson = await response.json();
const finalResponse = { data: responseJson, status: response.status };
if (response.status === 401) {
deleteUserInfo();
this.accessToken = undefined;
Actions.auth();
}
return finalResponse;
} catch (error) {
console.error(error);
return error;
}
};
patchRequest = async (endpoint: string, body: any) => {
try {
const response = await fetch(`${API_URL}/${endpoint}`, {
method: "PATCH",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-access-token": `${this.accessToken}`
},
body: JSON.stringify(body)
});
const responseJson = await response.json();
const finalResponse = { data: responseJson, status: response.status };
if (response.status === 401) {
deleteUserInfo();
this.accessToken = undefined;
Actions.auth();
}
return finalResponse;
} catch (error) {
console.error(error);
}
};
deleteRequest = async (endpoint: string, body: any) => {
try {
const response = await fetch(`${API_URL}/${endpoint}`, {
method: "DELETE",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"x-access-token": `${this.accessToken}`
},
body: JSON.stringify(body)
});
const responseJson = await response.json();
const finalResponse = { data: responseJson, status: response.status };
if (response.status === 401) {
deleteUserInfo();
this.accessToken = undefined;
Actions.auth();
}
return finalResponse;
} catch (error) {
console.error(error);
}
};
}
export const APIHelper = new ApiHelper();

Resources