How to programatically call an api in react js - reactjs

I'm using react js with fetch ,I wanna call an api of refresh token Before my token be expired ,then I calculate the difference between my local date and expire date of the token . I want to call my refresh token api every (expire - localData) - 5 min and update my token in my localStorage.
Is that possible ?
A call my refresh token in Api.js to work globally ,Is that right ?
This is my code but doesn't work like excepting:
Api.js
const duration = localStorage.getItem("duration")
useEffect(() =>{
if(token)
setInterval(() => {
RefreshApi.getRefreshToken().then(res => {
if (typeof res === "string") {
console.log("Error",res)
} else {
var now = moment(new Date(), "DD/MM/YYYY HH:mm:ss");
var expire = moment(new Date(res.expire), "DD/MM/YYYY HH:mm:ss");
var diffTime = moment.duration(expire.diff(now));
localStorage.setItem("token",res.token);
localStorage.setItem("expireDuration",diffTime.asMilliseconds());
localStorage.setItem("expire",res.expire);
}
}).catch(err => {
console.log(err);
})
},(duration - 300000))
},[])
refreshApi.js
getRefreshToken: function () {
return api.get("/refreshToken",{"Authorization": `Bearer ${localStorage.getItem("token")}`}).then(res => {
if (res.ok) {
return res.json();
} else {
return res.text();
}
});
},
Any help please ?

