skip to Main Content

I am creating a react app that is implementing JWT Authentication. I have used createContext() to handle all of the authentication stuff. I will just show my update token method as this is what is causing the issue.

import { createContext, useState, useEffect, useRef, useCallback } from "react";
import jwt_decode from "jwt-decode";
import { useNavigate } from "react-router-dom";

const AuthContext = createContext();

export default AuthContext;

export const AuthProvider = ({ children }) => {
  let [user, setUser] = useState(() =>
    localStorage.getItem("authTokens")
      ? jwt_decode(JSON.parse(localStorage.getItem("authTokens")).access)
      : null
  );
  let [accessToken, setAccessToken] = useState(() =>
    localStorage.getItem("authTokens")
      ? JSON.parse(localStorage.getItem("authTokens")).access
      : null
  );
  let [refreshToken, setRefreshToken] = useState(() =>
    localStorage.getItem("authTokens")
      ? JSON.parse(localStorage.getItem("authTokens")).refresh
      : null
  );

  let navigate = useNavigate();
  let [loading, setLoading] = useState(true);

  let ref = useRef(true);

 
  let updateTokens = useCallback(async () => {
    if (refreshToken !== null) {
      let response = await fetch("/api/token/refresh/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ refresh: refreshToken }),
      });

      let data = await response.json();

      if (response.status === 200) {
        localStorage.setItem("authTokens", JSON.stringify(data));
        setAccessToken(data.access);
        setRefreshToken(data.refresh);
        setUser(jwt_decode(data.access));
      } else {
        setAccessToken(null);
        setRefreshToken(null);
        setUser(null);
        localStorage.removeItem("authTokens");
        navigate("/login");
      }
    }

    if (loading) {
      setLoading(false);
    }
  }, [loading, refreshToken, navigate]);

  let contextData = {
    // Variable
    user: user,
    accessToken: accessToken,
    refreshToken: refreshToken,
    loading: loading,

    // Functions
    loginUser: loginUser,
    logoutUser: logoutUser,
  };

  useEffect(() => {
    if (ref.current) {
      console.log("hello");
      if (loading) {
        updateTokens();
      }
      ref.current = false;
    }

    let fourMinutes = 1000 * 60 * 4;
    let interval = setInterval(() => {
      if (refreshToken) {
        updateTokens();
        console.log("run");
      }
    }, fourMinutes);
    return () => clearInterval(interval);
  }, [refreshToken, loading, updateTokens]);

  return (
    <AuthContext.Provider value={contextData}>{children}</AuthContext.Provider>
  );
};

So in the use effect, call updateToken on load, and then every 4 minutes to make sure the access token stays valid (expires every 5 minutes in the back end)

My issue arises when i need to fetch authenticated data on load for a page.

import React, { useEffect, useRef, useState, useContext } from "react";
import AuthContext from "../../context/AuthContext";

const Home = () => {
  let { accessToken } = useContext(AuthContext);

  let ref = useRef(true);
  let getRooms = async () => {
    let response = await fetch("api/room/", {
      method: "GET",
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    let data = await response.json();

    if (response.status === 200) {
      console.log(data);
    }
  };

  useEffect(() => {
    if (ref.current) {
      ref.current = false;
      getRooms();
    }
  });

  return <h1>Home</h1>;
};

export default Home;

Everything works fine, unless a logged in user loads the page and the stored access token is not valid. I am seeing this is because it calls the getRooms function before the authContext has refreshed the token, responding in a 400 bad request. I know the updateToken() method has been called as when i refresh the page, the i get a response 200 from the api with the data as it has now used the new access token.

Im just wondering if there is a way i can make sure the updateToken method in AuthContaxt has finished before fetching any new data on another page??

I followed a tutorial for the authContext, so if you seem to think there is a better way of handling the authentication/refresh of tokens please let me know what i can look into.

2

Answers


  1. I suggest you to use react-cookie module to save your authenticate token to cookie.
    https://www.npmjs.com/package/react-cookie

    using React query or Tanstack, you can update your original token with refresh token, and check out it.

    Login or Signup to reply.
  2. I see two solutions here

    1. You can add context.isCredentialsLoading prop and place it true by default and load data only after isCredentialsLoading === false

    2. I personally use credential revalidating inside fetch request, if detect that request response is unauthorized then revalidate credentials, logic is kinda like this

    function makeRequest(props) {
       return fetch(...)
    }
    
    let data = makeRequest()
    
    if(data.result === 400 || data.result == 403) {
       revalidateCredentials().then(() => data = makeRequest())
    }
    
    return data
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search