Adrian
Adrian

Reputation: 86

JWT auth process - frontend part

Im making a user authorization process with JWT tokens.

How does the flow look like?

  1. User logs in - gets an access token and a refresh token from a server, as a response
  2. Access token comes in json body and is saved in local storage. Refresh token comes in a httpOnly cookie.
  3. User can use getAllUsers method untill access token is valid.
  4. Whenever getAllUsers method returns 401 unauthorized (when access token expires), there is a request being sent to refresh token endpoint - getRefreshToken, which returns new access token that is being saved to local storage
  5. Refresh token expires and user is being logged out.

Whole flow in Postman works but i have got problem at frontend side.

Function getAllUsers works until access token expires.

Thats why I made a global function in a util file that checks if a response is 401 and if so, it sends a request to get a new access token and calls a function which returned that error. However it does not work.

I think that the problem is in getAllUsers function which immediately goes to catch block (when cant fetch list of users because of 401) and does not invoke that global function from util file. Console logs from both functions (getDataFromResponse, getRefreshToken) does not work so it does not even get there.

Any ideas??

API utils file

import { AxiosResponse } from "axios";
import { apiService } from "./api.service";

type ApiServiceMethods = keyof typeof apiService;

export const getDataFromResponse = async (
  response: AxiosResponse,
  funName: ApiServiceMethods,
  ...args: any
): Promise<any> => {
  if (response.status === 401) {
    console.log("error");
    await apiService.getRefreshToken();
    return await apiService[funName](args);
  }

  return response.data;
};

API Service:

import { getDataFromResponse } from "./api.utils";
import axios from "./axios";

type LoginArgs = {
  password: string;
  username: string;
};

const apiServiceDef = () => {
  const login = async (args: LoginArgs) => {
    try {
      const response = await axios.post("/login", {
        username: args.username,
        password: args.password,
      });

      const { data } = response;
      const { token } = data;

      localStorage.setItem("accessToken", token);

      return response;
    } catch (e) {
      throw new Error("Custom");
    }
  };

/* problem here */
  const getAllUsers = async () => {
    const Token = localStorage.getItem("accessToken");

    try {
      const response = await axios.get("/users", {
        headers: {
          Token,
        },
      });
      return await getDataFromResponse(response, "getAllUsers");
    } catch (e) {
      console.log(e);
    }
  };
/* problem here */

  const getRefreshToken = async () => {
    try {
      console.log("fetch new access token");

      const response = await axios.get("/refreshToken");

      
      if (response.status === 401) {
        localStorage.removeItem("accessToken");
        throw new Error("TokenExpiredError");
      }

      const { data } = response;
      const { token } = data

      localStorage.setItem("accessToken", token);

      return response;
    } catch (e) {
      console.log(e);
    }
  };

  return { login, getRefreshToken, getAllUsers };
};

export const apiService = apiServiceDef();

Upvotes: 4

Views: 149

Answers (1)

Alopwer
Alopwer

Reputation: 630

I usually use a wrapper around the async functions or just use axios interceptors (https://stackoverflow.com/a/47216863/11787903). Be sure that err.response.status is right property, not sure about that, but this solution should work for you.

const asyncWrapper = async (handler) => {
  try {
    return handler()
  } catch (err) {
    if (err.response.status === 401) {
      // refresh token then again call handler
      await refreshToken()
      return handler()
    }
  }
}
const getAllUsers = asyncWrapper(() => {
  const Token = localStorage.getItem("accessToken");

  return axios.get("/users", {
    headers: {
      Token,
    },
  });
});

Upvotes: 2

Related Questions