Hey there fellow developers, I have an issue in my React Native + Redux + Saga + Axios app, where the refresh token is refreshed after 15 minutes. If the app is in an active state, i.e. the user is using it, there are no problems, and token is refreshed properly. However, when the app is inactive or in the background, and user opens it after 15 or more minutes of using it, app sends a POST request to renew the token, but while that is done, other API calls such as fetching user profile and some other features present on the initial screen (Home screen) are made in parallel, resulting in errors as the token renewal has not been finished yet.
In the Axios interceptors I have the following:
interface DispatchActions {
success?: (payload: any) => AnyAction
failure: (payload: any) => AnyAction
}
interface Options {
withoutAuth?: boolean
withoutNotification?: boolean
withoutInterceptors?: boolean
}
export const useApi = (
type: APITypes,
actions?: DispatchActions,
options?: Options
): AxiosInstance => {
const mockApiReqInterceptor = async (config: AxiosRequestConfig) => {
const {
authState: { data: authData, tokenRefreshing },
} = store.getState()
if (tokenRefreshing?.fetching) {
setTimeout(() => mockApiReqInterceptor(config), 1000)
} else if (!options?.withoutAuth && authData?.accessToken && authData?.userInfo?.exp) {
if (authData?.userInfo.exp * 1000 - Date.now() <= 10000) {
store.dispatch(setRefreshingToken(true))
const { data } = await axios.post(`${AUTH_SERVICE_URL}token/renew`, {
refreshToken: authData?.refreshToken,
})
if (data?.accessToken) {
store.dispatch(setAccessToken(data.accessToken))
config.headers.Authorization = `Bearer ${data.accessToken}`
}
store.dispatch(setRefreshingToken(false))
} else {
config.headers.Authorization = `Bearer ${authData?.accessToken}`
}
}
return config
}
Moreover, lately, I've tried to implement an automatic log out after 15 minutes, however, when I dispatch the logout action, the app returns back to the login screen, however token refreshing triggers and the app tries to log in automatically, switching the screen to Home screen. In the router I am checking for the access token and based on whether the user has one, the navigator directs the user to the Home or Login screen. Something along these lines:
<NavigationContainer fallback={<Text>Loading...</Text>} ref={navigationRef}>
{authState?.accessToken ? (
<AuthorizedRoutes Stack={Stack} />
) : (
<AuthRouter....
</NavigationContainer>
Moreover, this is my logic for checking the inactivity:
const appState = useRef(AppState.currentState)
const [appStateVisible, setAppStateVisible] = useState(appState.current)
const {
profileState: { user, loading },
authState: { data: authData, rcData, tokenRefreshing },
} = useSelector((state: RootState) => state)
useEffect(() => {
if (authData?.userInfo.sub && !tokenRefreshing?.fetching) {
dispatch(getOrCreateProfileRequest(authData.userInfo.sub, locale.toUpperCase()))
}
}, [authData?.userInfo.sub, tokenRefreshing?.fetching])
useEffect(() => {
checkInactivity()
}, [appStateVisible])
const checkInactivity = async () => {
const prevInactivityTime = await AsyncStorage.getItem('inactivityTime')
if (
prevInactivityTime &&
differenceInMinutes(new Date(), new Date(JSON.parse(prevInactivityTime))) > 15
) {
AsyncStorage.setItem('inactivityTime', JSON.stringify(new Date()))
dispatch(signout())
}
const subscription = AppState.addEventListener('change', nextAppState => {
if (appState.current === 'active' && nextAppState.match(/inactive|background/)) {
AsyncStorage.setItem('inactivityTime', JSON.stringify(new Date()))
}
appState.current = nextAppState
setAppStateVisible(appState.current)
})
return () => {
subscription.remove()
}
}
Could anyone provide me with some assistance on this matter and explain what goes wrong here?