ReactJs how to add interceptor in axios - reactjs

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)

Related

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

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;
};

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.

Axios making 2 requests on refresh

When I navigate using Link (react router-dom) I don't have this problem, but if I refresh the browser I get a 403 error in console saying unauthorised and then I get the data in the next request with a 200 response. Why is this making what looks like 2 requests when refreshing the browser?
import { AuthContext } from "../../shared/context/auth-context";
const ContactEntries = () => {
const auth = useContext(AuthContext);
useEffect(() => {
const source = Axios.CancelToken.source();
setIsLoading(true);
const getContactEnquiries = async () => {
try {
const response = await Axios.get(
`${process.env.REACT_APP_BACKEND_URL}/v1/contact`,
{
cancelToken: source.token,
headers: { Authorization: "Bearer " + auth.token },
}
);
if (response.status === 200) {
setIsLoading(false);
setEnquiries(response.data.enquiries);
}
} catch (err) {
setIsLoading(false);
console.log(err.response);
}
};
getContactEnquiries();
return () => {
source.cancel();
};
}, [!!auth.token]);
}
Here is my authContext:
import { createContext } from "react";
export const AuthContext = createContext({
isLoggedIn: false,
userId: null,
token: null,
email: null,
firstName: null,
login: () => {},
logout: () => {},
});
This is because your useEffect is running twice on refresh. On first render it is not getting auth.token and may be it null. And on second render it is making call with 200 status code.
You have to check auth token it coming successfully.
You can check it this way
useEffect(() => {
const source = Axios.CancelToken.source();
setIsLoading(true);
const getContactEnquiries = async () => {
try {
const response = await Axios.get(
`${process.env.REACT_APP_BACKEND_URL}/v1/contact`,
{
cancelToken: source.token,
headers: { Authorization: "Bearer " + auth.token },
}
);
if (response.status === 200) {
setIsLoading(false);
setEnquiries(response.data.enquiries);
}
} catch (err) {
setIsLoading(false);
console.log(err.response);
}
};
if(auth.token) getContactEnquiries();
return () => {
source.cancel();
};
}, [!!auth.token]);

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);
}

Gets 401 error while user tries to do Fetch Get request after authentication

I'm trying to get user details after user looged in but user is getting 401 error even user is looged in with 200 ok.
Explanation of process:
i have logged in user using fetch post request.
stored username,role,staffid to async storage
now i want to list all user (with /api/staff endpoint response throws user firstname and last name )with fetch get request but whenever i make GET request it
throws 401 error.
It will be lifesaver to crack this step for me,thank you!
here is my code
import AsyncStorage from "#react-native-community/async-storage";
import React, { useState, useEffect } from "react";
import { SafeAreaView, Text, StyleSheet, Alert } from "react-native";
import AuthService from "../api/auth-service";
import BASE_URL from "../api/baseUrl";
export default function HomeScreen(props) {
const [firstName, setFirstName] = useState({});
const [lastName, setLastName] = useState({});
const [userValue, setUserValue] = useState({});
useEffect(() => {
let mounted = true;
if (mounted) {
getDataFromStorage();
getUserInfo();
}
return () => {
mounted = false;
};
}, []);
const getDataFromStorage = async () => {
let user = await AsyncStorage.getItem("LoggedInUser");
setUserValue(JSON.parse(user));
};
const getUserInfo=async()=>{
return fetch(BASE_URL+"/api/staff")
.then((response) => {
if(response.ok){
console.log(response);
}else{
console.log(response.status);
}
})
.catch((error) => {
console.log(error);
this.setState({ errorMsg: "Error retreiving data" });
});
}
return (
<SafeAreaView>
<Text>
{"Good morning " + userValue.username + " "}
{"you role is " + userValue.role +"your staff id is " + userValue.staffId+" " + "your first name is "+ firstName +"this is your last name"+lastName}
</Text>
</SafeAreaView>
);
}
authservice.js
import AsyncStorage from "#react-native-community/async-storage";
import BASE_URL from "./baseUrl";
class AuthService {
login(Username, Password, role) {
console.log(Username, role);
return fetch(BASE_URL + "/api/authentication/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
Username,
Password,
}),
}).then((res) => {
if (res.ok) {
console.log("the login response", res);
return res.json();
} else {
alert("Invalid Username or Password");
window.stop();
}
});
}
logout() {
AsyncStorage.getAllKeys().then((keys) => AsyncStorage.multiRemove(keys));
}
}
export default new AuthService();
login.js
const submitData = async () => {
AuthService.login(Username, Password).then(
(data) => {
console.log(JSON.stringify(data));
AsyncStorage.setItem("LoggedInUser", JSON.stringify(data));
if (data.role == "Admin") {
console.log(data.username);
navigation.navigate("adminPage");
} else {
navigation.navigate("staffpage");
}
},
(error) => {
Alert.alert(error);
}
);
};
According to developer.mozilla.org
The HTTP 401 Unauthorized client error status response code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource.
It's seems the user doesn't have right to access the API. Make sure that the getUserInfo() API, /api/staff, don't need any authentication token in header of your HTTP request.
I have a feeling that you may need to resolve one more promise in AuthService.login.
res.json() is actually a promise which needs to be resolved as well, so you may need one more then block like so:
return fetch(BASE_URL + "/api/authentication/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
Username,
Password,
}),
}).then((res) => {
if (res.ok) {
console.log("the login response", res);
return res.json();
} else {
alert("Invalid Username or Password");
window.stop();
}
}).then(finalData=>finalData )// <---------- add this
.catch(err=> err)
It happens because getDataFromStorage is an async function so you have to resolve it first. Also in login.js we have to await before navigate to make sure that the data is saved in AsyncStorage. Please update the following part of your code:
login.js
const submitData = async () => {
AuthService.login(Username, Password).then(
async (data) => {
console.log(JSON.stringify(data));
await AsyncStorage.setItem("LoggedInUser", JSON.stringify(data));
if (data.role == "Admin") {
console.log(data.username);
navigation.navigate("adminPage");
} else {
navigation.navigate("staffpage");
}
},
(error) => {
Alert.alert(error);
}
);
};
next, initialize userValue with null
const [userValue, setUserValue] = useState(null);
Now have 2 useEffect,
useEffect(() => {
let mounted = true; //Why this required as it doesn't mean anything
if (!userValue) {
getDataFromStorage();
}
return () => {
mounted = false;
};
}, []);
useEffect(()=>{
if(userValue){
getUserInfo()
}
},[userValue])

Resources