Redirecting when user is authenticated - reactjs

I am trying to build a role based access control React app.
My vision was that when the App mounts, it checks if user token exists. If it does, it runs checkAuthToken() and sets the state accordingly.
Where I am struggling is: redirection doesn't work as I expect it to.
Here is my code:
in App.js
function App() {
const { isAuthenticated, user } = useSelector(state => {
return state.userState;
});
const dispatch = useDispatch();
useEffect(() => {
checkAuthToken();
}, []);
return (
<Router>
<Switch>
<Route exact path='/'>
{!isAuthenticated ? (
<Redirect to='/login'/>
) : (
<Redirect to={`/${user.role}`} />
)}
</Route>
<Route
path="/login"
render={() => {
return <Login />;
}}
/>
<Route
path="/admin"
render={() => {
return <AdminDashboard user={user} />;
}}
/>
<Route
path="/staff"
render={() => {
return <OrderMenu user={user} />;
}}
/>
<Route component={ErrorPage} />
</Switch>
</Router>
);
}
export default App;
My understanding is React rerenders when state or props change. When I get the updated state from Redux, the app component should rerender and thus go through the isAuthenticated check and redirect accordingly, but i'm being proven wrong.
Currently, if there's no token, it redirects to Login route. After user logs in, token is set in localStorage. So to test it out, I close and open a new tab, try to go to path / expecting it to redirect me to /[admin|staff] route since checkAuthToken would successfully set the state but redirect doesn't work and just lands on /login. However, I could access /[admin|staff] if I type in manually.

Related

How do I correctly implement a redirect?

I made it so that if the user is already logged in and he tried to go to the login page, then he is redirected to the main page. But when sign out, the redirection occurs only after the page is refreshed (also when sign in). How to fix it?
route:
const isLoggedIn = localStorage.getItem("token");
return (
<BrowserRouter>
<Route path={'/'} component={Home} />
<Route path={'/sign-in'} exact render={() => (isLoggedIn ? (<Redirect to="/" />) : (<SignIn />))} />
</BrowserRouter>
);
sign-out:
const signOut = async () => {
localStorage.removeItem('token')
await axios.post('sign-out', {});
setRedirect(true);
}
if (redirect) {
return <Redirect to="/sign-in" />
}
Try using a state in your component and set state according to your login status.Once the state is changed component will re-render automatically.
or else try using a global state like context or redux which will ease your process.

React-router - how to navigate to previous auth link after successful login

