React Authentication "fails" on refresh in protected Route - javascript

I've came across a problem with protected routes and the authentication of protected routes.
I'm using an AuthContext for the whole user authentication across the webapp. I save all the user-data inside a state. With the help of useEffect and the sessionStorage I store the user object so It can be used after a page reload.
[...]
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState({})
useEffect(() => {
const sessionUser = sessionStorage.getItem("user")
if(!sessionUser) return
changeUser(JSON.parse(sessionUser))
}, [])
const hasRank = (ranks) => {
if(!Object.keys(user).length > 0) return false
const matchingPerms = ranks.filter(rank => user.rank.includes(rank))
return matchingPerms.length > 0
}
const changeUser = (data) => {
setUser(data)
if(Object.keys(data).length > 0) {
return sessionStorage.setItem("user", JSON.stringify(data))
}
return sessionStorage.removeItem("user")
}
}
[...]
In order to protect certain pages I'm using a Protected Route Component that checks whether the user is logged in or not.
[...]
const auth = useContext(AuthContext)
const isAuthorized = auth.hasRank(rest.rank)
<Route
{...rest}
render={props => {
return isAuthorized ? (
<Component {...props} />
) : (
<Redirect to="/auth/login" />
)
}}
/>
The saving and fetching into and from the sessionStorage works fine until I want to render content that's inside a protected route - I always get redirected to the login page, because the user object is empty because of the reload and the state is not being updated early enough. Therefore the protected route checks for authentication with an empty object, which results in a redirect to the login page.
How can I wait until the user state is being updated before checking the authentication inside the protected route?
EDIT:
App Component:
return (
<Router>
<Switch>
<Route path="/auth/register" component={Register} />
<Route path="/auth/login" component={LogIn} />
<Route path="/err/404" component={Page404} />
<Route path="/" component={PanelRoutes}/>
</Switch>
</Router>
)
PanelRoutes Component:
return (
<div className="skin-default fixed-layout">
<div id="main-wrapper">
<Topbar />
<Sidebar />
<div className="page-wrapper">
<div className="container-fluid">
<Switch>
<Route exact path="/" component={Homepage} />
<Route exact path="/legal/imprint" component={Imprint} />
<Route exact path="/legal/privacy" component={Privacy} />
<ProtectedRoute exact path="/mc/sounds" component={McSounds} rank={["ADMIN", "MC"]} />
<ProtectedRoute exact path="/admin/users" component={AdminUsers} rank={["ADMIN"]} />
</Switch>
</div>
</div>
</div>
</div>
)
Kind Regards

Related

Trouble in configuring nested routes in react router dom v5

I'm having trouble configuring my react-router I'm building a dashboard with 2 pages with nested routes. The first page is the main page with all the protected routes and the second login with unprotected routes. The problem I'm facing is when I place the exact prop on AuthRoute in the Routes component I won't be able to navigate to all the protected routes but I can navigate to login page. If I remove the exact prop I'll be able to navigate to all the protected routes but I can't navigate to the login page. I tried purring the exact on both components it won't work as well
this is may Routes component with two pages Main with all protected routes such as Dashboard and logIn page with another route reset-page
function Routes({ ColorModeContext }) {
return (
<Router>
<Switch>
<AuthRoute
path="/"
ColorModeContext={ColorModeContext}
component={Main}
/>
<Route path="/login" component={Login} />
<Route path="*">
<div className="center">
<Box pb={2}>
<Typography variant="h3">Page Not Found (-_-)</Typography>
</Box>
<Button variant="contained" onClick={() => window.history.back()}>
Go Back
</Button>
</div>
</Route>
</Switch>
</Router>
);
}
this is my AuthRoute that separates protected routes from unprotected routes
const AuthRoute = ({ component: Component, authStatus = true, ...rest }) => {
const { ColorModeContext, ...other } = rest;
return (
<Route
{...other}
render={(props) =>
authStatus ? (
<Component {...props} ColorModeContext={ColorModeContext} />
) : (
<Redirect to="/login" />
)
}
/>
);
};
here's the main page with all the routes
const Main = ({ props }) => {
const { path } = useRouteMatch();
return (
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<DrawerHeader />
<Switch>
<Route exact path={path} component={Home} />
<Route path={`${path}/exam-results`} component={ExamResults} />
<Route path={`${path}/new-reg`} component={Registration} />
//...more components
</Switch>
</Box>
);
};
here's the login page
const LogIn = ({ props }) => {
const { path } = useRouteMatch();
return (
<Switch>
<Route exact path={path} component={LogInForm} />
<Route path={`${path}/reset-password`} component={ResetPassForm} />
</Switch>
);
};

