Axios interceptor - shows sign in form - reactjs

The axios interceptor is not working correctly for me.
Here is my code for refreshing JWT tokens:
axios.interceptors.request.use(
async config => {
const value = localStorage.getItem("access_token");
console.log("Sending request: " + value);
config.headers = {
'Authorization': `Bearer ${value}`,
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
return config;
},
error => {
return Promise.reject(error)
});
// Response interceptor for API calls
axios.interceptors.response.use((response) => {
console.log("Response OK!");
return response;
},
function (error) {
console.log("Error, refreshing access token!");
const originalRequest = error.config;
if (error.response.status === 401) {
console.log("Retry: true")
originalRequest._retry = true;
let axiosInstance = axios.create();
return axiosInstance
.post('/auth/token/refresh/', {refresh: localStorage.getItem("refresh_token")})
.then(res => {
if (res.status === 200) {
console.log("Access token:" + res.data.access);
store.dispatch(setAccessToken(res.data.access));
return axios(originalRequest);
}
});
}
return Promise.reject(error);
});
The token gets refreshed correctly, however everytime I make the first request (after the access_token expired) it always shows me the browsers 'Sign In' form.

The problem was not with the interceptor, but at backend site.
Basic Authentication was enabled and it seems that browsers tries to get the auth parameters from the user ('Sign in' form) first.

Related

Axios Interceptor is not working in React JS

I am using the below code as an interceptor in my React JS app for getting token back but unfortunately, it is not working. Refresh token returns new idToken and updates local storage data correctly. The same code I'm using some other application which works fine. One main difference is that I currently use React 18 and the previous 16. I struggled to identify the problem but failed. Your help will be appreciable.
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response.status === 401) {
// console.log(error.response.data.code)
let usersData = JSON.parse(localStorage.getItem("userData"));
const refreshToken = usersData.refreshToken;
return axios
.post(
`${api_base_url}/auth/authentication/refresh_token`,
JSON.stringify({
refresh_token: refreshToken,
})
)
.then((response) => {
usersData["accessToken"] = response.data.data.accessToken;
usersData["idToken"] = response.data.data.idToken;
setSessionStorage("userData", usersData);
error.response.config.headers[
"Authorization"
] = `Bearer ${response.data.data.idToken}`;
return axios(error.response.config);
})
.catch((error) => {
if (error.response.data.code !== "TOKEN_EXPIRED") {
return;
}
localStorage.clear();
window.location = "/login";
});
}
return Promise.reject(error);
}
);
function getIRequestProp(severType, isMultipart, isSocial) {
const serverUrl = severType ? social_api_base_url : api_base_url;
let userData = JSON.parse(localStorage.getItem('userData'));
let idToken;
idToken = userData !== null ? userData['idToken'] : '';
let content_type;
if (isSocial) {
content_type = 'application/x-www-form-urlencoded'
} else {
content_type = isMultipart ? 'multipart/form-data' : 'application/json'
}
return {
serverUrl: serverUrl,
requestHeader: {
'Content-Type': content_type,
'Accept-Language': DEFAULT_LANGUAGE,
Authorization: `Bearer ${idToken}`
}
};
}
async function post(url, body, isSocialServer, isMultipart) {
const {serverUrl, requestHeader} = getIRequestProp(isSocialServer, isMultipart);
return axios.post(serverUrl + url, body, {
headers: requestHeader
});
}
So, I call API like this:
AxiosServices.post(ApiUrlServices.SOCIALS_UPDATE_LINKS(UserInfo.userId), payload, false)
.then(response => {})
What i figured out that return axios(error.response.config); is not sending authorization token in API request headers and trying request infinitely. But consoling error.response.config shows token sets in the config correctly.
Adding an additional modification of axios request, I solved my problem.
axios.interceptors.request.use(request => {
// Edit request config
let usersData = JSON.parse(localStorage.getItem('userData'));
request.headers['Authorization'] = `${usersData.idToken}`;
return request;
}, error => {
return Promise.reject(error);
});

axios response interceptor not working properly

Im trying to use axios interceptor with refresh token but when my token has expired. My error code within in interceptors.response is not executing. What can i do to fix this issue, i am also getting a status code of 200 which does not make much sense to me.
Also here is my code
proctedInstance.interceptors.request.use(
async config => {
const accesstoken = localStorage.getItem('accesstoken');
config.headers = {
'Authorization': `Bearer ${accesstoken}`,
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
return config;
},
error => {
Promise.reject(error);
}
)
proctedInstance.interceptors.response.use((response) => {
console.log(response);
return response
},
function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
return axios.post('http://localhost:4000/refresh_token')
.then(res => {
if (res.status === 200) {
localStorage.setItem('accesstoken', res.data.accesstoken)
console.log('my token res.data.accesstoken', res.data.accesstoken);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('accesstoken');
return axios(originalRequest);
}
})
}
return Promise.reject(error);
})
export const onProtected = async () => {
const results = await (await proctedInstance.post('/protected')).data
if(results.data === 'This is protected data.'){
return true;
} else
return false;
}
any help is appreciated
Try letting it like this:
proctedInstance.interceptors.response.use(
function (response) {
return response;
},
function (error) {
const access_token = localStorage.getItem("accesstoken");
if (error.response.status === 401 && access_token) {
//Your logic to refresh token and reattempt request
} else {
console.error(error);
}
return Promise.reject(error);
}
);

