Stop Axios error propagation in TanStack Query - reactjs

I have app with access + refresh tokens auth. So I wrote interceptor for axios that refresh token if it has expired.
export const createAuthResponseErrorInterceptor = ({
onRefreshSuccess,
onRefreshError,
}: AuthResponseErrorInterceptorArgs) => {
return async (error: AxiosError<any, any>) => {
const prevRequest = error?.config
if (
error?.response?.status === HttpStatusCode.Unauthorized &&
prevRequest
) {
try {
const newAccessToken: string = await axiosPrivatePure.post(
getRefreshEnpointPath(),
)
onRefreshSuccess(newAccessToken)
;(prevRequest.headers as AxiosHeaders).set(
AUTHORIZATION,
`${BEARER} ${newAccessToken}`,
)
return axiosPrivatePure(prevRequest)
} catch (err) {
onRefreshError()
}
}
return Promise.reject(error?.response?.data?.message || error.message)
}
}
This one works good:
error response -> refresh token -> success reponse
But the problem is that query or mutation still in error state, although server responsed successfully. How can I prevent handling 401 errors in react query?

Related

Redux toolkit handling axios undefined response

I'm using redux toolkit and im sending request to Api.
In the example below, we try to find user by some shortId, if there is no user found, server respond with 404 status code.
Server side:
class Controller {
...
public sendInvitationHandler = async (
req,res
) => {
try {
const senderId = res.locals.user.id;
const { username, shortId } = req.body;
const user = await this.userService.findUserByShortId({
shortId,
username,
});
//I want this message in redux error message
if (!user) return res.status(404).send('No user found!')
const response = await this.friendService.sendInvitation({
senderId: senderId ,
receiverId: receiverId,
});
res.send(response);
} catch (error) {
res.status(400).send(error);
}
};
User service function:
...
public findUserByShortId=async({shortId,username})=>{
const user= await this.users.findOne(
//some logic
)
return user
}
Client side :
...
export const sendInvitationHandler = createAsyncThunk(
"friends/sendInvitation",
async ({ username, shortId }, thunkApi) => {
try {
if(!username || !shortId ) return
const response= await friendService.sendInvitation({ username, shortId });
if(!response) throw new Error()
return response
} catch (error: any) {
const message =
error?.response?.data?.message || error.message || error.toString();
return thunkApi.rejectWithValue(message);
}
});
I cant figure out how to pass an error from backend to redux state.In reducer response is undefined because we dont find any user. If i dont throw any error when there is no response, then reducer is fullfiled because it is not an error to redux.
I get error message:
'Cannot read properties of undefined (reading 'data')'

How to handle socket.io authentication errors with nodejs server and reactjs?

We are trying to display an error when the connection has failed during authentication (wrong JWT token).
For that, we have a middleware on the server that verifies the validity of the token and is expected to return an error if the token is invalid :
io.use((socket, next) =>
{
jwt.verify(socket.handshake?.query?.refreshToken, process.env.REF_JWT_SEC, (err, decoded) =>
{
if(decoded?._id && !err)
{
socket.decoded = decoded._id;
return next();
}
else
{
let err = new Error('authentication_error')
err.data = { content : 'refreshToken error!' };
return next(err);
}
});
});
On the client we have multiple socket.on() instances :
const refreshToken = localStorage.getItem('refresh_token');
const socket = io(
"http://localhost:4000",
{
query: { refreshToken },
},
);
...
export const Chat = () => {
...
useEffect(() => {
socket.on("connect_error", err => {
console.log(err); // <-- error we're trying to log
});
socket.on("error", err => {
console.log(err); // <-- error we're trying to log
});
socket.on("message", ({id, text, sender}) => {
console.log('# socket io res :', id, text, sender)
})
return () => {
socket.disconnect()
}
}, []);
...
}
While trying to trigger the error by sending an incorrect token ("ThisIsSomeIncorrectToken"), as expected, sending/recieving messages doesn't work. However, no error is being logged.
In the client's console we can see the requests to socket.io:
Note that somehow the error shows up in the client logs when I save my reactjs code, but not if I refresh completely the browser or re-render the component.

Axios - Refresh token loop

so im rather new to axios and context but I have an Auth context that is provided at App level and this context is used by multiple child components. Within this context I have an axios interceptor that checks requests for 401 (unauthorized) and then calls the refresh token api and replaces the token with a new one. My only concern is that the second time the refresh token API is called it goes into an endless loop of calling the refresh token api? Any ideas what im doing wrong? Any help would be greatly appreciated.
AuthContext.js
axios.interceptors.response.use((response) => {
return response
}, function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && originalRequest.url ===
`${BASE_URI}/Identity/Login`) {
history.push('/login');
return Promise.reject(error);
}
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const localStorage = JSON.parse(sessionStorage.getItem(AUTH_USER))
const refreshToken = localStorage.refreshToken;
return axios.post(`${BASE_URI}/Identity/Refresh`, null,
{
headers: {
'Refresh-Token': refreshToken
}
})
.then(res => {
if (res.status === 201 || res.status === 200) {
console.log("In refresh request !")
console.log(res)
setSession(null, res.data.token, res.data.refreshToken)
axios.defaults.headers.common['authorization'] = 'Bearer ' + res.data.token;
return axios(originalRequest);
}
}).catch((error) => {
console.log("Inside error refresh")
return Promise.reject(error);
})
}
return Promise.reject(error);
});
I have done something similar to get a refresh token when the token expires and I have encountered the same problem, actually, you are using the same instance of Axios, create another instance
const instance = axios.create();
axios.interceptors.request.use(async (config) => {
if (token && refreshToken) {
const data = JSON.parse(atob(token.split('.')[1]));
const time = Math.floor(new Date().getTime() / 1000);
if (data.exp < time) {
instance.defaults.headers.common["Authorization"] = `Bearer ${refreshToken}`;
const { data } = await instance.get(SERVER.API_ROOT + '/tokens/refresh');
if (data?.AccessToken) localStorage.setItem(config.AUTH_TOKEN, data.AccessToken)
else localStorage.clear();
}
return config;
}
Hope the above example will help you
#J.Naude I have done he similar thing but a generic wrapper around axios which i wrote for one of my project that handles almost all the edge cases
https://gist.github.com/tapandave/01960228516dd852a49c74d16c0fddb1
Hey I know this is an old question, but it seems that your problem was using the same axios instance to request a refresh token, essentially creating a nested refresh-token cycle. What you could do is create a new axios instance (alongside with the initial instance, you would use them both) without an interceptor like this: const noInterceptAxios = axios.create();, and then later use it to send requests where you don't need to check the access token, return noInterceptAxios.post(`/Identity/Refresh).then().catch().

How to pause and restart API calls in react-redux app while access token is being refreshed?

We have a react-redux app that fetches data using multiple API calls with every page load. The app follows the OAuth2 protocol. It has an access token that expires frequently and a refresh token to be used to get a new access token.
If an API call is made with an expired access token, a 401 error is received with error message "API token is expired." Then we need to get a new token from the auth server.
My problem is this:
When a page loads, say 8 API calls were dispatched. We receive 3 successful 200s but from the 4th response onwards, we receive 401 "API token is expired." At that point, I want to put all API calls that I have already made but didn't receive a response or received 401 error in a queue until I refresh the access token. After the access token is successfully refreshed, I want to re-do the API calls saved in the queue. How can I achieve this?
Looking for this online, I found that redux-saga might work, but didn't see any indication that it can be used for this use case.
I also used to handle this case. This is my solution:
/**
* Connect to API
*/
export const makeRequest = (args) => {
const request = fetch(args)//example
return _retryRequestIfExpired(request, args)
}
/**
* Fake get access token.
*/
const _getNewAccessToken = () => {
return new Promise((resolve, reject) => {
resolve('xyz')
})
}
const _retryRequestIfExpired = (request, args) => {
return request.catch(error => {
if (error === 'abc') {//Any reason you want
return _refreshAccessToken()
.then(newAccessToken => {
const updateArgs = {
...args,
headers: {
'Authorization': newAccessToken
}
}
//Retry with new access token
return makeRequest(updateArgs)
})
}
throw error
})
}
/**
* Important
*/
let _isRefreshingToken = false
let _subscribers = []
const _subscribe = subscriber => {
if (typeof subscriber !== 'function' || _subscribers.indexOf(subscriber) !== -1) {
return false
}
_subscribers = [].concat(_subscribers, [subscriber])
}
const _broadcast = (error = null, data) => {
_isRefreshingToken = false
_subscribers.forEach(subscriber => {
subscriber(error, data)
})
_subscribers = []
}
const _refreshAccessToken = () => {
if (_isRefreshingToken) {//If a request is creating new access token
return new Promise((resolve, reject) => {
const subscriber = (error, accessToken) => {
if (error) {
return reject(error)
}
return resolve(accessToken)
}
_subscribe(subscriber)
})
}
_isRefreshingToken = true
return _getNewAccessToken()
.then(accessToken => {
_broadcast(null, accessToken)
return accessToken
})
.catch(error => {
_broadcast(error)
throw error
})
}
/**
* End Important
*/
In this way, only the first request will actually create a new access token and remaining requests will temporarily be stopped until a new access token is created.

Firebase: Could not parse auth token

First-time Stack Overflow poster here!
I’m following an Angular 4 tutorial, and completing its authentication section with Firebase (link: ). I was able to successfully signup and login, but receive errors when passing the user’ token, via ‘getIdToken’, to my GET request to limit certain actions to authenticated users.
When passing my token to my GET request, I get the following error:
* Response {_body: "{↵ "error" : "Could not parse auth token."↵}↵", status: 401, ok: false, statusText: "Unauthorized", headers: Headers…}
I also experience this issue when copying and pasting tokens from the console into code
I’ve posted the (potentially) most relevant code below for debugging:
header.component.ts
onFetchData() {
this.dataStorageService.getRecipes();
}
data-storage.service.ts
getRecipes() {
const token = this.auth.getTokenGrabber()
this.http.get('https://recipes-fe1ba.firebaseio.com/.json?auth=' + token)
.map(
(response: Response) => {
console.log(response.json())
const recipes: Recipe[] = response.json();
for (let recipe of recipes) {
if (!recipe['ingredients']) {
recipe['ingredients'] = [];
}
}
return recipes;
}
)
.subscribe(
(recipes: Recipe[]) => {
this.recipeService.setRecipes(recipes);
}
);
}
authentication.service.ts
signinUser(email, password){
firebase.auth().signInWithEmailAndPassword(email, password)
.then(
(response) => {
firebase.auth().currentUser.getIdToken(
).then(
(token:string) => {
// console.log(token)
this.token = token
console.log('user was found')
// console.log(this.token)
}
)
}
).catch((erorr) => {
console.log('user was not found')
}
)
}
getTokenGrabber(){
firebase.auth().currentUser.getIdToken()
.then(
(token:string) =>
this.token = token
)
return this.token
}
}
The REST API documentation indicates the query parameter is access_token, not auth. Give this a try:
this.http.get('https://recipes-fe1ba.firebaseio.com/.json?access_token=' + token)

Resources