Sorry if the question is badly phrased. I am working with React.js and Parse Server at school. The issue is the following:
I would like to have protected routes, just for users that are currently logged in, and I am trying to do it by using the .getSessionToken() Parse built-in function. I retrieve the token successfully, get access to the protected routes only if I am logged in, but as soon as I refresh the page, the route is inaccessible again because the token is updated.
I have a working solution, but it creates some issues at the moment. The issue is that even if I have access on the page, there's the following error in the console: No routes matched location "/page1"
Do you have any idea of how I can prevent the token from updating or any other more elegant solution for this issue?
My code is the following:
1.When logging in, I pass a setter using useState() through props, all the way to the App.js where the routes are. The only reason I set the token here is in order to navigate to the next page which is protected.
await Parse.User.logIn(username, password);
const currentUser = Parse.User.current();
const currentToken = currentUser.getSessionToken();
setSessionToken(currentToken);
navigate("/page1");
2.Here, I am checking if a user is currently in my local storage, and if yes, get the token associated with the user.
//sessionToken = the one I pass with props from step 1
const [sessionToken, setSessionToken] = useState();
//This makes sure that I get the token again and again if page is refreshed
const [refreshedToken, setRefreshedToken] = useState();
const authenticateUser = async () => {
if (Parse.User.current() !== null) {
const token = Parse.User.current().getSessionToken();
setRefreshedToken(token);
}
};
useEffect(async () => {
await authenticateUser();
}, []);
3.Since I have 2 state variables now, I make a conditional rendering by using both.
<Route
path="/login"
element={<LoginPage setSessionToken={setSessionToken} />}
/>
{(sessionToken !== undefined || refreshedToken !== undefined) && (
<Fragment>
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
<Route path="/page3" element={<Page3 />} />
I believe the issue here is that you are attempting to navigate to a path "/page1" before the component rendering the routes knows it should be rendering them, hence the No routes matched location "/page1" error.
You may want to create an AuthWrapper component that checked the sessionStorage and token and renders an Outlet for nested routes you want to protect, otherwise redirect the users to another page, i.e. to home or login.
import { Outlet, Navigate, useLocation } from 'react-router-dom';
const AuthWrapper = (props) => {
const location = useLocation();
... business logic to check sesstionStorage/token ...
return (sessionToken !== undefined || refreshedToken !== undefined)
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />
};
Now declare the routes, wrapping the routes you want to protect in the AuthWrapper layout component.
<Route
path="/login"
element={<LoginPage setSessionToken={setSessionToken} />}
/>
<Route element={<AuthWrapper /* pass any session props here */ />}>
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
<Route path="/page3" element={<Page3 />} />
</Route>
Related
I have a firebase onAuthStateChange and a set of private routes to be rendered with react router v6
useEffect(() => {
const fetchData = async () =>
await auth.onAuthStateChanged(async authUser => {
console.log('here in authuser')
if (authUser) {
await dispatch(setUser('SET_USER', authUser))
} else {
await dispatch(setUser('SET_USER', null))
}
})
fetchData()
}, [])
<Route path='/' element={<PrivateRoute user={users} />}>
<Route path='/courses/:id' element={<CourseDetails />} />
<Route
path='/'
element={<Courses emailId={users?.user?.email} />}
/>
<Route path='/enrolled' element={<Enrolled />} />
<Route path='/authored' element={<Authored />} />
<Route path='/users' element={<Users />} />
</Route>
In the protected route component I am checking if user is null then redirect user to login page else render the children.
if (user === null || user.user === null) {
console.log('Entered!!!!!')
return <Navigate to='/login' replace />
} else {
return children ? children : <Outlet />
}
On page refresh if I am logged in also I am redirected to login route because onauthstatechange has not finished executing. So user is null inside the
What is the right way to handle this situation and make sure that user gets navigated to the same page where reload happened.
You can create a component that handles the check for you.
So in your route you wrap your PrivateRoute with your route component like this :
<Route path='/courses/:id' element={<PrivateRoute><CourseDetails /></PrivateRoute> } />
So what this component does is check if the user is authed. If it is authed it will render the child component that is your route. If not it will redirect you to /login
they are talking about it in the docs
const PrivateRoute =({ children }: { children: JSX.Element }) => {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
// along to that page after they login, which is a nicer user experience
// than dropping them off on the home page.
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
My user variable is defined after the component mounts after signing up or logging in but when navigating to another page it is null. I have this as the AuthContext component after the user signs in.
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user=>{
setCurrentUser(user);
})
{console.log(currentUser)}
return unsubscribe
})
const value = {
signup,
currentUser,
logout,
authError,
login
}
return(
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
This is my App.js Component.
function App() {
return (
<Router>
<div className = "App">
<AuthProvider>
<Routes>
<Route path="/" element={<Home/>}/>
<Route element= {<AuthRoute/>}>
{/*
If user is already signed in re-direct to home
*/}
<Route exact path = "/sign_up" element = {<SignUp/>} />
<Route exact path = "/login" element= {<SignIn/>}/>
</Route>
<Route exact path="/new_post" element ={<AuthRouteIfLoggedOut>
<SignUp/>
</AuthRouteIfLoggedOut>}/>
<Route exact path="/about" element={<About/>}/>
</Routes>
</AuthProvider>
</div>
</Router>
);
}
AuthContext is my context component.
Any reason why when I try to navigate to another page the user is undefined or is "signed-out" on that page only? But when I navigate back to my main page it is logged in and works properly. Any reason why when navigating to different pages currentUser is null?
You have to cache user and provider logic for your routes. Here is an example Authetication in React
Here is my code, I am completely new to front end development can any one help me solve the issue, I want the user to be redirected to the login page if the user is not logged in but once he is logged in every thing should work fine but even when I click sign in, the login page shows up when I change the URL, The login page is appearing on every URL
Here's my code (i am new to front end pls dont judge)
import { useState } from 'react'
import { Route, Switch} from 'react-router-dom'
import Dashboard from "./pages/Dashboard";
import DatasetGenerator from "./pages/DatasetGenerator"
import Simulator from "./pages/Simulator"
function App() {
const [login, setlogin] = useState(true)
const [homepage, setHomepage] = useState(false)
const loginHandler = ()=>{
if(login == true){setlogin(false)
setHomepage(true)}
}
return (
<div>
{login && <SignIn loginHandler={loginHandler} />}
<Switch>
<Route path='/' exact>
{homepage && <Dashboard />}
</Route>
<Route>
{homepage && <DatasetGenerator path="/dataset-generator"/>}
</Route>
<Route path="/simulator">
{homepage && <Simulator />}
</Route>
</Switch>
</div>
)
}
export default App;
Seems like you want to conditionally render the login component OR the rest of your app. Since the login state and the homepage state appear to be mutually exclusive you probably don't need both (though perhaps we're missing some context).
return (
<div>
{login ? (
<SignIn loginHandler={loginHandler} />
) : (
<Switch>
<Route path='/' exact>
<Dashboard />
</Route>
<Route>
<DatasetGenerator path="/dataset-generator"/>
</Route>
<Route path="/simulator">
<Simulator />
</Route>
</Switch>
)}
</div>
)
A better solution would be to implement an auth workflow by creating authenticated route components that handle redirecting to a login route if not authenticated, otherwise allows the user to access the route.
Suppose I have an application, where you can login both as user or admin and I have this routes:
Log In
Register
Home
Admin panel (only available to admins)
My Profile (only available to users)
I would have a switch with routes like this:
<Switch>
<UnloggedRoute exact path='/login'>
<Login />
</Route>
<UnloggedRoute exact path='/register'>
<Register />
</Route>
<PrivateRoute exact path='/'>
<HomePage />
</Route>
<AdminRoute exact path='/admin'>
<Admin />
</Route>
<UserRoute exact path='/profile'>
<Profile />
</Route>
</Switch>
So inside PrivateRoute I have a conditional that checks if the user is logged in, if not, it redirects to /login. In AdminRoute it checks if the user is admin, if not, redirects to / and in UserRoute I have a conditional that checks if the user is a normal user, if not, redirects to /.
Finally, in the UnloggedRoute I have a check to see if the user is logged in. If it is, then it goes to /.
So my question is: should I leave the switch like it is, or should I not render the route "Profile" if the user is an admin? Like this:
user.isAdmin() && (
<Route exact path='/admin'>
<Admin />
</Route>
)
I feel like the first way is more declarative and easier to understand and manage, but I want to hear opinions...
So for my app, I did something pretty similar to how you did it in the first way. I had to deal with 4 different logins using 5 different routes being: MainRoute, TeacherRoute, StudentRoute, TutorRoute, and AdminRoute. For my uses, I simply had each one of these routes check the accountType (or isAdmin() in your case) within the route itself like so:
const PrivateRouteAdmin = ({ component: RouteComponent, ...rest }) => {
const { currentUser } = useContext(AuthContext);
//If login is good, allow access or redirect to login
if (!!currentUser) {
return (
<Route
{...rest}
render={routeProps =>
(currentUser.isAdmin()) ? (
<RouteComponent {...routeProps}
currentUser={currentUser} />
) : (
<Redirect to={"/incorrect-login"} />
)
}
/>
)
} else {
//Bad login
return (
<Route
{...rest}
render={() => (
<Redirect to={"/login"} />
)}
/>
)
}
}
export default PrivateRoutedAdmin;
As long as you check the routes like so within the routes, you don't need to do the isAdmin() within the Switch.
Also, to keep things a bit more organized within your Switch, I would do:
<AdminRoute exact path='/admin' component={Admin} />
This is my router:
const Router = () => {
const { user } = useContext(UserContext)
return (
<Switch>
{user.token ?
<>
<Route exact path="/" component={Wall}/>
<Route path="/profile" component={Profile}/>
<Route path="/discover" component={Discover}/>
<Route path="/settings" component={Settings}/>
<Route path="/post" component={Post}/>
<Route path="/deleteuser" component={DeleteUser}/>
<Route path="/profileimg" component={ProfileImg}/>
</> :
<>
<Route exact path="/" component={Splash}/>
<Route path="/auth" component={Auth}/>
<Route path="/create" component={Create}/>
<Route path="/forgot" component={Forgot}/>
</>
}
{!user.token && <Redirect to="/"/>}
<Route component={Notfound}/>
</Switch>
)
}
As you can see I have protected routs based on whether the user has a token in context.
In my app, I have a function logout() that clears local storage and state information which of course includes the user token.
My problem is I can't seem to find any documentation on the best way to redirect to "/" if the user is on a protected route and this logout() function is called.
To be clear what I want is this: logout() => <Redirect to="/"/> because there is no longer a token in state.
I have thought of multiple ways of doing this BUT I'm looking for the best or cleanest solution:
An approach using history - history.location.pathname === "/protected" && !user.token && <Redirect to="/"/> I'm sure there is a way to achive the same logic this with <Redirect/> in my router?
An approach using redirect in the JSX of every single protected component - {!user.token && <Redirect to="/"/>} I would rather not put this in the component JSX of every single protected route I have!
I could pass the history prop to the logout() function and do this - history && history.push("/") I would also rather not do this as I would need to pass the history prop every time I call logout() which feels rather messy.
You can either have a route <Route exact path="/logout" component={Logout}/>.
with
export default const Logout = () => {
useEffect(() => {
//remove token here
}, []);
return <Redirect to="/">
}
or a function logout with transition to your home like
const history = useHistory();
const logout = () => {
// remove tokens
history.push('/');
}
or both
const logout = () => {
// remove tokens
return <Redirect to="/"/>
}
I didn't do anything inside router.js or logout() in the end. I instead used an approach that obviously had escaped my mind at the time of this post.
In order to redirect with React, we need a way to trigger a rerender. I'm can't be sure if all of the methods discussed above do in fact rerender however the default way of achieving this in a React is to update Context or your store state if you're using Redux. Then based on that state, in a component you know is always going to be rendered, add some code to redirect.
This is my code in Nav:
const Nav = ({ history }) => {
const { user, setUser } = useContext(UserContext)
useEffect(() => { // Redirect if user.redirect === truthy.
user.redirect && history.push(user.redirect)
setUser({ ...user, redirect: false })
}, [user.redirect]) // eslint-disable-line react-hooks/exhaustive-deps
*** rest of the code in Nav ***
}