Routes not working as desired in React JS

I am trying to build a full stack application with User login/logout functionality.
I want to protect certain pages such that they can only be viewed when the user is logged in. For login I have created a REST API and I am using session storage to keep track of whether the user is logged in or not.
validateUser = () => {
let user = {
username: this.state.email,
password: this.state.password,
//status: "LOGGED_IN"
};
UserService.authenticateUser(user).then((res) => {
if(res.data === 'SUCCESS') {
window.sessionStorage.setItem("isUserLogged", true);
} else if(res.data === 'FAILURE') {
window.sessionStorage.setItem("isUserLogged", false);
this.resetLoginForm();
this.setState({"error":"Invalid username or password"});
}
})
};
Tis is my App.js
function App() {
return (
<div>
<Router>
<HeaderComponent/>
<div className="container">
<Switch>
<Route path="/" exact component={LandingPageComponent}></Route>
{/* <Route path ="/customers" component = {ListCustomerComponent}></Route> */}
{/* <Route path ="/add-customer/:id" component = {CreateCustomerComponent}></Route> */}
<Route path = "/view-customer/:id" component = {ViewCustomerComponent}></Route>
<Route path = "/admin-login" component = {AdminLoginComponent}></Route>
<Route path = "/admin-register" component = {AdminResgisterComponent}></Route>
<Route path="/customers" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <ListCustomerComponent />
: <Redirect to='/admin-login' />
)} />
<Route path="/add-customer/:id" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent />
: <Redirect to='/admin-login' />
)} />
</Switch>
</div>
<FooterComponent/>
</Router>
</div>
);
}
export default App;
Everything works fine if I don't check my session storage. But when I try to implement the conditional routes as shown above I start getting errors.
If I just put simple routes, then I don't encounter this error.
Any help would be highly appreciated.
You didn't pass Route props into your component. So history does not included in props, you can console.log(this.props) to check what this.props contains.
To fix it, let's pass Route props into your components
<Route path="/add-customer/:id" exact render={(props) => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent {...props} /> // ADD PROPS HERE
: <Redirect to='/admin-login' />
} />
You didn't show what you did on ListCustomerComponent.
You could try to encapsulate your component using HOC withRouter or if you are using Functional component, use useHistory hook.
// on export class component
export default withRouter(YourComponent)
in functional component, you can use
const YourComponent = ()=>{
const history = useHistory();
// then you can say something such as
// history.push(...)
return <>...your view here...</>
}
export default YourComponent;
<Switch>
{/* Login Sections goes Here */}
<Route exact path='/' component={MainPage} />
<Route exact path='/login' component={Login} />
<Route exact path='/admin/' component={LoginAdmin} />
<Route exact path='/register' component={Register} />
{/* AdminUser ROutes goes here */}
<SuperUserDashboard>
<Route exact path='/admin/dashboard' component={Dashboardpage} />
<Route exact path='/admin/users' component={UsersAdmin} />
</SuperUserDashboard>
<Route exact path='' component={Notfound} />
</Switch>
in superuser dashboard check if user is authenticated if not redirect to admin login page else all the routes will be visible

How to redirect user to root route if the user refreshes the page on any other route in REACT ROUTER?

So, in my project I have two routes one is the root route and other is the /game route. And I always
want the user to start from the root route because the user needs to set a mandatory state variable before moving to /game route.
But if the user refreshes the page on game route I want to redirect to root route. So all I want is that the entry point to my application is always the root route.
<Router>
<Route path="/">
<Redirect to="/" />
</Route>
<Route exact path="/" render={() => <Home setLevel={setLevel} />}></Route>
<Route exact path="/game" render={() => <Game level={level} />}></Route>
</Router>
The setup above works on localhost but when I deploy my app on Netlify it doesn't work. So if the user refreshes the page on /game route, it says page not found.
Here's full code
import { useState } from "react";
import {
BrowserRouter as Router,
Link,
Redirect,
Route,
} from "react-router-dom";
const Home = ({ setLevel }) => {
return (
<>
<h1>Choose Difficulty</h1>
<ul>
<li onClick={() => setLevel("easy")}>Easy</li>
<li onClick={() => setLevel("hard")}>Hard</li>
</ul>
<Link to="/game">Play</Link>
</>
);
};
const Game = ({ level }) => {
return (
<>
<h1>Welcome to the game</h1>
<p>Your level is {level}</p>
</>
);
};
const App = () => {
const [level, setLevel] = useState(null);
return (
<Router>
<Route path="/">
<Redirect to="/" />
</Route>
<Route exact path="/" render={() => <Home setLevel={setLevel} />}></Route>
<Route exact path="/game" render={() => <Game level={level} />}></Route>
</Router>
);
};
export default App;
I haven't investigated your React set up too closely, but you may just be missing a _redirects file for your Netflify deployment: https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file
react-router-dom is a client side router, so when Netlify loads a page using server side rendering (aka, when you refresh or visit a page for the first time, that is not a client side redirect), it cannot find the route on the server. Adding a _redirects file will instruct Netlify how to serve up your pages on initial load.
Assuming that when the user refreshes the page, state level becomes null again.
Then update your code as below:
const Game = ({ level }) => {
// add a guard right here
if (!level) {
return <Redirect to="/"/>;
}
return (
<>
<h1>Welcome to the game</h1>
<p>Your level is {level}</p>
</>
);
};
const App = () => {
const [level, setLevel] = useState(null);
return (
<Router>
{ /* remove this code
* <Route path="/">
* <Redirect to="/" />
* </Route>
*/ }
<Route exact path="/" render={() => <Home setLevel={setLevel} />}></Route>
<Route exact path="/game" render={() => <Game level={level} />}></Route>
</Router>
);
};