How to use refresh token in reactjs

I am working on a admin app in reactjs which uses redux for state management. In my app when user log in it gets access_token and refresh _token. access_token which gets expired after 5 min. after 5 min token becomes invalid to make any api endpoint request.
I want to know how am I suppose to use this refresh_token to update my access_token which is stored in localStorage of the browser. I had no idea about this refresh_token before this. This is how I make login request and save my access_token and refresh_token.
authentication:
export const Authentication = async(payload) =>{
try{
const response = await fetch(`${generalConfig.baseURL}/oauth/token`, {
method: 'POST',
cache: 'no-cache',
headers: {
'Authorization': `Basic ${btoa("topseller:topseller")}`,
'Accept': '*/*',
// 'Content-Type': 'multipart/form-data',
'accept-encoding': 'gzip, deflate',
},
body: payload
})
.then((response)=>{
console.log(response)
return response.json()
},err=>{
console.log(err,'############')
})
console.log(response,'#########')
return response;
}catch(err){
console.log(err,'############')
}
}
authentication.action:
export const getAccessToken = (dataToSend) => async (dispatch) => {
try {
var formData = ConvertToFormData(dataToSend);
const authResponse = await Authentication(formData);
<-- See above about this Authentication function
const response = await fetch("http://api.smartocart.com/userType", {
method: "GET",
cache: "no-cache",
headers: {
Authorization: `Bearer ${authResponse.access_token}`,
},
});
const payload = await response.json();
if (payload.status === "admin") {
SaveToLocalStorage("access_token", authResponse.access_token);
SaveToLocalStorage("refresh_token", authResponse.refresh_token);
dispatch({
type: GET_ACCESS_TOKEN,
payload: {
access_token: authResponse.access_token,
refresh_token: authResponse.refresh_token,
},
});
} else {
dispatch({
type: ERROR_ACCESS_TOKEN,
buttonPressed: true,
});
}
} catch (exception) {
console.log("Log In again");
}
};
I did read about this in some of the blog post but i did get this. https://nmajor.com/posts/access-and-refresh-token-handling-with-redux
Any help would be highly appreciated.
You can add token expiry time validation on app.js so if you reload you application or move to next page or if you make api call it will check token expiry time validation always if token expired it will make call to fetch update token
check below example : i gave example with react axios
axios.interceptors.request.use(async (config) => {
const expireAt = localStorage.getItem('expiresAt');
let token = localStorage.getItem('authToken');
if (dayjs(expireAt).diff(dayjs()) < 1) {
const data = onGetForcedToken();
token = typeof data === 'string' ? data : await data();
}
// setting updated token
localStorage.setItem('authToken', token);
return config;
}, (err) => {
console.log("error in getting ",err)
});
If you are using Axios, you can intercept a request with the help of interceptor and call api to get a new token in case token got expired.
Another approach to get a new token is by periodically calling api after certain interval before the token gets expired.
For example in App.js
// Get new Token
const getNewUserToken = async () => {
const submitUrl = apiRoot + "/v1/user/refreshtoken";
try {
const res = await fetch(submitUrl, {
method: "GET",
headers: {
token: localStorage.getItem("token"),
"Content-Type": "application/json",
},
});
if (res.status === 200) {
const data = await res.json();
localStorage.setItem("token", data.token);
} else {
// New token didnt received.Remove the previous token and user
localStorage.removeItem("token");
localStorage.removeItem("user");
setUser({});
navigate("/");
}
} catch (err) {
console.log(err);
}
};
const intervalRef = useRef();
const getToken = useCallback(() => {
// Get new token if and only if existing token is available
if (localStorage.getItem("token") != null) {
getNewUserToken();
}
}, []);
// Trigger API to get a new token before token gets expired.
useEffect(() => {
const interval = setInterval(() => getToken(), 1000 * 60 * 6); // 6 minutes interval as our token will expire after 7 minutes.
intervalRef.current = interval;
return () => clearInterval(interval);
}, [getToken]);
Hope this will help you to automatically refresh the token without forcing user to login.

React-admin JWT authentication refresh token problem

