React Redux handling response after axios resubmit original request - reactjs

I have a small booking program to practice ReactJS + Redux + ReduxSaga + Axios + Axios Interceptor + JWT authentication
Here is the code in the component BookingDialog after the submit button click
bookingDialog.js
const handleSubmit = (event) => {
event.preventDefault();
let payload = {
selectedDate: selectedDate,
carId : carDetail.id,
userId : user.login.id, //or pass by jwt accesstoken
remarks: remarks
}
console.log(payload);
dispatch(createBooking(payload));
}
And there is saga watcher which take latest of action createBooking to function handleCreateBooking
bookingSaga.js
export function* handleCreateBooking(action) {
try {
const response = yield call(createBooking, action.payload);
const { data } = response;
console.log("handleCreateBooking");
console.log(response);
if (data && data.result && data.result > 0){
console.log("booked successfully");
yield put(setMessageBarOpen({type: "success", content: "booked successfully"}));
yield put(setCreateBookingOpen(false));
}
else{
console.log("booked failed");
//yield put(setMessageBarOpen({type: "error", content: "booked failed"}));
//yield put(setCreateBookingOpen(false));
}
} catch (error) {
console.log(error);
}
}
bookingRequest.js
const createBooking = (payload) => {
return postUrl(apiURL.createBooking.url, payload).then((res) => {
return res
});
}
The program works as expected. Success message shown and booking dialog closed after submission.
If the jwt is expired, the program will retrieve the access token by refresh token and resubmit the original request with the new access token.
The problem is that, after the original request is sent and booking is created successfully, the follow up actions (setMessageBarOpen & setCreateBookingOpen) are not performed as the posting of original request is not under the function handleCreateBooking in bookingSaga.js
axiosInstance.js
import axios from 'axios';
import apiURL from "requests/apiURL";
const ax = axios.create();
ax.interceptors.request.use(
request => {
const accessToken = JSON.parse(localStorage.getItem('token')) && JSON.parse(localStorage.getItem('token')).accessToken;
if (accessToken) {
let auth = false;
for (const [key, value] of Object.entries(apiURL)) {
if (request.url.includes(value.url)) {
auth = value.auth;
break;
}
}
if (auth) {
request.headers.authorization = `Bearer ${accessToken}`;
}
}
return request;
},
error => {
return Promise.reject(error);
}
);
const sendRefreshToken = (refreshToken) => {
return new Promise((resolve, reject) => {
console.log("refreshToken");
postUrl(apiURL.token.url, { token: refreshToken })
.then((res) => {
console.log(res);
if (res.data) {
console.log(res.data);
localStorage.setItem('token', JSON.stringify({accessToken: res.data.accessToken, refreshToken: refreshToken}));
resolve(res);
}
})
.catch(error => {
reject(error);
});
})
}
ax.interceptors.response.use(
(response) => {
return response;
},
error => {
console.log("axios.interceptors.response");
console.log(error);
const status = error.response ? error.response.status : null;
const originalRequest = error.config;
let isRefreshing = false;
if (status === 403) {
if (!isRefreshing) {
const refreshToken = JSON.parse(localStorage.getItem('token')) && JSON.parse(localStorage.getItem('token')).refreshToken;
console.log("403, refreshToken:");
console.log(refreshToken);
isRefreshing = true;
sendRefreshToken(refreshToken)
.then(({ status }) => {
console.log(status);
if (status === 200 || status === 204) {
isRefreshing = false;
console.log("start resendRequest");
console.log(originalRequest);
return ax(originalRequest);
}
})
.catch(error => {
console.error(error);
});
}
}
return error;
}
);
export const getUrl = async (url, opt) => {
const response = await ax.get(url, opt);
return response;
}
export const postUrl = async (url, data, opt) => {
const axios_res = await ax.post(url, data, opt);
return axios_res;
}
How should I handle the response from the resubmitted original request?
Thanks.

Related

async function in react component isn't working when triggered from the axios request

network.services.js
axiosCall = (axiosURL) => {
// const axiosURL = "https://api.github.com/user"
axios.get(axiosURL, {
headers: {
'Authorization': `qwdvryjutmnevw`,
}
}).then((res) => {
console.log(res.data);
return res.data;
}).catch((error) => {
throw error.message;
// console.error(error);
// toast.error(error.message);
})
}
component.js
const getData = async () => {
const asyncExample = async () => {
const result = await networkServices.axiosCall("/api/v1/calendars");
const responseData = await result;
console.log(responseData);
return responseData;
}
const data = asyncExample()
data.then(function(result) {
console.log(result); // "Some User token"
})
}
Trying to get data from service to my component in const result, console form service is consoling data but component is always returning undefined instead of data from the service file. SetTimeout function is also not working in component.
You have many mistakes. I advise you to take a look at documentation about Promises
First one:
You don't return data in axiosCall
A way to return data:
axiosCall = (axiosURL) => new Promise((resolve, reject) => {
axios.get(axiosURL, {
headers: {
'Authorization': `yourTokenHere`,
}
}).then((res) => {
// return a response data
resolve(res.data);
}).catch((error) => {
// return only error message
reject(error.message);
})
})
to use axiosCall:
try {
// don't forgot to configure axios with base url
const data = await axiosCall('/api/v1/calendars');
// do something with your data
} catch (e) {
// do something with error message
console.log(e);
}
Second:
Your make mistakes when call async function
Look at this example:
const getData = () => {
networkServices
.axiosCall("/api/v1/calendars")
.then(function(result) {
// when promise resolve
console.log(result);
})
.catch(error => {
// when promise reject
console.log(error)
})
}

