0

So I have this app that need to refresh its JWT token when coming from the background. I'm not sure if this is the best way to handle token refresh but I want this to work so I can improve the mechanism later on. My refresh token lives for 365 days and my access token lives for 12 hours.

    import React, {useEffect, useState} from "react";
    import {AppState, Platform} from "react-native";
    import SplashScreen from "react-native-splash-screen";
    import {AppRouter} from "./AppRouter";
    import {addBackgroundHandler} from "./services/RoutingHelperService";
    import {JwtToken} from "./types/types";
    import {getTokenFromDevice} from "./services/StorageService";
    import {refreshJwtToken} from "./services/AuthService";
    import useDidMountEffect from "./useDidMountEffect";

    export function App() {
      const [jwtToken, setJwtToken] = useState<JwtToken | null>(null);
      const [appInitiated, setAppInitiated] = useState(false);

      useEffect(() => {
        appStartup().then(() => console.log("Successfully started app"));

        AppState.addEventListener("change", refreshTokenFromBackground);
        // Platform specific handlers
        if (Platform.OS === "android") {
          addBackgroundHandler();
        }
        async function appStartup() {
          const token = await getTokenFromDevice();
          if (token) {
            setJwtToken(token);
          }
          SplashScreen.hide();
        }
      }, []);

      useDidMountEffect(() => {
        if (!appInitiated) {
          setAppInitiated(true);
        }
      }, [jwtToken]);

      async function refreshTokenFromBackground() {
        if (AppState.currentState === "active" && jwtToken) {
          setJwtToken(await refreshJwtToken(jwtToken.refresh_token));
        }
      }

      return <AppRouter />;
    }

So every first time the app boots, I add the appstate event listener (Which is called "change") that listens to the app going to the background or the app going to the foreground (state is called 'active' in this case)

Now when I come from the background, the app successfully gets a new JWT token. But when I go to the background again, its send the non-updated refresh token. Now, I know that state needs to time update, but here is what I find strange: even if I wait half a minute before going to the background again, it still sends the old refresh token. I can see the token change when I put it in a component.

Does anyone know what I'm doing wrong? Any advice is highly appreciated :)

BTW: This is the component useDidMomentEffect which is basicly useEffect which only listens to the an value changing instead of on mount and on change.

  import { useEffect, useRef } from 'react';

  const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
      if (didMount.current) func();
      else didMount.current = true;
    }, deps);
  };

  export default useDidMountEffect;

Update 1:

I actually found out that adding this eventhandler where it is now, resulting in jwt token to be always null. I can do calls with a valid jwt token but inside the refresh function, the token is null. So my problem now actually changed, now I wonder why this function can't access the state of my app.

Update 2 - Answer

So I discovered that useState inside the listener is not updated with its state, the solution is to use useRef. Now I can go to the background and to the foreground 10 times in 15 seconds and I never get a invalid refresh token. I found the answer here because i suspected the listener of being the problem: Wrong React hooks behaviour with event listener

SilentLucidity
  • 340
  • 1
  • 16
  • could you not just use the refresh token to get a new access token at the point of a request fail due to expiry? so it could just be handled as part of the request flow actually when it is needed? – scgough Dec 13 '19 at 10:29
  • @scgough Thats the logic i used before, but now I'm using a promise.all to do six calls simultaneously, fast. That causes six fails in microseconds, six refresh token calls being fired resulting in lock errors in the database. These are caught and handled well by the backend, but I think that it should be app logic that needs to prevent those deadlocks in this specific code. – SilentLucidity Dec 13 '19 at 10:53
  • Ah ok - bit more context there. I see why you would want to automate that silently in the background. Would there be noticeable friction if you had an initial preceeding single call to check the state of the tokens at the point of an action if the app has been in the background for N time....like a shorter timeout you could store and check exists?That way you're keeping the token refresh at the point of an action keeping requests down. – scgough Dec 13 '19 at 11:07
  • I think this is what you need https://www.naroju.com/how-to-refresh-jwt-tokens-automatically-in-react-native/ – Naroju Jun 04 '20 at 05:27

0 Answers0