I want to implement my own authProvider for react-admin but I'm stuck.
I use a Django-Rest-Framework backend and a JWT token authentication system.
I want to refresh the JWT token if it's almost expired before every request. According to the documentation the authProvider's checkAuth function gets called before every API call, which is true. My problem is that with my code it doesn't wait for the promise to finish and it uses the old access token which results in a 401 and I get redirected to the login page. Any guidance what am I missing?
import jwt from 'jsonwebtoken';
export default {
login: async ({ username, password }) => {
const request = new Request('http://localhost:8000/api/token/', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
const response = await fetch(request);
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
const { refresh, access } = await response.json();
localStorage.setItem("refreshToken", refresh);
localStorage.setItem("accessToken", access);
},
logout: params => {
console.log("logout");
localStorage.setItem('accessToken', "");
localStorage.setItem('refreshToken', "");
return Promise.resolve();
},
checkAuth: (params) => {
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
if (accessToken && refreshToken) {
console.log(accessToken);
const { exp } = jwt.decode(accessToken);
if (exp > (new Date().getTime() / 1000) - 10) {
return Promise.resolve();
} else {
const request = new Request('http://localhost:8000/api/token/refresh/', {
method: 'POST',
body: JSON.stringify({ "refresh": refreshToken }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
const response = fetch(request)
.then(response => {
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token }) => {
localStorage.setItem('accessToken', token);
return Promise.resolve();
});
return response;
}
}
return Promise.reject();
},
checkError: error => {
if (error.status === 401 || error.status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
getPermissions: params => Promise.resolve(),
}
Can you try something like that
checkAuth: async (params) =>
And
const request = new Request(...);
let data;
const response = await fetch(request);
if (response.ok) data = await response.json()
else throw new Error(response.statusText);
if (data && data.token) {
localStorage.setItem('accessToken', data.token);
console.log(data.token);
return Promise.resolve();
} else return Promise.reject();

How to refresh JWT tokens in React.js Application?

I checked all the similar questions here but none has what I need.
I'm securing the routs in my App and sending the JWT with every request and everything is fine here.
The issue is when the JWT expires, instead of logging out the user, I need to know how to refresh that token and keep the user logged in.
Everyone is talking about creating a "Middleware" that handles that, but no one says how to create that middleware and what's in it?
So, what is the best practice in doing that? Should I check for JWT expiration date before sending any request? or should I wait for a "401" response then try to refresh the token (which I don't know how to do), or what exactly?
If anyone has a working example of such a middleware or a package or a project on Github that can help me with this it would be great.
I'm only interested in the front-end part of the process, what to send from react and what should I expect to receive and what to do with it.
If you are using Axios (which I highly recommend), you can declare your token refreshing behaviours in the response's interceptors. This will apply to all https requests made by Axios.
The process is something like
Checking if the error status is 401
If there is a valid refresh token: use it to get the access token
if there is no valid refresh token: log the user out and return
Redo the request again with the new token.
Here is an example:
axios.interceptors.response.use(
(response) => {
return response
},
(error) => {
return new Promise((resolve) => {
const originalRequest = error.config
const refreshToken = localStorage.get('refresh_token')
if (error.response && error.response.status === 401 && error.config && !error.config.__isRetryRequest && refreshToken) {
originalRequest._retry = true
const response = fetch(api.refreshToken, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh: refreshToken,
}),
})
.then((res) => res.json())
.then((res) => {
localStorage.set(res.access, 'token')
return axios(originalRequest)
})
resolve(response)
}
return Promise.reject(error)
})
},
)
your middelware should look like this block of code (as example you can use whatever you want)
/* eslint-disable */
import request from 'superagent';
function call(meta, token) {
const method = meta.API_METHOD ? meta.API_METHOD : 'GET';
let req = request(method, 'http://localhost:8000/' + meta.API_CALL);
req = req.set({ Authorization: `JWT ${token}` });
req = meta.API_TYPE ? req.type('Content-Type', meta.API_TYPE) : req.set('Content-Type', 'application/json');
if (meta.API_PAYLOAD) {
req = req.send(meta.API_PAYLOAD);
}
if (meta.API_QUERY) {
req.query(meta.API_QUERY);
}
return req;
}
export default store => next => action => {
const state = store.getState();
const token = state.logged && state.logged.get('token') ?
state.logged.get('token') : 'eyJhbGciOiJIUzUxMiJ9';
if (action.meta && action.meta.API_CALL) {
call(action.meta, token)
.then((res) => {
store.dispatch({
type: action.meta.API_SUCCESS,
result: res.body,
});
})
.catch(({ status, response }) => {
if (action.meta.API_ERRORS && action.meta.API_ERRORS[status]) {
return store.dispatch({
type: action.meta.API_ERRORS[status],
result: response.body,
});
}
if (action.meta.API_ERRORS && action.meta.API_ERRORS[status] === '401') {
/*call the refresh token api*/
call(<Your Meta for refreshing>, <expiredtoken>)
.then((res) => {
store.dispatch({
type: action.meta.API_SUCCESS,
result: res.body,
});
})
.catch(({ status, response }) => {
if (action.meta.API_ERRORS && action.meta.API_ERRORS[status]) {
return store.dispatch({
type: action.meta.API_ERRORS[status],
result: response.body,
});
}
throw response;
});
}
throw response;
});
}
return next(action);
};

Resources