I am working on a project in react with redux/redux-saga and a doubt arose. I am implementing with Axios in the response interceptor a way to logout the user when the session token has expired.
Basically, what I'm looking for is, to logout the user when calling to a private endpoint and it returns a 403. But I have this problem:
I have routes where I must do 3 dispatches (calls to different endpoints on the API) during the component loads which all 3 bring me relevant information to the components. Obviously, when the token is expired it will return 403, and the interceptor in the response will do the logout process to remove it from the session. However, even after doing the logout, the other 2 requests will also be called and there is no need cuz I already detected in the first call that the token expired.
// EFFECTS
useEffect(() => {
dispatch(getAccountsInit("users"));
dispatch(getAccountsInit("kash"));
dispatch(getBanksInit());
dispatch(getCurenciesInit());
}, [dispatch]);
How do I prevent this? How do I cancel subsequent requests when detecting that the token has expired on the first one? .. I was looking for information about it but I couldn't find it. I thank you very much for the help.
Here is my response inteceptor
export const resInterceptor = (instance) =>
instance.interceptors.response.use(
(res) => res,
(error) => {
const configRequest = error.config,
status = error.status || error.response.status;
console.warn("Error status: ", status || error.code);
console.log(error);
if (status === 418 && !configRequest._retry) {
alert("Ha finalizado tu sesión, serás re dirigido y deberás iniciar sesión nuevamente.");
store.dispatch(logoutSuccess());
}
I assume action creators like getAccountsInit are async thunks. In this meaning, you actually send 4 requests in parallel and once any of it gets 403, there is no use try stopping others. This way you can only try preventing of interceptor to call logoutSuccess() but is it really needed? I doubt.
You can refer to store inside interceptor in order to get current "logged in or not" status, but to me it seems as unnecessary complication.
You also can return Promise from your getAccountsInit, getAccountsInit etc and chain them:
useEffect(() => {
dispatch(getAccountsInit("users"))
.then(() => dispatch(getAccountsInit("kash")))
.then(() => dispatch(getBanksInit()))
.then(() => dispatch(getCurenciesInit()));
}, [dispatch]);
But this way for "normal" flow(session has not expired) user will get ~4x longer loading(instead of running in parallel requests go in sequence).
TL;DR; just let it sending requests even if they may be useless due to session expiration; code complexity or slower loading is not worth it
Related
I've had csrf protection with the csurf module working for a while now on my React SPA. I am also using passport for authentication. I do not do any server-side rendering, so the server sends a csrf token in the response body to the client when it hits the /users/current endpoint, which is protected with csrfProtection, something like this:
import csrf from 'csurf';
const csrfProtection = csrf();
router.get("users/current", csrfProtection, async function(req, res)
{
.....
res.write(JSON.stringify({ ..., csrfToken: req.csrfToken() }));
res.end();
}
On the client side I then add the token to all subsequent request headers, a bit like this:
axiosInstance.get("/users/current")
.then(resJson =>
{
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = resJson.data.csrfToken;
return config;
});
}
My first question is how the first request even manages to pass the csrfProtection without a token in its header. Yet since the token can only be accessed on the server to send to the client if the route is csrf protected, I don't see a way around this, and it does work somehow.
However, recently I have been getting "ForbiddenError: invalid csrf token" when a user logs in or deletes their account. This has only started happening after I upgraded all my node packages to the latest versions. First the client makes a request to /users/login to submit the username & password, and then makes a request to /users/current to get the new csrf token:
axiosInstance.post("/users/login", {
"username": login.username,
"password": login.password
})
.then(async resJson =>
{
// *code to update user details in redux store*
// ......
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = undefined;
return config;
});
return resJson;
})
.then(async resJson =>
{
const { csrfToken } = await axiosInstance.get("/users/current")
.then(resJson => resJson.data);
axiosInstance.interceptors.request.use(config =>
{
config.headers["x-csrf-token"] = csrfToken;
return config;
});
return resJson.data;
}
I suspect it's something to do with subsequent requests coming from a different userId (which I obtain from req.user[0].userId), with which csurf will not accept the previously issued token. But I have no idea how to issue the new token csurf does expect, to the client. And it still doesn't explain why what I had before has suddenly stopped working since none of my logic has changed. This isn't the kind of error I'd typically expect after package updates.
Here someone mentions you can just set any header on the client and have the server check for that. atm I am adding the csrf token to all the client's request headers and using the csurf module's request handler function to check it, but there is nothing stopping me from writing my own. If this is true, the value of the header doesn't even matter, just that it exists. I am holding off on this option though because I feel there is something basic I'm not understanding about my current setup, which once rectified will mean this can be easily fixed.
Would appreciate any help or explanation! Thanks 🤍
Premise / What you want to achieve
React x Redux (port: 3000)
Go (port: 8080)
I am making a SPA.
I run into a CROS error when hitting the Go API.
I've encountered this problem many times, and every time I think it's solved, I hit a new API.
I should have made the basic settings, but I'm in trouble because I don't know what caused it.
We would appreciate it if you could help us.
Problem / Error message
Access to XMLHttpRequest at'http://localhost:8080/login' from origin'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No'Access-Control -Allow-Origin'header is present on the requested resource.
I encountered this when I hit the login API (post).
However, when I encountered this problem several times, I set cros on the header of api and axios side, and
Another get API avoided the error.
Also, when you hit api with postman, it becomes header
We have also confirmed that the header set in Go such as Allow-Origin is given without any problem.
Applicable source code
Header settings in Go
w.Header (). Set ("Content-Type", "application /json")
w.Header (). Set ("Access-Control-Allow-Origin", "http://localhost:3000")
w.Header (). Set ("Access-Control-Allow-Credentials", "true")
react axios settings
axios.defaults.baseURL ='http://localhost:8080';
axios.defaults.headers.post ['Content-Type'] ='application/json';
Posting code with an error
export const signIn = (email, password) => {
return async (dispatch) => {
try {
const response = await axios.post ('/login', {
email: email,
password: password,
});
const data = response.data;
dispatch (
signInAction ({
isSignedIn: true,
})
);
} catch (error) {
console.log (error);
}
};
};
Code hitting a successful getapi
useEffect (() => {
async function fetchTickers () {
try {
const response = await axios.get (`/ticker?Symbol=^skew`);
const data = response.data;
setChartAry ([... chartAry, [... data.daily]]);
} catch (error) {
console.log (error);
setChartAry ([]);
}
}
fetchTickers ();
}, []);
What I tried
I tried all the solutions that hit with stackoverflow etc. Also, considering the possibility of a problem with the browser itself, we also cleared the cache.
Is it the difference between axios by get and post? And how should I debug it?
I had this problem some time ago but I used Express for the backend, who knows this can solve your problem too.
try adding this to the axios settings axios.defaults.withCredentials = true;
You also need to allow the OPTIONS method for preflight requests
this article might help you solve the CORS problem on the backend: https://flaviocopes.com/golang-enable-cors/
The method was validated in gorilla / mux.
- r.HandleFunc("/login", app.loginHandler).Methods("POST")
+ r.HandleFunc("/login", app.loginHandler).Methods("POST", "OPTIONS")
We also had to deal with preflight.
if r.Method == "OPTIONS" {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.WriteHeader(http.StatusOK)
return
}
Our site currently has a filter feature that fetches new data via axios depending on what is being filtered.
The issue is that the filter is done on real time and every change made via react causes an axios request.
Is there a way to put a timeout on the axios request so that I only fetch the last state?
I would suggest using debounce in this case to trigger API call after a specified millisecond of user input.
But just in case you need to add a timeout during axios call, this can be achieved like -
instance.get('/longRequest', {
timeout: 5000
});
The problem has two parts.
The first part is debouncing and is default for event listeners that can be triggered often, especially if their calls are expensive or may cause undesirable effects. HTTP requests fall into this category.
The second part is that if debounce delay is less than HTTP request duration (this is true for virtual every case), there still will be competing requests, responses will result in state changes over time, and not necessarily in correct order.
The first part is addressed with debounce function to reduce the number of competing requests, the second part uses Axios cancellation API to cancel incomplete requests when there's a new one, e.g.:
onChange = e => {
this.fetchData(e.target.value);
};
fetchData = debounce(query => {
if (this._fetchDataCancellation) {
this._fetchDataCancellation.cancel();
}
this._fetchDataCancellation = CancelToken.source();
axios.get(url, {
cancelToken: this._fetchDataCancellation.token
})
.then(({ data }) => {
this.setState({ data });
})
.catch(err => {
// request was cancelled, not a real error
if (axios.isCancel(err))
return;
console.error(err);
});
}, 200);
Here is a demo.
From this axios issue (Thanks to zhuyifan2013 for giving the solution), I've found that axios timeout is response timeout not connection timeout.
Please check this answer
You can also use as a general setting by axios.defaults for all requests:
axios.defaults.timeout = 5000
I am developing a react js application and we are using a promise based library axios for calling APIs.
Now, in the initial part of application, user gets a login page, when the login is successful, we contact different systems to retrieve some extra information about user.
axios
.get('url to authentication endpoint') // 1st call
.then(response => {
// if login is successful
// 1. retrieve the user preferences like, on the customised screens what fields user wanted to see
axios.get('user preference endpoint') // 2nd call
// 2. send a request to one more external systems, which calculates what user can see and not based on LDAP role
axios.get('role calculation endpoint') // 3rd call
})
.catch(error => {
})
Now I can see that I can use
axios.all()
for second and third call, but with promised based client, how to chain first and second call? To retrieve user preferences, I have to wait for user to be authenticated.
How to chain this calls in a promise based way, rather than callback style?
as mentioned in the thread for this Github issue, axios() and axios.all() return Promise objects which can be chained however you see fit:
axios.get('/auth')
.then(function(response) {
return axios.all([ axios.get('/preferences'), axios.get('/roles') ]);
})
.then(function(responses) {
const [
preferencesResponse,
rolesResponse
] = responses;
// do more things
})
.catch(function(error) {
console.log(error);
});
Dan O's answer is very good and it works perfectly but it's much readable using async/await although it's also working with promises under the hoood
async yourReactClassFunction(){
try{
let getAuth = await axios.get('/auth');
//if login not successful return;
let result = await Promise.all([axios.get('/preferences'), axios.get('/roles')]);
//Do whatever with the results.
}catch(e){
//TODO error handling
}
}
Although it's the same thing, 'feels' more readable in my very subjective opinion
I am coding a SPA in react.js and I am using redux-api to handle backend connection. I want to do a sync action to refresh the auth token before doing the main action; this way, every time I will do an action to the backend I will be sure that the token is valid.
const endpoints = {
{
url: '/some/url',
crud:true,
prefetch:[
({actions, dispatch, getState}, cb) =>{
actions.auth_token.post(JSON.stringify({
token: "my token",
refreshToken: "my_refresh_token"
}),null, (err, data) =>{
if(err){
// HANDLE ERROR
}
setToken(data)
})
}
]
}
}
const api = reduxApi(endpoints)
How can I call the prefetch function in a sync way? So first the token refreshes and then the Action?
EDIT
We can do the stuff async, the important is the final call to cb(), here is the example
const endpoints = {
{
url: '/some/url',
crud:true,
prefetch:[
({actions, dispatch, getState}, cb) =>{
let mills = new Date().getTime()
const { token, generationTime, accessTokenLife, refreshTokenLife, refreshToken } = localStorage
// Conditions: exixts token, it is expired, refresh token is not expired
if(token && generationTime + accessTokenLife - 500 < mills && generationTime + refreshTokenLife - 500 > mills){
dispatch(actions.token_refresh.get(null, null, (err, data) =>{
if(err){
dispatch(setError(err))
}else{
refreshTokenData(data)
}
cb()
}))
}else{
cb()
}
}
]}}
const api = reduxApi(endpoints)
You may not need to request the token every time you do an async action. In fact, I'd encourage you not to.
You can request the token when you authenticate the user and cache it using web storage. Now instead of sending a network request to retrieve the users token every time you need it, you simply check the browsers cached storage. If the token for the user exists then the user has successfully authenticated. Otherwise, the user has not logged in and you can redirect the user to the authentication page.
Since that was not actually an answer to your problem but rather a different way to solve your problem I will also answer your question in a way that is more inline with the question. You should be able to utilize promise chaining to request the user's token and then once that resolves, do any other action.
I will explain in an abstract way that is not explicity related to redux-api that you should be able to adapt to redux-api specific constructs easy enough.
const actionOne = () => {
actions.post(myJson)
.then(response => actionTwo(response))
.catch(error => console.log(error))
}
An important modification you would need to make is to convert actions.auth_token.post to return a promise. Then you can chain other actions to the resolution of that promise. If you are not familiar with promises MDNs documentation is quite good. For more information on converting a function from callbacks to promises this Stack Overflow answer is quite detailed.