-1

i have made a custom hook to fetch data from api, but the token only valid for 5 second. so i made this hook the problem is when i call the hooks from my page it called many time and the refresh token already expired

when i access the api i will check the response first if the token invalid i tried to refresh my token using handleRefreshToken

nb : im using useContext for my state management

import React, {useEffect, useState, useContext} from 'react';
import {View, StyleSheet} from 'react-native';
import {AuthContext} from '../Auth/Context';

import AsyncStorage from '@react-native-community/async-storage';
import {urlLogin, URLREFRESHTOKEN} from '../Configs/GlobaUrl';

const FetchData = () => {
  const {loginState, authContext} = useContext(AuthContext);
  const [data, setData] = useState([]);
  const [message, setMessage] = useState('');
  const [loading, setIsLoading] = useState(false);
  const {dispatchRefreshToken} = authContext;

  const handleRefreshToken = async (callbackUrl, callbackBody) => {
    const refBody = {
      client_id: loginState.ipAddress,
      ipAddress: loginState.ipAddress,
      employee_id: loginState.userData.Pegawai_Id,
      jwttoken: loginState.userToken,
      refresh_tokenn: loginState.refreshToken,
    };
    console.log('======refreshtokencalled==========');
    console.log(refBody.refresh_tokenn, '<=refresh token');
    console.log(refBody.jwttoken, '<=jwt token');
    let response = await fetch(URLREFRESHTOKEN, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(refBody),
      redirect: 'follow',
    });
    let result = await response.json();
    console.log(result, ' ini result');
    if (
      result.item3 !== 'refresh token gagal' &&
      result.item3 !== 'refresh token sudah tidak berlaku'
    ) {
      let refresh = result.item2;
      let token = result.item1;
      // the backend doesnt send any succes / error code only item1 for token,       //item2 refresh token and item3 for error
      dispatchRefreshToken(token, refresh);
      await AsyncStorage.setItem('refreshToken', refresh);
      await AsyncStorage.setItem('token', token);
      return getData(callbackUrl, callbackBody);
    } else {
      return null;
    }
  };

  const getData = async (url, body) => {
    setIsLoading(true);
    let result;
    try {
      let response = await fetch(url, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
          Authorization: `Bearer ${loginState.userToken}`,
        },
        body: JSON.stringify(body),
        redirect: 'follow',
      });
      if (response.status == '401') {
        let refreshResult = await handleRefreshToken(url, body);
        console.log(refreshResult);
      } else {
        result = await response.json();
        console.log(result);
        console.log(loginState.refreshToken);
        if (result.code == '1') {
          setData(result.data);
          setIsLoading(false);
        } else {
          throw result;
        }
      }
    } catch (err) {
      setData([]);
      console.log(err, 'masuk error usefetchbybutton');
      console.log(err.message, err.code);
      setIsLoading(false);
      setMessage(err);
    }
  };
  return {
    data: data,
    message: message,
    loading: loading,
    getData: getData,
  };
};

export default FetchData;

this is my dispatch refresh token

const authContext = useMemo(
    () => ({
      logIn: async (token, userData, refreshToken) => {
        console.log(token, '<>', refreshToken, 'ini memoisa');
        dispatch({
          type: 'LOGIN',
          token: token,
          userData: userData,
          refreshToken: refreshToken,
        });
      },
      logOut: () => {
        AsyncStorage.clear((error) => {
          console.log(error);
        });
        dispatch({type: 'LOGOUT'});
      },
      dispatchRefreshToken: (userToken, refreshToken) => {
        console.log(refreshToken, '=refresh dispatch=');
        console.log(userToken, '=userToken dispatch=');
        dispatch({
          type: 'REFRESHTOKEN',
          userToken: userToken,
          refreshToken: refreshToken,
        });
      },
    }),
    [],
  );

my reducer function

  const loginReducer = (prevState, action) => {
    switch (action.type) {
    some case ...
     case 'REFRESHTOKEN':
        return {
          ...prevState,
          userToken: action.userToken,
          refreshToken: action.refreshToken,
        };
          }
  };
QrQr
  • 141
  • 1
  • 13
  • Sorry, but why do you set your token expiration for only 5 sec? – Bora Sumer Jan 15 '21 at 04:16
  • its requested by my client, it seems they are afraid if someone stole the token (?) – QrQr Jan 15 '21 at 07:01
  • Well that means you are gonna send at least 2 additional requests to auth server to check the JWT and refresh it. That is too much load on an Auth server. Imagine you have 1000 active users. Every 5 secs you the server is gonna take 10.000 request only for handling the auth logic. But if you really wanna go that way. The next answer should do the trick for you. – Bora Sumer Jan 15 '21 at 17:16

1 Answers1

0

Use recursion. The pseudo code is as follows

const getData = async (args, times) => {
    // try to fetch data
    const data = await Api.fetch(args);
    // if token need to be refreshed.
    if (check401(data)) {
        // Use variable times to prevent stack overflow.
        if (times > 0) {
           // refresh the token
           await refreshToken()
           // try again
           return getData(args, times - 1);
        } else {
           throw new Error("The appropriate error message")
        }
    }
    return dealWith(data)
}

The logical above can be encapsulated to all your api. Like this

const wrapApi = (api) => {
    const wrappedApi = async (args, times) => {
        const data = await api(args);
        // if token need to be refreshed.
        if (check401(data)) {
            // Use variable times to prevent stack overflow.
            if (times > 0) {
               // refresh the token
               await refreshToken()
               // try again
               return wrappedApi(args, times - 1);
            } else {
               throw new Error("The appropriate error message")
            }
        }
        return dealWith(data)
    }
    return wrappedApi;
}
vipcxj
  • 840
  • 5
  • 10