Decoding Firebase ID token failed

I am trying to restrict my API to only logged users. To do so I am sending firebase token and trying to verify it on server-side exactly how Google says to do.
However, I get an exception like this:
FirebaseAuthError: Decoding Firebase ID token failed. Make sure you passed the entire string JWT which represents an ID token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
Printing out those tokens on client-side as well as on server-side proves that on both sides they are identical.
Do I miss something?
My code:
const checkAuth = (token) => {
if (token === undefined) return false;
console.log(token);
admin
.auth()
.verifyIdToken(token)
.then((decodedToken) => {
const uid = decodedToken.uid;
admin
.auth()
.getUser(uid)
.then(() => {
return true;
})
.catch(() => {
return false;
});
})
.catch((error) => console.log(error));
};
// example usage
app.get("/fileslist/:id", async function (req, res) {
const authorized = checkAuth(req.headers.token);
if (authorized) {
const directoryPath = path.join(__dirname, "uploads/" + req.params.id);
fs.readdir(directoryPath, function (err, files) {
var array = [];
if (err) {
return console.log("Unable to scan directory: " + err);
}
files.forEach(function (file) {
array.push(file);
});
res.status(200).send(array);
});
} else {
res.status(403).send();
}
});
And client-side:
//getting token
const getToken = () => {
currentUser
.getIdToken(true)
.then(function (idToken) {
setToken(idToken);
})
.catch((error) => {
console.log(error);
});
};
//calling an api
const getFilesById = async (id, token) => {
console.log(token);
return await client.get(
"/fileslist/" + id,
{},
{ headers: { token: token } }
);
};
//api definition
import { create } from "apisauce";
const api = create({
baseURL: "http://localhost:3005",
});
export default api;
You need to return the entire Promises chain in the checkAuth() function, as follows:
const checkAuth = (token) => {
if (token === undefined) return false;
console.log(token);
return admin // <== see return
.auth()
.verifyIdToken(token)
.then((decodedToken) => {
const uid = decodedToken.uid;
return admin // <== see return
.auth()
.getUser(uid)
.then(() => {
return true;
})
.catch(() => {
return false;
});
})
.catch((error) => console.log(error));
};
You could actually have only one catch block, as follows:
const checkAuth = (token) => {
if (token === undefined) return false;
console.log(token);
return admin // <== see return
.auth()
.verifyIdToken(token)
.then((decodedToken) => {
const uid = decodedToken.uid;
return admin // <== see return
.auth()
.getUser(uid);
})
.then(() => {
return true;
})
.catch((error) => {
console.log(error);
return false;
});
};

Axios PUT Data with Params

My backend API route is /api/updateUser/:id
How am I supposed to POST data into this API? I'm familiar with POST request for non params APIs but this one has an /:id in the route.
Can someone show me an example with this demo code
state = {
username: "random123",
password: "random123",
userid: "qwertyuiop",
};
saveDetails = async () => {
const { username, password, userid } = this.state;
let data = new FormData();
data.append('username',username);
data.append('password',password);
axios
.put(apiEndPoint+'?id='+this.state.userid, data) //this is where I need help
.then(async (response) => {
if (response.data) {
console.log("success");
} else {
console.log("issue");
}
})
.catch((err) => {
console.log("error",err);
});
};
This is the working example for Path Parameter Axios PUT request -
saveDetails = async () => {
const { username, password, userid } = this.state;
axios
.put(apiEndPoint+"updateUser/"+userid, {
username:username,
password:password,
})
.then(async (response) => {
if (response.data) {
console.log("done");
} else {
console.log("error");
}
})
.catch((err) => {
console.log("error",err);
});
};

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 to implement the refresh token in react-redux

The following code I did allows to refresh the access token, when on the first request the response return an Unauthorized, run the service to refresh the access token.
The code works fine, the refresh token service runs on the backend made in Java.
The code is incomplete, there is only the call to the refresh token method.
On the following shown the code.
const apiMiddleware = ({ dispatch }) => (next) => (action) => {
const result = next(action);
if (action.type !== API) {
return result;
}
const {
url,
method,
data,
onSuccess,
types,
} = action.payload;
const axiosInstance = createAxios();
const refreshToken = async(originalRequest) => {
const axiosInstance = createAxios();
const data = authUtil.getUserLoggedIn();
const response = await axiosInstance({
url: `${AUTH_ENDPOINT}/refresh-token`,
method: 'PUT',
data: { token: data.refreshToken },
});
if (response.status === 200) {
authUtil.setUserLoggedIn(response.data);
originalRequest.headers['Authorization'] = response.data.accessToken;
axiosInstance(originalRequest)
.then(({ data }) => {
handlerSuccess({ data });
return result;
});
} else {
dispatch(apiError(types[1], response.error));
}
};
const handlerSuccess = ({ data }) => {
dispatch(onSuccess(data));
};
axiosInstance({
method,
url,
data,
})
.then(({ data }) => {
handlerSuccess({ data });
return result;
})
.catch((error) => {
if (error.response && error.response.status === 403) {
dispatch(accessDenied(types[2], window.location.pathname));
} else if (error.response && error.response.status === 401) {
refreshToken(error.config);
}
return result;
})
};
export default apiMiddleware;
Somebody can help me to improve this code

Resources