Issue with Axios interceptors - reactjs

I have issue with axios. I get new access token but it sends not updated access token with further request and I get same unauthenticated error.
import axios from 'axios'
axios.defaults.baseURL='http://127.0.0.1:8000/api/'
axios.defaults.headers.common['Content-Type']='application/json'
let refresh = false
axios.interceptors.response.use(res =>res ,async error =>{
if (error.response.status === 401 && !refresh ){
refresh=true
const response= await axios.post('/user/refresh/',{},{withCredentials:true})
if(response.status===200){
axios.defaults.headers.common['Authorization']='JWT '+response.data.token
return axios(error.config)
}
}
refresh=false
return error
})
Same access token with both requests is sent. After refresh request it doesn't change authorization header. It only changes when I refresh page

When axios initiate a request, it takes all the configuration at that moment. So, when you update the default config with the new token, it does not modify the configuration of the request that has been sent before. Hence, error.config contains the old token. You need to modify it directly.
We can modify this block:
if(response.status===200){
axios.defaults.headers.common['Authorization']='JWT '+response.data.token
return axios(error.config)
}
to:
if(response.status===200){
// we update the new token here but it does not affect the config of the previous request.
// error.config is not updated.
axios.defaults.headers.common['Authorization']='JWT '+response.data.token
// now we resend the request that produces the error
// we need to update the config directly
error.config.headers['Authorization'] = 'JWT '+response.data.token
return axios(error.config)
}

Related

csurf with React: Invalid token after changing user

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 🤍

What's the best way to store a HTTP response in Ionic React?

I'm developing an app with Ionic React, which performs some HTTP requests to an API. The problem is I need to store the response of the request in a local storage so that it is accessible everywhere. The way I'm currently doing it uses #ionic/storage:
let body = {
username: username,
password: password
};
sendRequest('POST', '/login', "userValid", body);
let response = await get("userValid");
if (response.success) {
window.location.href = "/main_tabs";
} else if (!response.success) {
alert("Incorrect password");
}
import { set } from './storage';
// Handles all API requests
export function sendRequest(type: 'GET' | 'POST', route: string, storageKey: string, body?: any) {
let request = new XMLHttpRequest();
let payload = JSON.stringify(body);
let url = `http://localhost:8001${route}`;
request.open(type, url);
request.send(payload);
request.onreadystatechange = () => {
if (request.readyState === 4 && storageKey) {
set(storageKey, request.response);
}
}
}
The problem is that when I get the userValid key the response hasn't come back yet, so even awaiting will return undefined. Because of this I have to send another identical request each time in order for Ionic to read the correct value, which is actually the response from the first request. Is there a correct way of doing this other than just setting timeouts everytime I perform a request?
You are checking for the results of storage before it was set. This is because your sendRequest method is calling an asynchronous XMLHttpRequest request, and you are checking storage before the sendRequest method is complete. This can be fixed by making sendRequest async and restructuring your code a bit.
I would suggest you instead look for examples of ionic react using hooks or an API library - like fetch or Axios. This will make your life much easier, and you should find lots of examples and documentation. Check out some references below to get started:
Example from the Ionic Blog using Hooks
Example using Fetch using React
Related Stack Overflow leveraging Axios

REDUX SAGA - API Retry Isomorphic Fetch