Bashing my head against the wall all weekend with this gem.
What I'm trying to do
I want a user to be able to be redirected to a previous auth link on successful login that they tried to get to.
Steps of logic wanted.
User navigates to /suggestions
User is redirected to the login page to get auth token
User logs in successfully
User gets navigated to the /suggestions page not the root /
At the moment I'm having the below logic.
User navigates to /suggestions
User is redirected to the login page to get auth token
User logs in successfully
User is redirected to the /suggestions.
Then immediately after is redirected to the root / path.
How am I trying to do this?
I'm trying to do this by setting a cookie in the browser to save the previous path. Which is working well at the moment. This then is checked against in my routeComponent file and uses a useHistory to push to that path and then removes the cookie. This is all done in a useEffect.
useEffect(() => {
const cookie = getCookie('redirectPath');
if (!HAS_ACCESS_TOKEN && !cookie) {
RedirectOnLoginService.setRedirectCookie(rest.location.pathname);
}
if (HAS_ACCESS_TOKEN && cookie) {
history.push(cookie);
RedirectOnLoginService.removeRedirectCookie('redirectPath');
}
}, [HAS_ACCESS_TOKEN, history, rest]);
This seems to be working for the most part it redirects the user but soon after it hits a return and this is where the user is then returned back to the root /. The cookie at this point is no longer available as it has been deleted in the useEffect.
if (!ev(VARIABLES.ACCESS_TOKEN)) {
return (
<Route
{...rest}
render={props =>
HAS_ACCESS_TOKEN ? (
!NO_USER_PROFILE ? (
renderFn ? (
renderFn(props)
) : (
<Component {...props} />
)
) : (
<Loader />
)
) : (
<Redirect to="/login" />
)
}
/>
);
}
AuthenticationRoute
export const AuthenticationRoute = ({ component: Component, ...rest }) => {
const { HAS_ACCESS_TOKEN } = usePermissions();
return (
<Route
{...rest}
render={props => (!HAS_ACCESS_TOKEN ? <Component {...props} /> : <Redirect to="/" />)}
/>
);
};
For transparency this is what my app.js holds for the private routes:
<Switch>
<Route path="/legal/:key" component={Legal} />
<AuthenticationRoute
key={'login'}
exact
path={'/login'}
component={LoginScreen}
/>
<Route key={'logout'} exact path={'/logout'} component={LogOutScreen} />
<AuthenticationRoute
key={'auth'}
exact
path={'/oauth'}
component={AuthContainer}
/>
<PrivateRoute
path={'/hr'}
renderFn={props => (
<Page navigation={hrNavigation} routes={HrRoutes} isHr {...props} />
)}
adminOnly
/>
<PrivateRoute
path={'/'}
renderFn={props => <Page routes={Routes} {...props} />}
/>
<Route key={'404'} path={'/**'} component={NotFound} />
</Switch>
For the life of me I can't figure out why it is getting redirected to the root / and not to the suggestions page. Is there anything obvious that I am missing?
UPDATE
I have just tried to use Redirect component from react-router to see if this helps but I get the same outcome:
const cookie = getCookie('redirectPath');
if (!HAS_ACCESS_TOKEN && !cookie) {
RedirectOnLoginService.setRedirectCookie(rest.location.pathname);
}
if (HAS_ACCESS_TOKEN && cookie) {
// history.push(cookie);
RedirectOnLoginService.removeRedirectCookie('redirectPath');
return <Redirect to={cookie} />;
}
React-route provides a history.back() function, Did you tried it ?

react router v5 privateroute goes to login after logging in and refreshing page

i'm trying to implement privateroute with react router, there is a problem when i login for the first time it goes to the specific page that i want but when i refresh the page it goes to login page even when it's logged in.
my router code:
export default function PsyaRouter() {
const auth = useSelector((state) => state.auth);
return (
<Router>
<SnackbarProvider maxSnack={4} hideIconVariant={false}>
<Header>
<CustomSnackbar />
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/enterPhone">
<EnterPhone />
</Route>
<Route path="/enterCode">
<EnterCode />
</Route>
<PrivateRoute path="/assessment/create">
<AssessmentForm />
</PrivateRoute>
<PrivateRoute path="/assessment">
<Assessment />
</PrivateRoute>
<Route path="/users">
<Users />
</Route>
<Route path="/groups">
<Groups />
</Route>
<Route path="/not-found">
<NotFound />
</Route>
<Redirect to="/not-found" />
</Switch>
</Header>
</SnackbarProvider>
</Router>
);
}
this is my privateroute implementation:
export default function PrivateRoute({ children, ...rest }) {
const auth = useSelector((state) => state.auth);
// const { last_role_type: userType } = useSelector((state) => state.user);
useEffect(() => {
console.log("router auth obj: ", auth);
}, []); //auth
return (
<Route
{...rest}
render={(props) => {
return auth.isLoggedIn ? (
children
) : (
<Redirect
to={{
pathname: "/enterPhone",
state: { from: props.location },
}}
/>
);
}}
/>
);
}
When you refresh the page, react remount the whole application which then reset all of your state. To fix this, either using localStorage or sessionStorage to persist your authentication data and then get them for validation in your private route component.
Keep in mind that when you refresh your browser, all of your state and props are cleared to their initial values, the same way as if you typed the address path and hit return.
So, although it's not implied by the code you supplied, it's obvious that your state/prop value auth.isLoggedIn is cleared, so probably false.

