React.js protected route has a refresh problem - javascript

i have a problem here with my protected routes in react.js so
i go get the token item in the sessionStorage after that i validade if the token is valid if not returns null
so when i did my protected routes it protects the routes but when i have a valid token in Session Storage if i refresh the page redirects me to the login
MOUNTCOMPONENT
state = {};
componentDidMount() {
try {
const jwt = sessionStorage.getItem('Token');
const user = jwtDecode(jwt);
console.log(user);
this.setState({ user });
} catch (e) {
return null;
}
}
Routes
render() {
const { user } = this.state;
return (
<div className='form'>
<BrowserRouter>
<Switch>
<Route exact path='/registo' component={registo} />
<Route path='/login' component={Login} />
<Route
path='/dashboardVet'
render={(props) => {
if (!user) return <Redirect to='/login' />;
return <Dashboard {...props} />;
}}
/>

Related

React JWT auth best practice

I have an App component that checks if I have a token and then loads the components for authorized ones, if not then only the login page is available.
const App: FC<any> = () => {
const { token } = useToken();
if (token && Object.values(user)) {
return <LoggedInComponent />;
}
return <LoggedOutComponent />;
};
const LoggedInComponent: FC<any> = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact={true} component={ForLoggedInUsers} />
<Redirect from={'*'} to={'/'} />
</Switch>
</BrowserRouter>
);
};
const LoggedOutComponent: FC<any> = () => {
return (
<BrowserRouter>
<Switch>
<Route path="/" exact={true} component={Login} />
<Redirect from={'*'} to={'/'} />
</Switch>
</BrowserRouter>
);
};
Token hook just writes the received token to session storage.
export default function useToken() {
const getToken = (): string => {
const tokenString: string | null = sessionStorage.getItem('X-JWT');
let userToken;
if (tokenString) {
userToken = JSON.parse(tokenString);
}
return userToken;
};
const [token, setToken] = useState(getToken());
const saveToken = (userToken: string): void => {
sessionStorage.setItem('X-JWT', JSON.stringify(userToken));
setToken(userToken);
};
return {
setToken: saveToken,
token
};
}
The problem is that if I write a non-valid token in sessionstorage ('X-JWT': 'not-valid') then I'm still redirected to the page as authorized. What should I do to recognize a valid token in session storage?

Redirect user after authorization where user previously clicked

Somehow I came to a problem of thinking of how to make that if the person clicks on a link, it should be redirected to sign-in page if not authorized and to that page if authorized. This sounds easy, but the problem is that I want to make that if the user redirected to one page where it should be authorized, the user authorizes and redirect to the same page as he clicked.
For now, I have a protected route that looks like this: (I have fromPath argument for next redirection but that does not work for me.)
const ProtectedRoute = ({
isAllowed,
redirectPath = "/sign-in",
fromPath = null,
children,
}) => {
const dispatch = useDispatch();
if (fromPath) dispatch(setURLPath(fromPath));
if (!isAllowed) {
return <Navigate to={fromPath} replace />;
}
return children ? children : <Outlet />;
};
And here how it looks from the App.js side:
<Suspense fallback={<Spinner />}>
<GlobalStyle />
<Routes>
<Route
path='/'
element={
<ProtectedRoute
isAllowed={roleLevel > 0}
/>
}
>
<Route path='bookings' element={<BookingsPage />} />
<Route path='single-booking/:id' element={<SingleBookingPage />} />
<Route path='documents' element={<DocumentsPage />} />
<Route path='my-account' element={<MyAccountPage />} />
<Route path='reservation' element={<ReservationPage />} />
</Route>
</Route>
<Route path='*' element={<NotFoundPage />} />
</Routes>
</Suspense>
The ProtectedRoute component should grab the current location object for the route being accessed and pass this in route state to the login route.
import { useLocation } from 'react-router-dom';
const ProtectedRoute = ({
isAllowed,
redirectPath = "/sign-in",
fromPath = null,
children,
}) => {
const location = useLocation();
const dispatch = useDispatch();
if (fromPath) dispatch(setURLPath(fromPath));
if (!isAllowed) {
return <Navigate to={fromPath} replace state={{ from: location }} />;
}
return children ? children : <Outlet />;
};
The login component should then access the passed route state and redirect back to the original route being accessed.
const location = useLocation();
const navigate = useNavigate();
...
const login = () => {
...
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
};
You can achieve this by passing some params (next_route) for example. and keep it along the process in signin so that when he finishes he can ge reredirected to the right place (next_route)

How to /WAIT A SECOND/ to navigate after user signed in in a restricted route?