implement auth route - with nested /signin and /signup routes

I want to implement simple signin/signup routs with react-router-dom.
here is the App.js
<div className="App">
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/auth" component={SignInSignUpPage} />
</Switch>
</div>
and I'm using route guard like so :
const HomePage = ({ currentUser }) => {
return currentUser ? <HomePageComponent /> : <Redirect to="/auth" />;
};
Now what I want to do, is to have the /auth route, for users to login, and inside a container of both the signInSignUp, I have a Link that will change the route to auth/signup to view the signup page like so:
const SignInSignUpPage = ({ match }) => {
return (
<SignInSignUpContainer>
<Route path={`${match.path}`} component={SignIn} />
<Route path={`${match.path}/signup`} component={SignUp} />
</SignInSignUpContainer>
);
};
which in turn will render the correct component :
const SignInSignUpContainer = ({ match, children, history }) => {
const { isExact } = match;
return (
<SignInSignUpContainerContent>
<SignInSignUpContainerContentForm>
<LockIconContainer iconName="icon-lock-closed" />
{children}
</SignInSignUpContainerContentForm>
</SignInSignUpContainerContent>
);
};
I must be doing it wrong, and the react-router-dom docs are addressing the protected route, which I didn't find suitable for this case.
Just from looking at the structure: you don't send match prop down to SignInSignUpContainer, and it seems that SignInSignUpContainer expects it.
should be :
const SignInSignUpPage = ({ match }) => {
return (
<SignInSignUpContainer match={match}>
<Route path={`${match.path}`} component={SignIn} />
<Route path={`${match.path}/signup`} component={SignUp} />
</SignInSignUpContainer>
);
};
same with history prop

Login route is not getting renderd on logout react

I am new to the react js . Now , Here, what I have is a private Route.
const PrivateRoute = ({ component: Component, isFetching, hasUserLogIn, path, ...rest }) => {
hasUserLogIn = localStorage.getItem("access_token");
if (hasUserLogIn !== undefined && hasUserLogIn !== null) {
hasUserLogIn = true;
} else {
hasUserLogIn = false;
}
console.log("hasUserLogIn",hasUserLogIn);
return hasUserLogIn ?
(
<Route
{...rest}
path={path}
component={Component}
/>
)
:
(
<Redirect
to={{
pathname: "/login",
state: { from: path }
}}
/>
)
};
<div>
<Router history={history}>
<div>
{this.props.isFetching && <Loading />}
<Switch>
<PrivateRoute exact path="/:job?" component={LandingScreen} />
<PrivateRoute exact path="/quiz-setup/:job" component={QuizSetupMain} />
<PrivateRoute exact path="/quiz-questions" component={FetchedQuestionComponent} />
<Route exact path="/login" component={LoginComponent} />
<Route exact path="/*" component={NotFound} something="foo" />
</Switch>
</div>
</Router>
</div>
)
handleLogout = () => {
this.props.logoutUser();
}
Here, first one is as soon as I log In success.
So, In login success I have written like ,
history.push('/'); then it redirects to the LandingScreen Component.
Now, I do have one dropdown, on change of that I am adding :job param in the route which is like ,
onchange ,
history.push({
pathname: "/" + `${jdId}`
});
So It again rerenders the LandingScreen component.
Now, on click of the logout button,
I want to redirect the use to the login page that is /login.
So, on click of logout what I do is ,
localStorage.clear();
history.push('/');
so with this it is not rendering the LoginComponent and also route does not get changes ,
But If I remove the :job? from the route it redirects to the login I mean renders the login component.
Can any one help me with this ?

Categories