React - Redirect user is not authenticated

I am using Firestore and have access to state that will inform me if the user is authenticated or not. I am mapping these conditions to props and will use react-router-dom's redirect to navigate user to the login page if not authenticated.
However, when I console log the authentication status i see that initially it will show false while it loads this information and then switched to true. However, by the time it sees that the users is authenticated, I have already redirected them.
How am I suppose to redirect users based on their authentication within react?
export class PrimaryNavBar extends Component {
render(){
const { auth, location } = this.props;
const authenticated = auth.isLoaded && !auth.isEmpty;
return (
<React.Fragment>
{!authenticated ? <Redirect to="/"/> : null}
<Navbar bg="primary" variant="dark">
<Container>
<Link to="/">
....
I have looked at other answers here but they don't really helped with redirects, just render nulls if not authenticated.
Update
I restructured my code to redirects will happen in the app.js file
The new problem is, whenever I user changes route, it will recheck auth which will be false at first and they end up back at the login screen.
class App extends Component {
state = {
isAuthenticated: this.props.isAuthenticated
}
render() {
const logoutHandler = () => {
this.props.firebase.logout();
};
const authenticated = this.props.auth.isLoaded && !this.props.auth.isEmpty;
return (
<Aux>
{!authenticated ? <Redirect to="/" /> : null}
<Route path="/" exact component={Login} />
<Route path='/(.+)' render={() => (
<React.Fragment>
<PrimaryNavBar logout={logoutHandler} />
<Switch>
<Route path="/dashboard" exact component={Dashboard} />
<Route path="/auctions" component={Auctions} />
<Route path="/auctions/:id" component={AuctionItem} />
<Route path="/auctions/create-auction" component={CreateAuction} />
<Route path="/bidders/create-bidder" component={CreateBidder} />
<Route path="/bidders/:id" component={Bidder} />
<Route path="/bidders" component={Bidders} />
</Switch>
</React.Fragment>
)} />
<ReduxToastr position="bottom-right" />
</Aux>
);
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.firebase.auth.uid,
auth: state.firebase.auth
};
};
const mapDisptachToProps = dispatch => {
return {};
};
export default withRouter(withFirebase(
connect(
mapStateToProps,
mapDisptachToProps
)(App)
));
You should probably wait untill auth.isLoaded is true? If thats the case, you could just do
const { auth, location } = this.props;
if (!auth.isLoaded) return null
This will refrain your nav component from rendering untill the authentication info is loaded. You might want to consider to implement this check (and the redirect) in a parent component level, though.

How to properly set property(if user is logged) from cookie after page opens or F5 in React with router and redux

I would like to know what is the best practise in my case.
I am checking cookies in App constructor if there is stored a token. If it is there I am dispatching action which will set property isAuth to true.
My problem is that I will get redirected to login because isAuth is false from the beginning.
I came up with solution that I will set isAuth after checking session and I will not redner anything until then. Is it OK? And even if is, isn't there a better solution for handling this?
return (
<div>
<Router>
{isAuth ? (
<Switch>
auth routes...
</Switch>
)
: (
<Switch>
login and redirect to login
</Switch>
)}
</Router>
</div>
);
I usually create a PrivateRoute component that renders the Route or Redirect components after checking the login status, something like:
export const PrivateRoute = ({ component: Component, ...otherProps }) => (
<Route
{ ...otherProps }
render={ props => (
isLoggedIn()
? <Component {...props} />
: <Redirect to={ Paths.LOGIN } />
)}
/>
)
You should replace my isLoggedIn method and my Paths.LOGIN constants with your case.
Then you just use them as:
<PrivateRoute path="/admin/something" component={ MyAdminPage } />
<Route path="/non/logged/in/route" component={ MyNonLoggedInPage } />
<Route path="/login" component={ LogInPage } />

Resources