Problem with Unauthenticated Users Briefly Accessing Pages Intended Only for Logged-in Users - reactjs

I am creating a React project.
There are users who use the website, and there is a page that can only be accessed by logged-in users.
I am using a Higher Order Component (HOC) to check the user's authentication status.
If an unauthenticated user attempts to visit a page that only a logged-in user can access, I am using useNavigate from react-router-dom to redirect the user to the login page.
The problem is that an unauthenticated user can briefly (for about 0.5 seconds) see the content of the page that they cannot access.
It seems that the page's content is briefly exposed while the user authentication process is running after rendering.
How can I prevent this from happening?
client > src > hoc > Auth.js
export default function Auth(SpecificComponent, option, adminRoute = null) {
// Auth option:
// 1. null: anyone can access
// 2. true: only authenticated users can access
// 3. false: only unauthenticated users can access
function AuthenticationCheck() {
const dispatch = useDispatch();
const navigate = useNavigate();
useEffect(() => {
dispatch(userActions.auth()).then((res) => {
if (res && !res.isAuth) {
// If the user is not authenticated and the page requires authentication,
if (option) {
// redirect to login page
navigate("/login", { replace: true });
}
} else {
if (adminRoute && !res.isAdmin) {
// If the page is an admin route and the user is not an admin,
navigate("/", { replace: true });
alert("This page is only accessible by admin users.");
}
if (option === false) {
// If the page is intended only for unauthenticated users and the user is authenticated,
navigate(-1, { replace: true });
}
}
});
}, []);
return <SpecificComponent />;
}
return AuthenticationCheck;
}
client > src > App.js
function App() {
const AuthLogin = Auth(LoginPage, false);
const AuthRegister = Auth(RegisterPage, false);
const AuthHome = Auth(HomePage, null);
const AuthAboutPage = Auth(AboutPage, null);
const AuthCakes = Auth(CakesPage, null);
const AuthOrder = Auth(OrderPage, null);
const AuthOrderDetail = Auth(OrderDetailPage, null);
const AuthMypage = Auth(MyPage, true);
const AuthCart = Auth(CartPage, true);
const AuthPayment = Auth(PaymentPage, true);
const AuthPaymentSuccessPage = Auth(PaymentSuccessPage, true);
const AuthPaymentFailurePage = Auth(PaymentFailurePage, true);
const AuthGuideRice = Auth(GuideRicePage, null);
const AuthGuideBread = Auth(GuideBreadPage, null);
const AuthFAQ = Auth(FAQPage, null);
const AuthContact = Auth(ContactPage, null);
const AuthUploadProduct = Auth(UploadProductPage, true, true);
const AuthTerms = Auth(TermsPage, null);
const AuthPrivacyPolicy = Auth(PrivacyPolicyPage, null);
return (
<>
<Navbar />
<Sidebar />
<Routes>
<Route path="/" element={<AuthHome />} />
<Route path="/login" element={<AuthLogin />} />
<Route path="/register" element={<AuthRegister />} />
<Route path="/about" element={<AuthAboutPage />} />
<Route path="/cakes/:ingredient" element={<AuthCakes />} />
<Route path="/order" element={<AuthOrder />} />
<Route path="/order/detail/:productId" element={<AuthOrderDetail />} />
<Route path="/user/mypage" element={<AuthMypage />}>
<Route path="order-history" element={<OrderHistoryPage />} />
<Route
path="order-cancellation-history"
element={<OrderCancellationHistoryPage />}
/>
<Route path="coupon" element={<CouponPage />} />
<Route path="mileage" element={<MileagePage />} />
<Route path="edit-profile" element={<EditProfilePage />} />
<Route path="unregister" element={<UnregisterPage />} />
</Route>
<Route path="/user/cart" element={<AuthCart />} />
<Route path="/payment" element={<AuthPayment />} />
<Route path="/payment/success" element={<AuthPaymentSuccessPage />} />
<Route path="/payment/failure" element={<AuthPaymentFailurePage />} />
<Route path="/guide/rice" element={<AuthGuideRice />} />
<Route path="/guide/bread" element={<AuthGuideBread />} />
<Route path="/faq" element={<AuthFAQ />} />
<Route path="/contact" element={<AuthContact />} />
<Route path="/upload" element={<AuthUploadProduct />} />
<Route path="/policy/privacy-policy" element={<AuthPrivacyPolicy />} />
<Route path="/policy/terms" element={<AuthTerms />} />
</Routes>
<Footer />
</>
);
}
export default App;
I would like to perform the AuthenticationCheck of my HOC before the page is rendered.
As I am new to implementing this feature, I may have written incorrect or inefficient code by referring to outdated videos and articles.
I would like advice on how to approach or solve this problem in order to perform the AuthenticationCheck before the page is rendered as I intended. Could you please help me with this issue on how to achieve what I want?