I am trying to add RETRY Logic in the context of - I make an API call -> response is 401 -> I invoke APi to request for a NEW Token in the background. The poin there si MY API Calls shouldnt fail. Following is my API File (This is common - Every API in my application invokes this File to make an FETCH)
NOTE : I have seen articles using the fetch().then() approach, but we are using YIELD.
Specific API File -
// apiRequest = part of api.js file i am specifying below
const response = yield retry(3,1000,apiRequest,options); // My apiRequest while trying for getting new access tokens send me a NULL, do we want that ?
if (undefined !== response && null !== response) {
const formattedResponse = yield apply(response, response.json);
if (response.status === 200) {
yield call(handleAddCampaignResponseSuccess, formattedResponse);
} else {
yield call(handleAddCampaignResponseFailure, formattedResponse);
}
} else{
// Show some Message on UI or redirect to logout
}
// api.js
function* apiRequest(options) {
const { method, body, url } = options;
const accessToken = yield select(selectors.AccessToken);
const idToken = yield select(selectors.IdToken);
try {
var response = yield call(fetch, url, {
method: method,
body: body,
headers: {
"Content-Type": ContentTypes.JSON,
Authorization:
accessToken != "" ? `Bearer ${accessToken} ${idToken}` : "",
},
});
if (null !== response) {
if (response.status === HTTP_CODES.HTTP_UNAUTHORIZED) {
// Unauthorized requests - redirect to LOGOUT
// Request for Refresh Token !
yield put(refreshTokenOnExpiry());
return null; // Is this necessary
} else if (response.status === HTTP_CODES.HTTP_NOT_FOUND) {
return null;
} else if (response.status === HTTP_CODES.HTTP_SERVER_ERROR) {
// Logout cos of serrver error
yield put(handleLogout());
return null;
} else {
console.log("From Else part");
// - Called on intent to ensure we have RESET redirections and that it does not cause issues of redirection.
yield put(resetRedirections());
return response;
}
} else {
// Handle Logout
yield put(stopTransition());
yield put(handleLogout());
}
} catch (error) {
// Cors Error in case of DEV URL
// See if SAGA is Still listening to the Action Dispatches
console.log("From CATCH BLOCK");
yield put(stopTransition());
yield put(handleLogout());
return null;
}
}
My concern is the documentation says that - if API request fails then it will retry, I do not get the meaning of it. Does it mean if the API returns NULL, or anything other than Http 200 ? Cos I want the API to retry in case of 401
API.JS is the file invoked by ALL API's across my website. Also, how can I ensure that refreshTokenOnExpiry gets called ONLY once (meaning at a time there will be multiple API calls and each one when got a 401 will eventually invoke refreshTokenOnExpiry this API)
I am new to generator functions, so I am sure I must have goofed up somewhere.
Also if anyone who can help me build this code correctly, would be great help. Thanks !
Adding Image for reference - I want the FAILED API's to be retried which aint happening :
My concern is the documentation says that - if API request fails then it will retry, I do not get the meaning of it. Does it mean if the API returns NULL, or anything other than Http 200 ? Cos I want the API to retry in case of 401
Scroll down to the section "Retrying XHR calls" in the redux-saga recipes to get an idea of what the retry effect is doing behind the scenes.
The retry effect can be used on any function, no just an API call, so it's not looking at the response code. It defines "failure" as code that throws an error rather than completing execution. So what you need to do is throw an error in you apiRequest.
No guarantees, but try this:
if (response.status === HTTP_CODES.HTTP_UNAUTHORIZED) {
// Unauthorized requests - redirect to LOGOUT
// Request for Refresh Token !
yield put(refreshTokenOnExpiry());
throw new Error("invalid token");
}
You need to figure out how to make sure than the new token gets set before retrying. You might want to build your own chain of actions rather than relying on retry. For example, you can put an action with type "RETRY_WITH_NEW_TOKEN" that has a payload containing the original options and the token that it was tried with. That way you can compare it against the token in state to see if you have a new one.

It was set headers and axios but Why happen cros error?

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
}

How to get authenticated token inside already logged in page

I'm new to React. I have already set up small size web service with Django backend on AWS EB. It has custom user model. And most contents are available after user logged in. It works fine.
When I start to make a mobile app with React Native, I set up the Django Rest into same place with sharing same db models with web service. I have chosen a Token authentication way for it. It works fine with React Native app on mobile. Once users log in through a mobile app, API returns auth Token. After getting Token from API, mobile app interacts with API including Token in JSON header.
During learn and develop React Native mobile app. I enjoyed it very much. So, I want to put small react app into one of my web pages, not writing a whole single page app. At this stage, one problem came to my mind that how my react app gets auth Token without inputting user ID and password again.
I spent hours to find any clue through googling, but failed. Can anyone give me a hint? How react app inside already logged web page interact with Token auth based API without log in again?
I think you can use cookies or localStorage to save your token and then in react part just get this token out of there.
You can use localStorage to set and get the token
ex:
set - > localStorage.setItem('token', 'dac43hh5r3nd23i4hrwe3i2un32u');
get - > localStorage.getItem('token');
For ReactJS, take a look at Where to store token. You can use localStorage or cookies.
Cookie usage:
document.cookie = cookie_name + "=" + value + ";expires=" + expire_date + ";";
PS: The expire date should be on GMT format.
localStorage usage:
// To set the token
localStorage.setItem('token', 'dac43hh5r3nd23i4hrwe3i2un32u');
// To get the token
localStorage.getItem('token');
The Django docs for Session, and read this question, it would help you.
Cookie or localStorage?
I would take cookies. Why? Because you can set expiration date(for automatic deletion, for example), domain, path and other things that can be useful. On the localStorage, you just store the info.
I'd like to leave my answer after I solved in my way through my long research and study. My solution is quite simple.
1. set DRF session authentication enable. Adding some code in setting.py
REST_FRAMEWORK = {
# ...
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}
2. add 'credentials: "include"' into fetch code to use already logged in session cookie for authentication.
await fetch(API_URL, {
credentials: "include"
})
this solution solved my case.
React you can use any libray for the calling API, ex- axios is one of them. When you do the login first time save that token in the localstorage or session.
Now we have to add that token in header for that you can user the interceptor i.e when we make API call every time interceptor will get call, at that place you can get the token from the local storage or session add the request header.
below is sample code of interceptor.
import axios from 'axios';
import cookie from 'react-cookies';
import * as utils from './utils';
let axios_instance = axios.create();
axios_instance.interceptors.request.use(
(configuration) => {
const config = configuration;
const authToken = cookie.load('authToken');
if (authToken) {
config.headers.Authorization = `Token ${authToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
axios_instance.interceptors.response.use((response) => {
return response;
}, (error) => {
if (error.response.status == 401) {
utils.clearCookie();
window.location.href = '/login';
return;
}
if (error.response.status == 403) {
utils.clearCookie();
window.location.href = '/login';
return;
}
if (error.response.status == 404) {
// window.location.href = '/not-found';
return;
}
if (error.response.status == 500) {
// window.location.href = '/server-error';
return;
}
return Promise.reject(error);
});
export default axios_instance;

Resources