You can do this in different ways.
You can use the fetch API provided by JavaScript
SomeJsxFile.jsx
import { getRefreshToken } from './RefreshToken'
const refreshApi = async () => {
const token = localStorage.getItem("token");
if(token) {
const response = await getRefreshToken(token)
const now = Date.now() // this will return the Epoch time in milliseconds
const expire = Date.now(response.expire)
localStorage.setItem("token", response.token);
localStorage.setItem("expireDuration", now - expire);
localStorage.setItem("expire", response.expire);
return
}
// if dont have the token do something else
}
useEffect(() => {
// 10min = 600000 milleseconds
setInterval(refreshApi(), 600000)
},[])
RefreshToken.js
module.exports = () => ({
getRefreshToken: async (token) => {
return new Promise((resolve, reject) => {
fetch("https://localhost:3000/refreshToken", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`
},
cache: "default"
})
.then(response => response.json())
.then(responseInJSON => resolve(responseInJSON))
.catch(err => reject(err))
})
}
})
You can use a third-party library, one that is used a lot is the axios.js, you can follow this example:
Api.js
import axios from 'axios'
const api = axios.create({ baseURL: 'http://localhos:3000' })
module.exports = {
api
}
RefreshToken.js
import { api } from './Api'
module.exports = () => ({
getRefreshToken: async (token) => {
return api.get('/refreshToken', {
headers: {
"Authorization": `Bearer ${token}`
}
})
}
})

Related

dataLoaded state only changed after page reloaded

I'm doing an API call to get some data. then I keep a useState called dataLoaded. on a successful API call I make the dataLoaded=true. but to see it changed I have to reload the page.
following is my code.
const [dataLoaded, setDataLoaded] = useState(false)
useEffect(() =>{
const url = `${process.env.REACT_APP_DEV_BASE_URL}/v1/movie/`+ path.eventId + `/venue/`+ path.venue +`/showtime`;
const requestOptions = (token) => {
return ({
method: 'GET',
headers: { 'Content-Type': 'application/json', 'client_token': '4ece-9e89-1b6d4d2cbb61' }
})
};
const fetchData = async () => {
try {
const response = await fetch(url, requestOptions());
const json = await response.json();
// console.log(json);
// console.log(json.data.venueDateShowtime)
setShowTimes(json.data.dateShowtimes[0].showtimes[0]);
console.log(json.data.dateShowtimes[0].date)
setShowdate(json.data.dateShowtimes[0].date);
setDataLoaded(true);
console.log(dataLoaded)
console.log(showTimes.showtimeId)
console.log(showdate)
if(dataLoaded){
getSeatsArrangement();
}
console.log('jjjj')
}
catch (error) {
console.log("error",error);
}
};
fetchData();
},[]);
const getSeatsArrangement = async () => {
const requestOptions = (token) => {
return ({
method: 'GET',
headers: { 'Content-Type': 'application/json', 'client_token': '4ece-9e89-1b6d4d2cbb61' }
})
};
console.log(showTimes.showtimeId)
console.log(showdate)
try{
const url = `${process.env.REACT_APP_DEV_BASE_URL}/v1/seat?venueId=` + path.venue + `&movieId=`+ path.eventId +`&showtimeId=1011&movieDate=2022-10-11`;
const response = await fetch(url,requestOptions());
const json = await response.json();
console.log(json)
setReservedSeats(json.data.reservedSeats.reservedSeat)
setNonReservedSeats(json.data.reservedSeats.nonReservedSeats)
console.log(reservedSeats)
console.log(nonReservedSeats)
} catch(error) {
console.log("error",error);
}
}
Console logs when page loads
What is the aim of the code? fetchData is performed once after page loading (because of using ,[] at the end of useeffect.
And a remark: If you log your state right after setting it, the previous value will be shown! you should define another useeffect with your state as dependency (for each state) and log your state in there.
useEffect(() => {
console.log(dataLoaded)
if(dataLoaded){
getSeatsArrangement();
}
console.log('jjjj')
}, [dataLoaded]);
useEffect(() => {
console.log(showTimes.showtimeId)
}, [showTimes]);
useEffect(() => {
console.log(showdate)
}, [showdate]);
useEffect(() =>{
const url = `${process.env.REACT_APP_DEV_BASE_URL}/v1/movie/`+ path.eventId + `/venue/`+ path.venue +`/showtime`;
const requestOptions = (token) => {
return ({
method: 'GET',
headers: { 'Content-Type': 'application/json', 'client_token': '4ece-9e89-1b6d4d2cbb61' }
})
};
const fetchData = async () => {
try {
const response = await fetch(url, requestOptions());
const json = await response.json();
// console.log(json);
// console.log(json.data.venueDateShowtime)
setShowTimes(json.data.dateShowtimes[0].showtimes[0]);
console.log(json.data.dateShowtimes[0].date)
setShowdate(json.data.dateShowtimes[0].date);
setDataLoaded(true);
}
catch (error) {
console.log("error",error);
}
};
fetchData();
},[]);

How to create a global 401 unauthroized handler using React + ReactQuery + Axios stack?

So I architected frontend in the way which encapsulates every API operation tied to a single resource inside custom hook like this:
export default function useSubjects() {
const queryClient: QueryClient = useQueryClient();
const token: string | null = useStore((state) => state.user.token);
const { yearCourseId } = useParams<{ yearCourseId: string }>();
const getSubjects = async () => {
const response = await axios.get(`yearCourses/${yearCourseId}/subjects`, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
};
const postSubject = async (subject: SubjectType) => {
const response = await axios.post(`yearCourses/${yearCourseId}/subjects`, subject, {
headers: { Authorization: `Bearer ${token}` },
});
return response.data;
};
const query = useQuery(SUBJECTS_QUERY_KEY, getSubjects);
const postMutation = useMutation(postSubject, {
onSuccess: (subject: SubjectType) => {
queryClient.setQueryData(SUBJECTS_QUERY_KEY, (old: any) => [...old, subject]);
},
});
return { query, postMutation };
}
Now what is the way to globally handle 401 unauthorized? I would like to navigate user to /login on every unauthorized request. Note that I have more hooks like this tied to other resources.
use the onError callback. You can also do this globally as a callback on the queryCache
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: error => {
// check for 401 and redirect here
}
})
})

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 making 2 requests on refresh

When I navigate using Link (react router-dom) I don't have this problem, but if I refresh the browser I get a 403 error in console saying unauthorised and then I get the data in the next request with a 200 response. Why is this making what looks like 2 requests when refreshing the browser?
import { AuthContext } from "../../shared/context/auth-context";
const ContactEntries = () => {
const auth = useContext(AuthContext);
useEffect(() => {
const source = Axios.CancelToken.source();
setIsLoading(true);
const getContactEnquiries = async () => {
try {
const response = await Axios.get(
`${process.env.REACT_APP_BACKEND_URL}/v1/contact`,
{
cancelToken: source.token,
headers: { Authorization: "Bearer " + auth.token },
}
);
if (response.status === 200) {
setIsLoading(false);
setEnquiries(response.data.enquiries);
}
} catch (err) {
setIsLoading(false);
console.log(err.response);
}
};
getContactEnquiries();
return () => {
source.cancel();
};
}, [!!auth.token]);
}
Here is my authContext:
import { createContext } from "react";
export const AuthContext = createContext({
isLoggedIn: false,
userId: null,
token: null,
email: null,
firstName: null,
login: () => {},
logout: () => {},
});
This is because your useEffect is running twice on refresh. On first render it is not getting auth.token and may be it null. And on second render it is making call with 200 status code.
You have to check auth token it coming successfully.
You can check it this way
useEffect(() => {
const source = Axios.CancelToken.source();
setIsLoading(true);
const getContactEnquiries = async () => {
try {
const response = await Axios.get(
`${process.env.REACT_APP_BACKEND_URL}/v1/contact`,
{
cancelToken: source.token,
headers: { Authorization: "Bearer " + auth.token },
}
);
if (response.status === 200) {
setIsLoading(false);
setEnquiries(response.data.enquiries);
}
} catch (err) {
setIsLoading(false);
console.log(err.response);
}
};
if(auth.token) getContactEnquiries();
return () => {
source.cancel();
};
}, [!!auth.token]);

Values won't update after api call with axios

I'm trying to fetch data with axios using React's useEffect in two steps:
1- Get the access token with a POST request
2- Use the token on another POST request to get the desired data
After the first post request, the response returns as expected, but the state value doesn't update. So it sends undefined as the token for the second request.
const [infos, setInfos] = useState(null)
const [token, setToken] = useState('')
useEffect(() => {
const getToken = async () => {
try {
const response = await axios.post(
'adress',
{
username: 'root',
password: 'i_want_my_token',
}
)
setToken(response.data.access)
} catch (error) {
console.log(error)
}
}
getToken()
const getCatalogo = async () => {
try {
let data = { id: 6 }
let configCatalogo = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
}
const catalogoResponse = await axios
.post(
'adress',
data,
configCatalogo
)
setInfos(res.data)
} catch (error) {
console.log(error) }
}
getCatalogo()
}, [])
Since the function to get/set your token is asynchronous, you need to use two different useEffects: one to fetch the token and one to use that token once it is set.
const [infos, setInfos] = useState(null)
const [token, setToken] = useState('')
useEffect(() => {
const getToken = async () => {
try {
const response = await axios.post(
'adress',
{
username: 'root',
password: 'i_want_my_token',
}
)
setToken(response.data.access)
} catch (error) {
console.log(error)
}
}
getToken()
}, []);
useEffect(() => {
const getCatalogo = async () => {
try {
let data = { id: 6 }
let configCatalogo = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
}
const catalogoResponse = await axios
.post(
'adress',
data,
configCatalogo
)
setInfos(res.data)
} catch (error) {
console.log(error) }
}
getCatalogo()
}, [token]);

Resources