Related

IN REACT, how can I request API before rendered

My Application: Laravel(API SERVER) + React(Frontend)
What I want to do is restrict the access to routes of App if the user is not authenticated.
function App() {
const [userInfo, setUserInfo] = setState({isSignedIn: false});
return (
<Routes>
<Route path="/admin/*" element={ userInfo.isSignedIn? <Admin />: <Navigate replace to="/home/sign-in"/> } />
<Route path="/home/*" element={<Home />} />
</Routes>
);
}
To do that, auth API should be requested before App rendered.
My first try was like...
function App() {
const requestUserInfo = async() => {
const result = await axios.get("/auth");
return result.data;
}
const [userInfo, setUserInfo] = setState(requestUserInfo);
return (
<Routes>
<Route path="/admin/*" element={ userInfo.isSignedIn? <Admin />: <Navigate replace to="/home/sign-in"/> } />
<Route path="/home/*" element={<Home />} />
</Routes>
);
}
However I realized initial state cannot be async.
Also I tried axios in useEffect(), but I think it forces too many rerendering.
So Here Is My Question: What is the most common way to get auth info by API?

how i can render directly my menu when my local storage is not null?

i wanna render my menu directly when i put something on my localStorage.
basically my render doesn't appeared when i have nothing on my localstorage. When i have something is re rendering but i have to actualise my page.
How i can have my composant menu directly with my redirection ?
function App() {
const [login, setLogin] = useState(false);
let logged = localStorage.getItem("connexion");
const showNav = () => {
if (localStorage.getItem("connexion") != null) {
return <Menu />;
} else {
return <div></div>;
}
};
useEffect(() => {
showNav();
if (localStorage.getItem("connexion") != null) {
setLogin(true);
}
}, [login]);
return (
<Router>
<div>
{showNav()}
<Switch>
<Route path="/contact" component={MeContacter} />
<Route path="/Home" component={Home} />
<Route path="/" component={Connexion} />
</Switch>
<Footer />
</div>
</Router>
);
}
export default App;
You can set a storage event handler on the window object:
First define a state which controlls whether the menu should be showing or not:
const [isMenuShowing,setMenuShowing]=useState(false)
Now in a useEffect, add the event listener to change isMenuShowing whenever localStorage is modified:
useEffect(()=>{
const storageEventHandler=()=>{
if (localStorage.getItem("connexion") != null) {
setMenuShowing(true)
}}
window.addEventListener("storage",storageEventHandler)
return ()=>{window.removeEventListener(storageEventHandler)}},[])
Finally :
return (
<Router>
<div>
{isMenuShowing && <Menu/>}
<Switch>
<Route path="/contact" component={MeContacter} />
<Route path="/Home" component={Home} />
<Route path="/" component={Connexion} />
</Switch>
<Footer />
</div>
</Router>
);
Important! According to MDN docs, this event fires whenever the change to localStorage is from another windows, but i tested it on chrome and it actually worked if the modification was on the same window too.

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: how to structure routes for admin, user and public

