Reputation: 330
I'm pretty new in React-Native programming, but here is the context.
We are using React Query and Axios libraries in our project. As AuthManager we are using Keycloak and for the library managing auth status we have React Native Keycloak. We encounter a tedious problem with our server responding randomly 401 at our requests after a certain amount of time, bringing also to the app crash sometimes.
We reproduced the error making the Bearer Token of Keycloak expire after only 1 minute. This caused almost immediatly the 401 error and we wondered why this is happening.
Let's say we have a screen with some "Activities" and this screen is the first thing the user will see. For handling requests, in our code we use some custom hooks that reference useQuery
, for example:
export function useActivities(): UseQueryResult<ActivityList> {
const { headers } = useHeaders();
return useQuery(
['activities', today.start],
() => getActivitiesList(headers), // Note 1
{
enabled: !!today.start,
}
);
}
The important point of it is that we useHeaders
to get our updated headers with the Keycloak token and our realm settings. useHeaders
is almost everywhere in our app.
export function useHeaders(): UseHeaders {
const { keycloak } = useKeycloak();
const KEYCLOAK_REALM = remoteConfig().getString('KEYCLOAK_REALM');
const headers = {
Authorization: `Bearer ${keycloak?.token}`,
Realm: KEYCLOAK_REALM,
};
return { headers };
}
Now, the getActivitiesList
is simple as five:
async function getActivitiesList(headers: UseHeaders['headers']): Promise<ActivityList> {
const url = `${BASE_URL}${VERSION}/activities/grouped?end=${end}&start=${start}`;
// Note 2
return axios
.get(url, { headers })
.then((res) => res.data)
.catch((e) => console.error('Error fetching grouped activities:', e));
}
The problem with all of that is that whenever Keycloak will trigger the refresh token, the token inside keycloak
object is changed, the headers inside useActivities
are changed BUT if I print the headers inside my getActivitiesList
(// Note 2
), or even inside the query function (// Note 1
), headers will not be updated. Sometimes it just causes to make two requests (one that fails and show error, the other one actually working), some other times the app crashes without any explain. This makes me wonder why the query function will not update its headers and passed the "old" headers inside getActivitiesList
.
For now we are mitigating this problem in two different points.
axios.defaults.headers.common.Realm = KEYCLOAK_REALM;
axios.defaults.headers.common.Authorization = 'Bearer ${keycloak?.token}';
This is not a perfect solution and we are here to search some info about this problem.
Someone get something similar? How to manage the token refresh in useQuery function?
Upvotes: 2
Views: 1378
Reputation: 330
After almost a year I can admit I was dumb. Actually the solution was pretty simple.
TL;DR
One cannot expect to get fresh value, if value is a const that doesn't change once rendered. But you can "fetch" a new token, requesting with a function
, not just "initialized" as const
.
// This is a function, not a simple const anymore
const headers = () => {
Authorization: `Bearer ${keycloak?.token}`,
Realm: KEYCLOAK_REALM,
};
// Usage, please note the "()" after headers, it's a function now!
axios.get(url, { headers() })
LONG ANSWER
When using useHeaders
the React-Native render cycle will render the hook once. The hook itself will initialize the const headers
and it will never change again, until a re-render will happen. But if the const headers
will not change after timeout
time, token will be invalid. If something will cause a re-render of that hook, the const headers
will be reinitialized, so it will contain a fresh token.
This was the reason sometimes it would work, sometimes not. The solution is pretty simple. Since useKeycloak
has always a valid and fresh token, instead of going with a simple variable declaration:
const headers = something
we can go with a
const headers = () => something
that will always return a new fresh (eventually refreshed) something, that in this case would be my precious token.
Upvotes: 0