'"Uncaught (in promise) Error: Request failed with status code 403" instead of redirecting to login page
Instead of getting redirected to the login page i get a 403 error when my JWT Token expired. What i'm trying to accomplish is that when the token expires or there is any other issue that leads to the token not being valid it redirects to the login page. But when the token (line 22-25 inside App.js is where the code related to the redirect is).
AuthContext.js
import React, { useEffect, useState } from 'react'
import { API } from "../api"
import axios from "axios"
import { isAfter, isEqual, parseISO, sub } from 'date-fns'
export const AuthContext = React.createContext(null)
export function AuthContextProvider({ children }) {
const [accessTokenExpiration, setAccessTokenExpiraton] = useState(undefined);
const getUser = () => {
return JSON.parse(localStorage.getItem('user'))
}
const isLoggedIn = () => {
return localStorage.getItem('user') !== null
}
const [user, setUser] = useState(() => {
return isLoggedIn() ? getUser() : null;
})
const [shouldGoToLogin, setShouldGoToLogin] = useState(() => {
if (!user || !user.access_token || !user.refresh_token) {
return true;
}
return false;
})
const logout = async () => {
if (!user) {
return;
}
const { access_token } = user;
localStorage.removeItem('user')
setUser(null);
return axios.post(API.auth.logout, {
headers: {
"Authorization": `Bearer ${access_token}`,
"Content-Type": "application/json"
},
withCredentials: true
});
}
const login = async (values) => {
console.log(values);
const correctedValues = { ...values, username: values.email };
return axios.post(API.auth.login, correctedValues)
.then(res => {
const data = res.data;
processApiData(data);
})
}
const refreshToken = async () => {
const user = getUser();
const redirectToLogout = () => {
localStorage.clear(); // Clear our localStorage
setShouldGoToLogin(true);
};
if (!user) { // No user
redirectToLogout();
}
console.log(API.auth.refreshToken);
const resp = await fetch(API.auth.refreshToken, {
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({'refresh': user?.refresh_token}),
method: "POST",
withCredentials: true
})
console.log("status", resp.status);
if (resp.status === 200) {
const data = await resp.json(); // Convert to JSON
console.log("refresh token data", data);
processApiData(data);
} else {
redirectToLogout();
}
}
const resetPassword = async (values) => {
return axios.post(API.auth.passwordReset, values);
}
const processApiData = (resp) => {
let newUser = { ...user, ...resp };
delete(newUser.user); // Delete the user sub-object since we merged that directly into the top-level object
saveUser(newUser); // Save the user
const { access_token_expiration } = newUser;
if (access_token_expiration) {
console.log("have expiration", access_token_expiration);
const nextExpiration = parseISO(access_token_expiration); // Convert from ISO 8601 to a Date Object
const earlyRefreshTime = sub(nextExpiration, { minutes: 55 }); // Do an hourish early
setAccessTokenExpiraton(earlyRefreshTime); // Set the upcoming expiraton
}
}
const saveUser = async (newUser) => {
localStorage.setItem('user', JSON.stringify(newUser))
setUser(newUser)
}
const signup = async (values) => {
return axios.post(API.auth.signup, values);
}
useEffect(() => {
if (!user) {
return;
}
const interval = setInterval(()=> {
if(!user){
return false;
}
if (accessTokenExpiration) {
const now = new Date(); // Get the current time
console.log(now);
console.log(accessTokenExpiration);
if (isAfter(now, accessTokenExpiration) || isEqual(now, accessTokenExpiration)) { // If we are late to the party or the stars have aligned
refreshToken(); // Refresh the token
}
} else { // We do not have an access token expiration yet
refreshToken(); // Refresh the token immediately so we get a time
}
}, 1000 * 15)
return ()=> clearInterval(interval)
}, [accessTokenExpiration, refreshToken, user])
return (
<AuthContext.Provider value={{
getUser,
isLoggedIn,
logout,
login,
resetPassword,
signup,
user,
shouldGoToLogin
}}>
{children}
</AuthContext.Provider>
)
}
App.js
import React, { useContext } from "react";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate
} from "react-router-dom";
import { AuthContext, AuthContextProvider } from './contexts/AuthContext'
import { FacilityDetail } from './components/FacilityDetail'
import { Settings } from './components/Settings'
import { Login } from './components/Login'
import { Reset } from './components/Reset'
import { Navbar } from "./components/Navbar";
import { FacilityUpdate } from "./components/FacilityUpdate";
import { Signup } from "./components/Signup"
import { ConfirmEmail } from "./components/ConfirmEmail";
import { FacilityList } from './components/FacilityList'
import { ResetConfirm } from './components/ResetConfirm'
import { Home } from "./components/Home";
const EnforceAuthOnRoute = ({ children }) => {
const { shouldGoToLogin, user } = useContext(AuthContext)
return user && !shouldGoToLogin ? children : <Navigate replace to="/login" />
}
export default function App() {
return (
<Router>
<AuthContextProvider>
<div>
<Navbar />
{/* A <Routes> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<div className="max-w-8xl mx-auto px-4 sm:px-6 md:px-8">
<Routes>
<Route path="/about" element={<About/>} />
<Route path="/users" element={<Users />} />
<Route path="/facilities/:id" element={<EnforceAuthOnRoute><FacilityDetail /></EnforceAuthOnRoute>} exact />
<Route path="/facilities/:id/update" element={<EnforceAuthOnRoute><FacilityUpdate /></EnforceAuthOnRoute>} exact />
<Route path="/settings" element={<EnforceAuthOnRoute><Settings /></EnforceAuthOnRoute>} exact />
<Route path="/login" element={<Login />} exact />
<Route path="/signup" element={<Signup />} exact />
<Route path="/reset" element={<Reset />} exact />
<Route path="/password-reset/confirm/:uid/:token" element={<ResetConfirm />} exact />
<Route path="/accounts/confirm-email/:key" element={<ConfirmEmail />} exact />
<Route path="/facilities" element={<EnforceAuthOnRoute><FacilityList /></EnforceAuthOnRoute>} exact />
<Route path="/" element={<Home />} exact />
</Routes>
</div>
</div>
</AuthContextProvider>
</Router>
);
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
FacilityList.js
import { useContext, useEffect, useState } from "react"
import axios from "axios"
import { NavLink, Link } from "react-router-dom"
import { useParams } from "react-router"
import { API } from "../api"
import { AuthContext } from "../contexts/AuthContext"
function FacilityListItem({ facility }) {
return (
<div className="table-row border-t">
<div className="table-cell pl-8"><NavLink to={`/facilities/${facility.id}`}><h3 className="text-2xl text-gray-800 font-semibold">{facility.Name}</h3><p>{facility.AddressInfo}</p></NavLink></div>
<div className="table-cell"><img src={verified} alt="verified badge" className="arrowbadge" width={15}/><span className="verifiedText">verified</span></div>
<div className="table-cell"><NavLink to={`/facilities/${facility.id}/update`}><img src={pen} alt="edit facility" className="editfacil" width={20}/></NavLink></div>
<div className="table-cell pr-8 text-right"><NavLink className="btn btn-gr previewbtn" to={`/facilities/${facility.id}`}><img src={logo} alt="preview facility" width={20} className="previewfacil" /> Preview Facility</NavLink></div>
</div>
)
}
export function FacilityList() {
const [facilities, setFacilities] = useState(null)
const { id } = useParams()
const { user } = useContext(AuthContext);
const { access_token } = user;
console.log("access_token", access_token);
useEffect(() => {
axios.get(API.facilities.list, {
headers: {
"Authorization": `Bearer ${access_token}`
},
withCredentials: true,
})
.then(res => {
const restOfFacilities = res.data
setFacilities(restOfFacilities)
})
}, [id, access_token])
return (
<div>
<div className="table w-full">
<div className="table-header-group">
<div className="table-row">
<div className="table-cell text-left pl-8"><h1 className="">Facilities</h1></div>
<div className="table-cell text-left"></div>
<div className="table-cell text-left"></div>
<div className="table-cell text-right pr-8"><Link className="btn btn-g" to="/add-facility">Add Facility</Link></div>
</div>
<div className="table-row">
<div className="table-cell text-left pb-3 pl-8">Facility Name</div>
<div className="table-cell pb-3 text-left">Status</div>
<div className="table-cell text-left"></div>
<div className="table-cell text-left"></div>
</div>
</div>
<div className="table-row-group">
<div className="pl-8 pb-3">
{!facilities && "Loading Facilities..."}
</div>
{facilities && facilities.map(facility => {
return <FacilityListItem key={facility.id} facility={facility} />
})}
</div>
</div>
</div>
);
}
Solution 1:[1]
From which line you are getting that error?
So are you checking the token every 15 seconds?
I would suggest doing that in a different way. You can use Axios interceptors for this purpose. Whenever you send any request with an access token and if your server rejects your request because of the expiration of the token then the interceptor will intercept that request and automatically send a request for token refresh and then automatically resend the previously failed request. Otherwise, if you check your server every 15 sec then your API server will be overloaded if there are huge traffics.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | SKG |
