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
Related
I want to learn the correct approach for firebase routing authetication,what I mean is:
function App() {
const auth = getAuth();
if (!auth) {
<Spinner />;
}
return (
<>
{auth && (
<Router>
<Routes>
<Route path='/' element={<PrivateRoute />}>
<Route index element={<Explore />} />
<Route path='/offer' element={<Offers />} />
<Route path='/profile' element={<Profile />} />
</Route>
<Route path='/sign-in' element={<SignIn />} />
<Route path='/sign-up' element={<SignUp />} />
<Route path='/forgot-password' element={<ForgotPassword />} />
</Routes>
<Navbar />
</Router>
)}
I have this code-block, at first I thought I should've gotten a useAuth hook which use onAuthStateChanged, but I realize that this auth variable from getAuth is kinda work the same way so why not use it instead of a hook?
and my PrivateRoute looks like this:
function PrivateRoute() {
const currentUser = getAuth().currentUser;
return currentUser ? <Outlet /> : <Navigate to='/sign-in' />;
}
the problem is once app mounts, because of there is no app-level state, it stays the same.
Then if I try to log off and put some logic into routing like if user exists, dont' allow routing to signup or signin, it doesn't work.
If I use redux or context API, I would dispatch whenever I login, logout, signup but without them what is the correct set-up for handling this kind of routing?
after some time, i just figured out how to do what I needed to do, so I post it here just in case someone also encounters this problem and seeks help.
If you don't use redux or context api and still want to implement this kind of feature, here's how I do:
So what I needed to do? I want authenticate through firebase, what you need to do -at least what I found- implement a APP/LEVEL/STATE in order to check if user logged in or not and update the app level rendering, that renders the whole app and enforces behaviour accordingly.
So:
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 user={user} />}>
<Route index element={<Explore />} />
<Route path='/offers' element={<Offers />} />
<Route path='/profile' element={<Profile />} />
<Route path='/create-listing' element={<CreateListing />} />
<Route path='/category/:categoryName' element={<Category />} />
</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>
<Navbar />
</Router>
<ToastContainer autoClose={1000} />
</>
);
}
export default App;
Here, basically after component mounts, we want useEffect to only execute once and set up listener for auth state changing, whenever auth state changes like sign-in or log-out, the code block of onAuthStateChanged runs and when it runs, every time it sets the user if there's one or not, therefore re-renders the component which is App itself, and below the routing works accordingly. If you have a private route as I do, you can just pass the user as a prop, then:
import { Navigate, Outlet } from 'react-router-dom';
function PrivateRoute({ user }) {
return user ? <Outlet /> : <Navigate to='/sign-in' />;
}
export default PrivateRoute;
So I'm using react router v6 in my React App.
I have the following routes enabled in my app.js file
<Routes>
<Route path='/' component={<Home />} />
<Route path='/login' component={<SignUp />} />
<Route path='/signup' component={<Login />} />
</Routes>
Everything's fine and that. What I want to to do is to put restriction on pages. Now I know how to create PrivateRoutes and PublicRoutes based on LoggedIn User.
For this purpose I want the user to not be able to access Homepage after he or she signups.
Are there an functions for that or what strategy would I use.
I accomplished this using 'react-router-dom' and creating a PrivateRoute component. The following code is not tested but can give you some ideas. LoaderComponent is a loading animation of your choice can be toher component as well.
// Based on https://reactrouter.com/web/example/auth-workflow
// If the user is not yet authenticated.
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children, path, ...props }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
//put your authentication logic
setIsAuthenticated(true);
}, []);
return (
<Route
path={path}
{...props}
render={() => (isAuthenticated ? children : <LoaderComponent />)}
/>
);
};
And in your router config
import { Switch } from 'react-router-dom';
<Switch>
<PrivateRoute exact path='/'>
<Home />
</PrivateRoute>
...
</Switch>
I have built a CRUD application with react, redux and react-router. Application is redirecting to /login, if page is refreshed from PrivateRoutes. I am updating login status of user by fetching data from localStorage and dispatching action to update redux in App component.
App.js
const updateLoginStatus = () => {
if (checkIfUserLoggedIn()) {
const user = getLoginInitialState();
dispatch(updateLoginInitialState(user));
}
};
useEffect(() => {
updateLoginStatus();
}, [login.isLoggedIn]);
Routing in App component
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<Route exact path="/logout" component={Logout}></Route>
<PrivateRoute exact path="/expenses" component={ListExpensesComponent} />
<PrivateRoute exact path="/expenses/add-expense" component={AddExpenseComponent} />
<PrivateRoute
exact
path="/expenses/edit-expense/:expense_id"
component={EditExpenseComponent}
/>
</Switch>
</Router>;
PrivateRoute.js
const login = useSelector((state) => state.loginReducer);
return (
<div>
<Route
{...rest}
render={(props) => {
if (!login.isLoggedIn) {
return <Redirect to="/login" />;
}
return <Component {...props} />;
}}></Route>
</div>
);
One solution I found is
adding one more action in login UPDATE_LOGIN_REQUEST and add loading property in login Reducer and set loading: true . Thus, displaying <Loader /> until login status is updated.
Is there any other way we can handle it? Thanks in advance.
Actually I had no problem with directing to another route by clicking a button, but somehow I can't direct manually by changing the URL. Every time I was about changing the URL (ex: localhost:3000/proposal), it always directs me to the first Route. Here's the Route in order :
<Switch>
<Route exact path="/" component={Landing} /> // => always goin here
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
{/* Dashboard */}
<PrivateRoute
exact
path="/home"
component={Home}
StickyNav={StickyNavbar}
/>
<PrivateRoute
exact
path="/proposal"
component={Proposal}
StickyNav={StickyNavbar}
/>
<PrivateRoute
exact
path="/laporan"
component={Laporan}
StickyNav={StickyNavbar}
/>
<Route component={NotFound} />
</Switch>
It doesn't direct me to Landing if I change the URL to non-private route. Here's my private route code :
import React from "react"
import { Route, Redirect } from "react-router-dom"
import { connect } from "react-redux"
import PropTypes from "prop-types"
const mapStateToProps = state => ({
auth: state.auth
})
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated === true ? (
<Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)
}
/>
)
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
}
export default connect(mapStateToProps)(PrivateRoute)
Based on #zhuber said, the auth object from react-redux doesn't call before the private route was called. So I changed the condition from isAuthenticated using localStorage like this :
!isEmpty(localStorage.jwtToken) ? (
<Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)
I have a simple React app and I'm trying to implement auth logic. This is how my application looks like:
class App extends Component {
render() {
return (
<Router history={history}>
<div className="App">
<Switch>
<PrivateRoute path="/account" component={Account} />
<Route component={PublicLayout} />
</Switch>
</div>
</Router>
)
}
}
My auth logic is: if user isAuthenticated === true render /account page else redirect to /signin. So for this I created my own simple PrivateRoute functional component:
export default ({ component: Component, ...args }) => {
return (
<div>
<Route {...args} render={(props) => {
return fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/signin' />
}} />
</div>
)
}
Because PrivateRoute path="/account" is NOT exact path I expect that if user goes to /account-setting it will redirect him to /sign-in. But it's not working. The user successfully goes to /account-setting and see PublicLayout.
Why is that? There is no exact key in route, it has to grab all that starts from "/account" and use my logic in the PrivateRoute functional component.
Not providing exact props doesn't mean that it will match /account, /account-settings but it will match /account, /account/other, /account/other/paths. So, you'll need to provide both path:
<PrivateRoute path="/account" component={Account} />
<PrivateRoute path="/account-setting" component={Account} />
Or, you may use regex:
<PrivateRoute path="/account(?=\S*)([a-zA-Z-]+)" component={Account} />