So what I mean is, I have a functionality of after user signed in, navigate him/her to the homepage in a second, such as:
const handleSignIn = async (e) => {
e.preventDefault();
const user = await signIn(formData);
if (user) {
toast.success('Signed In!');
setTimeout(() => {
navigate('/');
}, 1000);
} else {
toast.error('Bad user credentials!');
}
};
But, I built a protected route, such as:
function App() {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
if (user) {
setUser(user);
} else {
setUser(null);
}
setIsLoading(false);
});
return unsubscribe;
}, []);
if (isLoading) return <Spinner />;
return (
<>
<Router>
<Routes>
<Route path='/' element={<PrivateRoute />}>
<Route index element={<Explore />} />
<Route path='/offers' element={<Offers />} />
<Route path='/profile' element={<Profile />} />
<Route path='/contact/:landlordId' element={<Contact />} />
<Route path='/create-listing' element={<CreateListing />} />
<Route path='/category/:categoryName' element={<Category />} />
<Route path='/category/:categoryName/:id' element={<Listing />} />
</Route>
<Route
path='/sign-in'
element={!user ? <SignIn /> : <Navigate to='/' />}
/>
<Route
path='/sign-up'
element={!user ? <SignUp /> : <Navigate to='/' />}
/>
<Route
path='/forgot-password'
element={!user ? <ForgotPassword /> : <Navigate to='/' />}
/>
</Routes>
{user && <Navbar />}
</Router>
<ToastContainer autoClose={1000} />
</>
);
}
export default App;
So the problem here is, whenever user signs in or signs out,the onAuthStateChanged gets executed, therefore it enforce app to re-render in order to understand who's logged in and logged out then behave accordingly, like restrict some routes and allow others.
When user signs in, I'd like to show some toast message BUT it triggers the App level state and before I show toast and navigate user, it automatically navigates user into the homepage due to the restriction, how can I wait a second before doing that?
Can't you just wait for x amount of time before calling setUser()? That is because setUser() updates the user value, which triggers navigation.
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
setTimeout(() => {
if (user) {
setUser(user);
} else {
setUser(null);
}
setIsLoading(false);
}, 1000);
});
return unsubscribe;
}, []);
Alternatively, use async/await with a custom wait async function that will avoid nesting:
const wait = async (d) => new Promise(r => setTimeout(d, r));
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (user) => {
setIsLoading(true);
await wait(1000);
if (user) {
setUser(user);
} else {
setUser(null);
}
setIsLoading(false);
});
return unsubscribe;
}, []);
Allow the SignIn, component to handle the redirect itself. You have two different pieces of logic that are redirecting to "/"
Instead of this:
<Route
path='/sign-in'
element={!user ? <SignIn /> : <Navigate to='/' />}
/>
Switch to this:
<Route
path='/sign-in'
element={<SignIn />}
/>
If that causes a problem (ie, if you want to bypass SignIn and go right to / if the user is already logged in), you should add a useEffect call to SignIn and call navigate('/'); there if necessary.

How to direct user to another page if the person not authorized?

I want to direct user to the login page if he/she is not authorized. I wrote the bottom code for this purpose. Root route is login page, and AdminPanel page is for admin. If data.success is true this means person is admin. But this code render 404 not found on http://localhost:3000/user url. How can I fix this issue?
const App = () => {
const [auth, setAuth] = useState(false);
const authControl = async () => {
try {
const res = await axios({
url: "https://localhost:44357/api/Auth/user",
withCredentials: true,
});
console.log(res.data.success);
if (res.data.success) setAuth(true);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
authControl();
}, []);
return (
<div>
<BrowserRouter>
<Route path="/" exact component={Login} />
<Route
path="/user"
render={() => {
auth ? <AdminPanel /> : <Redirect to="/" />;
}}
/>
<Route render={() => <h1>404 Not Found</h1>} />
</BrowserRouter>
</div>
);
};
you need to wrap the <Route />s in a <Switch /> from react router.
something like this:
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
</Switch>

Checking user validity before logging in

I got an unexpected behavior when a user login to app. I store a jwt token in a cookie. Before logging into app,I checked whether jwt token is exists and that token is valid with backend.
Below is my code. Here is app.js.
class App extends Component {
render() {
return (
<BrowserRouter>
<Layout>
<LoginRoute></LoginRoute>
</Layout>
</BrowserRouter>
);
}
}
LoginRoute component is as below.
const LoginRoute = withRouter(({ history }) => (
isValidUser() ? (
<Switch>
<Route path="/incident-reporting" component={Home}></Route>
<Redirect path='/' to='/incident-reporting/home' />
<NotFound />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Login}></Route>
<NotFound></NotFound>
</Switch>
)
))
Here is isValidUser()
const isValidUser = () => {
if (cookies.get("token")) {
let token = cookies.get("token")
axios.get("https://0.0.0.0:9094/auth/v1.0.0/user-info", {
headers: { 'Authorization': 'Bearer ' + token }
}).then(
response => {
return true;
}
).catch(
error => {
return false;
}
)
//return true
} else {
return false;
}
}
But I can't login in to app with valid token. isValidUser() return undefined before exucuting axios post request. How to solve this problem?
Your function should be asynchronous, either with ES6 async, returning promises or using callbacks. Otherwise calling the axios.get function just "falls through" and it returns the default undefined value.
You need to wait for the isValidUser to finish. To do that, you can do this:
const LoginRoute = withRouter(async ({ history }) => (
let isUserValid = await isValidUser()
isUserValid ? (
<Switch>
<Route path="/incident-reporting" component={Home}></Route>
<Redirect path='/' to='/incident-reporting/home' />
<NotFound />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Login}></Route>
<NotFound></NotFound>
</Switch>
)
))
And on the isValidUser:
const isValidUser = () => {
if (cookies.get("token")) {
let token = cookies.get("token")
return axios.get("https://0.0.0.0:9094/auth/v1.0.0/user-info", {
headers: { 'Authorization': 'Bearer ' + token }
}).then(
response => {
return true;
}
).catch(
error => {
return false;
}
)
//return true
} else {
return false;
}
}

Categories

Resources