导航至登录页面当JWT令牌过期。

huangapple go评论83阅读模式
英文:

Navigate to login when JWT token expires

问题

ProtectedRoutes.js

import { Navigate, Outlet } from "react-router-dom";
import jwt_decode from "jwt-decode";

export default function ProtectedRoutes() {

  var token = sessionStorage.getItem("token");
  var decoded = jwt_decode(token);

  const isAuthActive = () => {
    const expiryTime = new Date(decoded.exp * 1000).toLocaleString(
      'en-sg'
    );
    const currentTime = new Date().toLocaleString('en-sg');
    if (expiryTime < currentTime) {
      sessionStorage.remove("token");
      return <div>{isAuthActive ? <Outlet /> : <Navigate to="/auth/login" />}</div>;
    }
    return true;
  }
  
  let userid = sessionStorage.getItem("token") == null ? false : true;
  return <div>{userid ? <Outlet /> : <Navigate to="/auth/login" />}</div>;
}

authService.js

const login = async (formData) => {
  const response = await axios.post(
    `${import.meta.env.VITE_BACKEND_STAGING_URL}/api/v1/auth/signin`,
    formData
  );

  if (response.data) {
    const { token } = response.data;
    sessionStorage.setItem("token", token);
  }
  return response.data;
};

const logout = async () => {
  const response = await axios.post(
    `${import.meta.env.VITE_BACKEND_STAGING_URL}/api/v1/auth/logout`
  );

  if (response.data) {
    sessionStorage.removeItem("token");
  }
  return response.data;
};

authSlice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import authService from "./authService";

const initialState = {
  user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : null,
  isLoading: false,
  isError: false,
  isSuccess: false,
}

export const login = createAsyncThunk("auth/signin", async (formData, thunkAPI) => {
  try {
    return await authService.login(formData);
  } catch (error) {
    const message =
      (error.response && error.response.data && error.response.data.message) ||
      error.message ||
      error.toString();
    return thunkAPI.rejectWithValue(message);
  }
});

export const logout = createAsyncThunk("auth/logout", async () => {
  await authService.logout();
});

const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    reset: (state) => {
      (state.isLoading = false),
        (state.isSuccess = false),
        (state.isError = false),
    },
  },

  extraReducers: (builder) => {
    builder
      .addCase(login.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(login.fulfilled, (state, action) => {
        state.isSuccess = true;
        state.user = action.payload;
      })
      .addCase(login.rejected, (state, action) => {
        state.isError = true;
        state.user = null;
      })
      .addCase(logout.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(logout.fulfilled, (state) => {
        state.isSuccess = true;
        state.user = null;
      });
  },
});

export default authSlice.reducer;
英文:

Hi I'm quite new to this. I'm trying to navigate the user to the login page when the JWT token expires but I'm struggling with it. I was thinking maybe I need to set a timeout when the login action is dispatched? The token expires in 8 hours ( what the backend dev said ). I used jwt-decode to decode the token. I would appreciate any tip

ProtectedRoutes.js

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import { Navigate, Outlet } from &quot;react-router-dom&quot;;
import jwt_decode from &quot;jwt-decode&quot;;
export default function ProtectedRoutes() {
var token = sessionStorage.getItem(&quot;token&quot;);
var decoded = jwt_decode(token);
// console.log(decoded);
// console.log(decoded.exp)
const isAuthActive = () =&gt; {
const expiryTime = new Date(decoded.exp * 1000).toLocaleString(
&#39;en-sg&#39;
);
const currentTime = new Date().toLocaleString(&#39;en-sg&#39;);
if (expiryTime &lt; currentTime) {
sessionStorage.remove(&quot;token&quot;);
return &lt;div&gt;{isAuthActive ? &lt;Outlet /&gt; : &lt;Navigate to=&quot;/auth/login&quot; /&gt;}&lt;/div&gt;;
}
return true;
}
let userid = sessionStorage.getItem(&quot;token&quot;) == null ? false : true;
return &lt;div&gt;{userid ? &lt;Outlet /&gt; : &lt;Navigate to=&quot;/auth/login&quot; /&gt;}&lt;/div&gt;;

<!-- end snippet -->

authService.js

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const login = async (formData) =&gt; {
const response = await axios.post(
`${import.meta.env.VITE_BACKEND_STAGING_URL}/api/v1/auth/signin`,
formData
);
if (response.data) {
const { token } = response.data;
sessionStorage.setItem(&quot;token&quot;, token);
}
return response.data;
};
const logout = async () =&gt; {
const response = await axios.post(
`${import.meta.env.VITE_BACKEND_STAGING_URL}/api/v1/auth/logout`
);
if (response.data) {
sessionStorage.removeItem(&quot;token&quot;);
}
return response.data;
};

<!-- end snippet -->

authSlice.js

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import { createSlice, createAsyncThunk } from &quot;@reduxjs/toolkit&quot;;
import authService from &quot;./authService&quot;;
const initialState = {
user: localStorage.getItem(&#39;user&#39;) ? JSON.parse(localStorage.getItem(&#39;user&#39;)) : null,
isLoading: false,
isError: false,
isSuccess: false,
}
// User Login
export const login = createAsyncThunk(&quot;auth/signin&quot;, async (formData, thunkAPI) =&gt; {
try {
return await authService.login(formData);
} catch (error) {
const message =
(error.response &amp;&amp; error.response.data &amp;&amp; error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
});
export const logout = createAsyncThunk(&quot;auth/logout&quot;, async () =&gt; {
await authService.logout();
});
const authSlice = createSlice({
name: &quot;auth&quot;,
initialState,
reducers: {
reset: (state) =&gt; {
(state.isLoading = false),
(state.isSuccess = false),
(state.isError = false),
},
},
extraReducers: (builder) =&gt; {
builder
.addCase(login.pending, (state) =&gt; {
state.isLoading = true;
})
.addCase(login.fulfilled, (state, action) =&gt; {
state.isSuccess = true;
state.user = action.payload;
})
.addCase(login.rejected, (state, action) =&gt; {
state.isError = true;
state.user = null;
})
.addCase(logout.pending, (state) =&gt; {
state.isLoading = true;
})
.addCase(logout.fulfilled, (state) =&gt; {
state.isSuccess = true;
state.user = null;
});
},
});
export default authSlice.reducer;

<!-- end snippet -->

答案1

得分: 1

如果你希望在令牌过期后立即取消用户身份验证并重定向到登录页面,我建议使用useEffect钩子回调来检查当前令牌值的过期时间,并在“会话”期间或在应用程序启动/挂载时如果令牌已过期,则有条件地设置一个超时来在令牌过期时注销并重定向用户。

示例:

const App = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch();

  const token = useSelector(state => state.auth.user.token);

  useEffect(() => {
    let timerRef = null;

    const decoded = jwt_decode(token);

    const expiryTime = (new Date(decoded.exp * 1000)).getTime();
    const currentTime = (new Date()).getTime();

    const timeout = expiryTime - currentTime;
    const onExpire = () => {
      dispatch(logout());
      navigate("/auth/login");
    };

    if (timeout > 0) {
      // 令牌未过期,设置未来的超时以注销并重定向
      timerRef = setTimeout(onExpire, timeout);
    } else {
      // 令牌已过期,注销并重定向
      onExpire();
    }

    // 在组件卸载或令牌状态更改时清除任何正在运行的定时器
    return () => {
      clearTimeout(timerRef);
    };
  }, [dispatch, navigate, token]);

  // ...
};
英文:

If you would like the user to be deauthenticated and redirected to log in again as soon as the token expires then I would suggest using a mounting useEffect hook callback to check the current token value's expiration and conditionally set a timeout when the token will expire during a "session" or if as soon as the app starts/mounts it is already expired to log out and redirect the user.

Example:

const App = () =&gt; {
  const navigate = useNavigate();
  const dispatch = useDispatch();

  const token = useSelector(state =&gt; state.auth.user.token);

  useEffect(() =&gt; {
    let timerRef = null;

    const decoded = jwt_decode(token);

    const expiryTime = (new Date(decoded.exp * 1000)).getTime();
    const currentTime = (new Date()).getTime();

    cont timeout = expiryTime - currentTime;
    const onExpire = () =&gt; {
      dispatch(logout());
      navigate(&quot;/auth/login&quot;);
    };

    if (timeout &gt; 0) {
      // token not expired, set future timeout to log out and redirect
      timerRef = setTimeout(onExpire, timeout);
    } else {
      // token expired, log out and redirect
      onExpire();
    }

    // Clear any running timers on component unmount or token state change
    return () =&gt; {
      clearTimeout(timerRef);
    };
  }, [dispatch, navigate, token]);

  ...
};

huangapple
  • 本文由 发表于 2023年7月17日 15:23:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76702258.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定