简体   繁体   中英

Logout user when jwt expires in react/redux

I am using localstorage to store a jwt in my react/redux app for authentication. I am trying to have the user get logged out if their token is expired. One way I have gotten this to work would be to use my authMiddleware.js on the backend to send the error.message and set the payload equal to an error variable, and then in a useEffect if the error is jwt expired I run my logout function (which just clears the localstorage) and reset the error to null. Like the following:

authMiddleware.js:

const jwt = require("jsonwebtoken");
const User = require("../models/user");

const protect = async (req, res, next) => {
  let token = req.body.token;

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    req.user = await User.findById(decoded.id).select("-password");

    next();
  } catch (error) {
    res.status(400).json({ message: error.message });
  }


};

module.exports = { protect };

Portfolio.slice:

export const getCurrentHoldings = createAsyncThunk(
  "/portfolio/getCurrentHoldings",
  async (value, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      const userID = thunkAPI.getState().auth.user._id;
      const newObj = {
        token: token,
        userID: userID,
      };
      let url = `http://localhost:3001/api/portfolio/getCurrentHoldings`;
      const response = await axios.post(url, newObj);
      console.log("New request ran in getCurrentHoldings");
      return response.data;
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

const initialState = {
  error: null,
  status: "idle",
  holdings: null,
  jwtError: null,
};
export const portfolioSlice = createSlice({
  name: "portfolio",
  initialState,
  reducers: {
    reset: (state) => initialState,
  },
  extraReducers(builder) {
    builder
      .addCase(getCurrentHoldings.pending, (state, action) => {
        state.status = "loading";
      })
      .addCase(getCurrentHoldings.fulfilled, (state, action) => {
        state.status = "success";
        state.holdings = action.payload;
        console.log(state.holdings);
      })
      .addCase(getCurrentHoldings.rejected, (state, action) => {
        state.status = "failed";
        state.error = action.error.message;
        state.jwtError = action.payload;
      })
  },
});

Portfolio.js:

 useEffect(() => {

    if (jwtError == "jwt expired") {
      dispatch(logout());
      dispatch(reset());
    }

  }, [jwtError]);

The problem with this solution is I have multiple slices that I would need to add a similar variable for each and the useEffect would grow and start looking like:

 useEffect(() => {
    
        if (jwtError == "jwt expired") {
          dispatch(logout());
          dispatch(reset());
        }

        if (jwtError1 == "jwt expired") {
          dispatch(logout());
          dispatch(reset1());
        }

        if (jwtError2 == "jwt expired") {
          dispatch(logout());
          dispatch(reset2());
        }

      

      }, [jwtError, jwtError1, jwtError2]);

Thus this solution is not scalable, one way I thought to fix this was having some of the slices access data from another slice so at least the useEffect would be reduced to the original size and be scalable but I found that reducers only have access to the state they own Thus looking into this problem more I found a couple of posts related to this and I got suggestions to either 1. use cookies instead of localstate 2. Use middleware and 3. use instance.interceptors

Now one question I had for all of the above solutions is if this issue should be solved on the frontend, backend, or both? Since the middleware and instance.interceptors solution looks like its solved on the frontend. I would like to know if this is a security risk and if you should also use a backend middleware aswell.

I also would like to know if using cookies instead of useState is just a best practice, but either way I would like to implement this with localstorage also.

And finally I would like a best practices for how this should be done with redux in react and what the code might look like with my setup.

Update: The solution I am trying currently is redux middleware and I am unable to decode the token on the frontend, installing jsonwebtoken in the react project results in a an error: Module not found: Error: Can't resolve 'crypto' in myfile . As far as I know I will need this library on the frontend if I am to decode it as suggested in middleware link.

Thus looking into this problem more I found a couple of posts related to this and I got suggestions to either 1. use cookies instead of localstate 2. Use middleware and 3. use instance.interceptors

The suggestions you got are great, I would definitely use an http-only cookie to store the token (safer because separated from the JS runtime, no malicious js code can ever see it) and a redux middleware and an axios interceptor.

The solution I am trying currently is redux middleware and I am unable to decode the token on the frontend, installing jsonwebtoken in the react project results in a an error: Module not found: Error: Can't resolve 'crypto' in myfile. As far as I know I will need this library on the frontend if I am to decode it as suggested in middleware link.

If you're using https://www.npmjs.com/package/jsonwebtoken , this seems to be a Node.js-only implementation, not meant for the browser. Looking at JWT Verify client-side? suggests that https://github.com/auth0/jwt-decode should be sufficient for you in the browser.

If you don't go with a http-only cookie based solution, there is a more elegant solution: You can decode the JWT, read the expiration time, and then schedule a function to run a few seconds before the expiration time (via setInterval ) that refreshes the token. If this fails, the function can dispatch an action that logs the user out and resets the redux state to what you need it to be. This is a more proactive solution, as you don't need to wait until a request to the backend fails because of an expired token - after all you know when it will expire.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM