英文:
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 "react-router-dom";
import jwt_decode from "jwt-decode";
export default function ProtectedRoutes() {
var token = sessionStorage.getItem("token");
var decoded = jwt_decode(token);
// console.log(decoded);
// console.log(decoded.exp)
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>;
<!-- end snippet -->
authService.js
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-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;
};
<!-- end snippet -->
authSlice.js
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-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,
}
// User Login
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;
<!-- 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 = () => {
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();
cont timeout = expiryTime - currentTime;
const onExpire = () => {
dispatch(logout());
navigate("/auth/login");
};
if (timeout > 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 () => {
clearTimeout(timerRef);
};
}, [dispatch, navigate, token]);
...
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论