Axios Interceptor is not working in React JS - reactjs

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);
});

Related

React-Admin Simple Refresh JWT Token

I have an react-admin with an auth provider like the below.
I want to refresh my token, but I don't know how to do it.
I tried to follow this blog post, but my auth is a little different and I can't make it work (the error "httpClient(...).then" is not a function and others make me leave it).
I can make it with a more simple solution, does not need to be in memory. I tried to call my refresh endpoint to get my refresh token, but my call go without the current token.
My endpoint to refresh the token is:
/auth/jwt/refresh
I need to call it like this:
curl -X 'GET' \
'http://localhost:8000/auth/jwt/refresh' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
And My response body would be: (and I need to save it to my localstorage or the in memory way)
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZTUwZDdhZDctOWE5Ni00NzQyLTgxNWEtZTNmZmJmNGRiMTVjIiwiYXVkIjpbImZhc3RhcGktdXNlcnM6YXV0aCJdLCJleHAiOjE2Mzk4NDE1MDF9.-o2yk56sCj_MZx_VA6PxH7gZ-KKSMmopbDNDiapHmn0",
"token_type": "bearer"
}
My inMemoryJWTManager file:
const inMemoryJWTManager = () => {
let inMemoryJWT = null;
let isRefreshing = null;
let logoutEventName = 'ra-logout';
let refreshEndpoint = '/auth/jwt/refresh';
let refreshTimeOutId;
const setLogoutEventName = name => logoutEventName = name;
const setRefreshTokenEndpoint = endpoint => refreshEndpoint = endpoint;
// This countdown feature is used to renew the JWT before it's no longer valid
// in a way that is transparent to the user.
const refreshToken = (delay) => {
refreshTimeOutId = window.setTimeout(
getRefreshedToken,
delay * 1000 - 5000
); // Validity period of the token in seconds, minus 5 seconds
};
const abordRefreshToken = () => {
if (refreshTimeOutId) {
window.clearTimeout(refreshTimeOutId);
}
};
const waitForTokenRefresh = () => {
if (!isRefreshing) {
return Promise.resolve();
}
return isRefreshing.then(() => {
isRefreshing = null;
return true;
});
}
// The method make a call to the refresh-token endpoint
// If there is a valid cookie, the endpoint will set a fresh jwt in memory.
const getRefreshedToken = () => {
const request = new Request(refreshEndpoint, {
method: 'GET',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'include',
});
isRefreshing = fetch(request)
.then((response) => {
if (response.status !== 200) {
ereaseToken();
global.console.log(
'Token renewal failure'
);
return { token: null };
}
return response.json();
})
.then(({ token, tokenExpiry }) => {
if (token) {
setToken(token, tokenExpiry);
return true;
}
ereaseToken();
return false;
});
return isRefreshing;
};
const getToken = () => inMemoryJWT;
const setToken = (token, delay) => {
inMemoryJWT = token;
refreshToken(delay);
return true;
};
const ereaseToken = () => {
inMemoryJWT = null;
abordRefreshToken();
window.localStorage.setItem(logoutEventName, Date.now());
return true;
}
// This listener will allow to disconnect a session of ra started in another tab
window.addEventListener('storage', (event) => {
if (event.key === logoutEventName) {
inMemoryJWT = null;
}
});
return {
ereaseToken,
getRefreshedToken,
getToken,
setLogoutEventName,
setRefreshTokenEndpoint,
setToken,
waitForTokenRefresh,
}
};
export default inMemoryJWTManager();
This is my auth provider: (updated, using inMemoryJWTManager)
import inMemoryJWTManager from './inMemoryJWT'
const apiUrl = 'http://localhost:8000'
const authProvider = {
login: ({username, password}) => {
const oAuthParams = {
username,
password
}
const body = Object.keys(oAuthParams).map((key) => {
return encodeURIComponent(key) + '=' + encodeURIComponent(oAuthParams[key]);
}).join('&');
const request = new Request(`${apiUrl}/auth/jwt/login`, {
method: 'POST',
body: body,
headers: new Headers({'Content-Type': 'application/x-www-form-urlencoded'}),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(( {access_token} ) => {
inMemoryJWTManager.setToken(access_token);
});
},
checkError: (error) => {
const status = error.status;
if (status === 401 || status === 403) {
inMemoryJWTManager.ereaseToken();
return Promise.reject({redirectTo: '/login'});
}
// other error code (404, 500, etc): no need to log out
return Promise.resolve();
},
checkAuth: () => inMemoryJWTManager.getToken()
? Promise.resolve()
: Promise.reject({ message: 'Login necessário', redirectTo: 'login' }),
logout: () => {
inMemoryJWTManager.ereaseToken();
return Promise.resolve();
},
getPermissions: () => {
return inMemoryJWTManager.getToken() ? Promise.resolve() : Promise.reject();
},
};
export default authProvider;
My updated httpClient code using inMemoryJWTManager: (and I'm using: const dataProvider = jsonServerProvider(apiUrl, httpClient); with modifications to it, but I think it is irrelevant)
const httpClient = (url) => {
const options = {
headers: new Headers({ Accept: 'application/json' }),
};
const token = inMemoryJWTManager.getToken();
console.log(token)
if (token) {
options.headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, options);
} else {
inMemoryJWTManager.setRefreshTokenEndpoint(`${apiUrl}/auth/jwt/refresh`);
return inMemoryJWTManager.getRefreshedToken().then((gotFreshToken) => {
if (gotFreshToken) {
options.headers.set('Authorization', `Bearer ${inMemoryJWTManager.getToken()}`);
};
return fetchUtils.fetchJson(url, options);
});
}
};
My problem is that, when I call my refresh token endpoint, my request go without the {'Authorization': Bearer... and it is not renewed and I got logged out. The other endpoints are fine, they go with the token.
You must check token expire before each requests, if token expired you must get new from /auth/jwt/refresh, then you can send current request. All this information is in the article post. Example:
const httpClient = (url) => {
const options = {
headers: new Headers({ Accept: 'application/json' }),
};
const token = inMemoryJWT.getToken();
if (token) {
options.headers.set('Authorization', `Bearer ${token}`);
return fetchUtils.fetchJson(url, options);
} else {
inMemoryJWT.setRefreshTokenEndpoint('http://localhost:8001/refresh-token');
return inMemoryJWT.getRefreshedToken().then((gotFreshToken) => {
if (gotFreshToken) {
options.headers.set('Authorization', `Bearer ${inMemoryJWT.getToken()}`);
};
return fetchUtils.fetchJson(url, options);
});
}
};

Axios interceptor - shows sign in form

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.

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