Im trying to figure out how to structure a Router to use different routes for admin, user and public.
I have seen this post and the answer describing a key cloak - but I haven't been able to make sense of it.
I've seen this code sandbox which looks logical to me, but I'm having trouble incorporating it.
I have a constants file where I and defining routes as:
export const NEWBLOG = '/admin/newblog';
export const VIEWBLOG = '/viewblog';
I'm importing that into my App.js and then wanting to define different consts for Admin, User and Public as follows:
import * as ROUTES from '../../util/constants/Routes';
import NewBlog from '../../components/blog/admin/New';
// admin routes
const Admin = ({ match }) => (
<React.Fragment>
<Route path={`${match.path}/${ROUTES.NEWBLOG}`} component={NewBlog} />
<Route path={`${match.path}/2`} render={() => <h2>test</h2>} />
</React.Fragment>
);
// authenticated user routes
const Other = ({ match }) => (
<React.Fragment>
<Switch>
<Route path={`${match.path}/2`} render={() => <h2>one</h2>} />
<Route path={`${match.path}/2`} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);
// public routes
const Public = ({ match }) => (
<React.Fragment>
<Switch>
<Route path={`${match.path}/2`} render={() => <h2>one</h2>} />
<Route path={`${match.path}/2`} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);
Then inside the router statement I have:
const App = () => (
<Router>
<Navigation />
<Switch>
<Route path="/a" component={Admin} />
<Route path="/u" component={Other} />
<Route path="/p" component={Public} />
<Route
component={({ location }) => {
return (
<div
style={{
padding: "50px",
width: "100%",
textAlign: "center"
}}
>
<ErrorMessage />
</div>
);
}}
/>
</Switch>
</Router>
);
export default App;
This all works until I try to use the routes constants inside the back ticks part of the Admin constant.
I can't seem to use that approach.
Can anyone help with a source of reference materials to find a way through this?
There are few things you need to know
Child Routes will render only when the Parent route path is matched
For the Child Route the path needs to be the path that matched the parent + the child route path
You can write wrappers over route which are responsible for deciding if the user is authenticated or an admin
In all such scenarios you need to store the user authentication state within state, context or redux store.
When you render the Route in Admin like
<Route path={`${match.path}/${ROUTES.NEWBLOG}`} component={NewBlog} />
The path to the component actually becomes /a/admin/newBlog which is actually incorrect
Overall you can change your code to something like this
App.js
const App = () => (
<Router>
<Navigation />
<Switch>
<Route path="/admin" component={Admin} />
<Route path="/user" component={Other} />
<Route path="/public" component={Public} />
</Switch>
</Router>
);
AuthRoute.js
const AuthRoute = (props) => {
const {path, match, component: Component, render, ...rest} = props;
const {user, isLoading} = useContext(AuthContext); // Assuming you use context to store route, you can actually get this values from redux store too.
return (
<Route
{...rest}
path={`${match.path}${path}`}
render={(routerProps) => {
if(isLoading) return <div>Loading...</div>
if(!user) return <div>Not Authenticated</div>
return Component? <Component {...rest} {...routerProps} /> : render(routerProps)
}}
/>
}
An adminRoute needs to both check whether the user is admin as well as check if he is authenticated or not so you component would look like
AdminRoute.js
const AdminRoute = (props) => {
const {path, match, ...rest} = props;
const {user, isLoading} = useContext(AuthContext); // Assuming you use context to store route, you can actually get this values from redux store too.
return (
<Route
{...rest}
path={`${match.path}${path}`}
render={(routerProps) => {
if(isLoading) return <div>Loading...</div>
if(!user) return <div>Not Authenticated</div>
if(user.role !== "admin") return <div>Need to be an admin to access this route</div>
return Component? <Component {...rest} {...routerProps} /> : render(routerProps)
}}
/>
}
Now you can use the above two components to separate out the Admin and Auth Routes
Also keep in mind that AuthRoutes and public routes paths cannot be the same
Route constants
export const NEWBLOG = '/newblog';
export const VIEWBLOG = '/viewblog';
Routes
import * as ROUTES from '../../util/constants/Routes';
import NewBlog from '../../components/blog/admin/New';
// admin routes
const Admin = (props) => (
<React.Fragment>
<AdminRoute {...props} path={ROUTES.NEWBLOG} component={NewBlog} />
<AdminRoute {...props} path='/2' render={() => <h2>test</h2>} />
</React.Fragment>
);
// authenticated user routes
const Other = (props) => (
<React.Fragment>
<Switch>
<AuthRoute {...props} path={'/3'} render={() => <h2>one</h2>} />
<AuthRoute {...props} path={'/4'} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);
// public routes
const Public = ({ match }) => (
<React.Fragment>
<Switch>
<Route path={`${match.path}/5`} render={() => <h2>one</h2>} />
<Route path={`${match.path}/6`} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);

React nested Routes implementation

I am new to reactjs. i am trying to develop website who's home page or landing page has different design then signin user. after user logs in header changes and there is a sidebar. I have placed my signed routes inside signed in component but still its not working
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/Resident" component={customer} />
<Route path="/search" component={search} />
<Route component={EmptyPage} />
</Switch>
class customer extends Component {
constructor() {
super()
this.setLayout = this.setLayout.bind(this)
// Listen for changes to the current location.
history.listen((location, action) => {
// location is an object like window.location
//console.log('history', location.pathname, this.setLayout(location.pathname))
this.setLayout(location.pathname)
})
}
componentWillMount() {
this.setLayout(this.props.pathname)
}
setLayout(url) {
const emptyView1 = [
'/pages/error-page',
'/pages/create-account',
'/pages/login',
'/pages/under-maintenance',
];
let isEmptyView = indexOf(emptyView1, url) !== -1 ? true : false
let currentLayout = this.props.config.layout
if(isEmptyView && currentLayout !== 'empty-view-1') {
this.props.setConfig('layout', 'empty-view-1')
} else if(!isEmptyView && currentLayout !== 'default-sidebar-1') {
this.props.setConfig('layout', 'default-sidebar-1')
}
}
render() {
let {layout, background, navbar, logo, leftSidebar, topNavigation, collapsed} = this.props.config
// let {pathname} = this.props
let isEmptyView = layout === 'empty-view-1' ? true : false
return (
<ConnectedRouter history={history}>
<div
data-layout={layout}
data-background={background}
data-navbar={navbar}
data-logo={logo}
data-left-sidebar={leftSidebar}
data-top-navigation={topNavigation}
data-collapsed={collapsed}
>
<Shortcuts />
<Backdrops />
{!isEmptyView &&
<RightSidebar1 />
}
{!isEmptyView &&
<Navbar1 />
}
<div className="container-fluid">
<div className="row">
{!isEmptyView &&
<LeftSidebar1 />
}
<div className="col main">
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/policies/index" component={Policies}/>
<Route path="/pages/create-account" component={CreateAccount} />
<Route path="/pages/empty-page" component={EmptyPage} />
<Route path="/pages/under-maintenance" component={UnderMaintenance} />
<Route path="/pages/error-page" component={ErrorPage} />
<Route path="/pages/user-profile" component={UserProfile} />
<Route path="/on-notice" component={OnNotice} />
<Route path="/profile" component={UserProfile} />
<Route path="/kyc-documents" component={KYCDocuments} />
<Route path="/booking" component={Booking} />
<Route path="/bookings" component={Bookings} />
<Route path="/pay-amount" component={Payment} />
<Route path="/security-deposit" component={Deposit} />
<Route path="/transactions" component={Transactions} />
<Route path="/notice-board" component={NoticeBoard} />
<Route path="/deals" component={Deals} />
<Route path="/checkin" component={Checkin} />
<Route path='/subscriptions' component={MySubscriptions} />
<Route path='/view-ticket' component={ViewTicket} />
<Route path="/new-ticket" component={NewTicket} />
<Route component={EmptyPage} />
</Switch>
</div>
</div>
</div>
</div>
</ConnectedRouter>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
pathname: state.router.location && state.router.location.pathname ? state.router.location.pathname : window.location.pathname,
config: state.config,
tickets : state.ticket
}
}
const mapDispatchToProps = dispatch => {
return {
setConfig: (key, value) => dispatch(setConfig(key, value))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(customer)
I want to know how to do routing header and sidebar shouldn't be shown for non logged in user, there are few pages user can access without sign in
Above code which i have written is not routing.
Please guide me in right direction
router is old one method you can use ReactRouter (flowRouter kadira) package for easy routing
group.route('/', {
name: 'Routes.Task.List',
action () {
let props = {
content: (<Containers.List />),
title: 'title xyz',
pageTitle: 'title xyz',
};
mount(Wrapper, { props });
},
});
and now you can use this as flowRouter.path('Routes.Task.List')

Resources