An unauthenticated user can still see the "/account" page - javascript

An unauthenticated user can still see the "/account" page for a few seconds I mean there is still a delay of a few seconds before the unauthenticated user is completely redirected to another page. even though I have used ProtectedRoute, how can I make unauthenticated users unable to see the "/account" page at all without delay? i use react-router-dom 6.6.1 and firebase auth
here is for the app.js
import { ThemeProvider } from '#mui/material/styles';
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom'
import Home from './components/Home'
import Account from './components/Account';
import Signin from './components/Home/Signin';
import theme from './theme';
import { AuthContextProvider } from './components/Context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
<Route path='/account' element={<ProtectedRoute><Account /></ProtectedRoute>} />
</Routes>
</Router>
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
and this is for the ProtectedRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
import { UserAuth } from './Context/AuthContext';
const ProtectedRoute = ({ children }) => {
const { user } = UserAuth();
if (!user) {
return <Navigate to='/' />;
}
return children;
};
export default ProtectedRoute;
this if for the UserAuth :
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
} from 'firebase/auth';
import { auth } from '../../firebase';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<UserContext.Provider value={{ createUser, user, signIn }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
I want to redirect unauthenticated users to the main page without delay

it’s probably due to the fact that your guard is validating off of !user however your initial auth state has user as {}. In this case an empty object will validate as truthy.
You could either set the initial state of user as null or you could update th guard validation to check for a value on user like if (!user.id) …

Render Protected and Base routes conditional so unauthenticated user never have access of Protected routes.
Use the following code.
Always make separate file for routing for better code readability and Separation of concerns.
BaseRoutes.js
import React from 'react'
import { Route } from 'react-router-dom'
import Home from './components/Home'
import Signin from './components/Home/Signin';
const BaseRoutes = (
<>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
</>
)
export default BaseRoutes
ProtectedRoutes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Account from './components/Account';
const ProtectedRoutes = () => {
return (
<>
<Route path='/account' element={<Account />} />
</>
)
};
export default ProtectedRoutes;
Routes.js
import React from 'react'
import { Navigate, Route, Routes, BrowserRouter } from 'react-router-dom'
import { UserAuth } from './Context/AuthContext';
import ProtectedRoutes from './components/ProtectedRoutes';
import BaseRoutes from './components/BaseRoutes';
const Router = () => {
const { user } = UserAuth();
const isUser = Boolean(user)
const redirectUrl = isUser ? '/account' : '/signin'
return (
<BrowserRouter>
<Routes>
{isUser ? <BaseRoutes /> : <ProtectedRoutes />}
<Route
path="*"
element={<Navigate to={redirectUrl} />}
/>
</Routes>
</BrowserRouter>
)
}
export default Router
app.js
import React from 'react';
import { AuthContextProvider } from './components/Context/AuthContext';
import { ThemeProvider } from '#mui/material/styles';
import theme from './theme';
import Router from './routes'
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router />
</AuthContextProvider>
</ThemeProvider>
)
}
export default App

Related

react-router-Dom: Blank page after redirect multiple user on login in React

I have two(USER and ADMIN) users in my react project and I want to redirect them to their respected page after a successful login. To achieve this I created a standalone component called authenticatedRoute.js and wrap those components by AuthenticatedRoute component. Here are the codes
AuthenticatedRoute.js
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ children, ...rest }) => {
const history = useHistory();
var tokenn = JSON.parse(localStorage.getItem("desta"));
var user_type = tokenn?.user.roles[0]; //USER or ADMIN
var loggedIn = tokenn?.user_status; //true
const redirect = () => {
try {
switch (user_type) {
case "USER":
return [history.push("/user"), children];
case "ADMIN":
return [history.push("/admin"), children];
default:
console.log("redirect");
}
} catch (err) {
return [history.push("/signin"), children];
}
};
return (
<Route
render={() => {
return loggedIn ? redirect() : history.push("/signin");
}}
/>
);
};
export default AuthenticatedRoute;
App.js (minimal code)
import "./App.css";
import Header from "./components/Header/Header";
import HeaderLinks from "./components/Header/HeaderLinks";
import Hero from "./components/Header/Hero/Hero";
import Login from "./components/auth/Login";
import {
BrowserRouter as Router,
Route,
Switch,
useHistory,
} from "react-router-dom";
import SignUp from "./components/auth/SignUp";
import ViewUserProfile from "./components/Admin/ViewUserProfile";
import AdminHeader from "./components/Admin/AdminHeader";
import ManageBusinessType from "./components/Admin/ManageBusinessType";
import ManageCompany from "./components/Admin/ManageCompany";
import UserDashboard from "./components/Users/UserDashboard";
import ViewProfile from "./components/Users/ViewProfile";
import { ResetPassword } from "./components/Users/ResetPassword";
import ChangePassword from "./components/Users/ChangePassword";
import { useEffect } from "react";
import AuthenticatedRoute from "./components/auth/authRoute/AuthenticatedRoute";
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/" exact>
<Hero />
</AuthenticatedRoute>
<AuthenticatedRoute path="/admin" exact>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" exact>
<UserDashboard />
</AuthenticatedRoute>
</Switch>
</Router>
);
}
export default App;
After a successful login it redirects me to the right user role page but it's empty page as the screenshot below.
How can I fix the Issue? I used react-router-dom#5.3.0
Thanks
You are rendering the result of a navigation action instead of JSX. AuthenticatedRoute should render either a Route rendering the routed content or a Redirect to the appropriate path.
Example:
If loggedIn is falsey, then redirect to "/signin", otherwise check the role the route should have access to, and if the role matches render a Route with the props passed through, otherwise redirect to the appropriate user/admin path.
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ roles, ...props }) => {
const tokenn = JSON.parse(localStorage.getItem("desta"));
const user_type = tokenn?.user.roles[0]; //USER or ADMIN
const loggedIn = tokenn?.user_status; //true
return loggedIn
? roles.includes(user_type)
? <Route {...props} />
: <Redirect to={user_type === "USER" ? "/user" : "/admin"} />
: <Redirect to="/signin" />;
};
export default AuthenticatedRoute;
Specify the roles a route should accessible by.
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/admin" roles={["ADMIN"]}>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" roles={["USER"]}>
<UserDashboard />
</AuthenticatedRoute>
<Route path="/">
<Hero />
</Route>
</Switch>
</Router>
);
}

Facing issue with Custom Route with latest react-router-dom version 6

Recently, I am facing issue with Custom Route with latest react-router-dom version 6 in my code.
I have created custom route for authorized if user is not login then it will redirect to sign in page. It was working with previous react router version but now it's got broke with latest one.
I am posting the code below.
Getting below error message as well.
[UserRoute] is not a component. All component children of must be a Route or <React.Fragment>
LoadingToRedirect.js
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
const LoadingToRedirect = () => {
const [count, setCount] = useState(5);
const navigate = useNavigate();
useEffect(() => {
const interval = setInterval(() => {
setCount((currentCount) => --currentCount);
}, 1000);
count === 0 && navigate("/login");
return () => clearInterval(interval);
}, [count, navigate]);
return (
<div>
<p>Redirecting you in {count} seconds</p>
</div>
);
};
export default LoadingToRedirect;
UserRoute.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import { useSelector } from "react-redux";
import LoadingToRedirect from "./LoadingToRedirect";
const UserRoute = ({ children, ...rest }) => {
const { currentUser } = useSelector((state) => state.user);
return currentUser ? <Route {...rest} /> : <LoadingToRedirect />;
};
export default UserRoute;
App.js
import React, { useEffect, Fragment } from "react";
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import UserRoute from "./components/UserRoute";
import { useDispatch } from "react-redux";
import { auth } from "./firebase";
import { setUser } from "./redux/actions";
import Header from "./components/Header";
import AddEdit from "./pages/AddEdit";
function App() {
const dispatch = useDispatch();
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if (authUser) {
dispatch(setUser(authUser));
} else {
dispatch(setUser(null));
}
});
}, [dispatch]);
return (
<BrowserRouter>
<div className="App">
<Header />
<ToastContainer position="top-center" />
<Fragment>
<Routes>
<UserRoute path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<UserRoute path="/addContact" element={<AddEdit />} />
</Routes>
</Fragment>
</div>
</BrowserRouter>
);
}
export default App;
In react-router v6, the Routes component allows only Route children.
In UserRoute.js if there's no user you are returning LoadingToRedirect:
return currentUser ? <Route {...rest} /> : <LoadingToRedirect />;
You can try changing this to:
const UserRoute = ({ children, path, element }) => {
const { currentUser } = useSelector((state) => state.user);
return <Route path={path} element={ currentUser ? element : <LoadingToRedirect />} />;
};

ReactJS: Global Okta Auth Helper Function

I have my React web app authenticated using Okta however if the user is not authenticated or the token expires then it will redirect the user to the /login page.
Page1.js:
import React, { useState, useEffect } from "react";
import { useOktaAuth } from "#okta/okta-react";
import { Redirect, useHistory } from "react-router-dom";
const { authState } = useOktaAuth();
const history = useHistory();
useEffect(() => {
if (!authState) {
history.push("/login");
}
});
Is there a way I can include the above into an function that I can import to each different Page[#].js instead of including the above code in each file?
Just trying to refactor and make sure my code is tidy as I go but unsure the best way to tackle this.
TIA!
You can wrap all the components that required users to be logged in inside a component and protect the routes.
Once the user logs in, set a token in localstorage or redux (you need to persist it). Specify the route to go through IsloggedIn component so that, those routes will only be accessible if user is logged in, else will be redirected to the login page
Example
import React from "react";
import { Redirect, Route } from "react-router-dom";
function IsLoggedIn({ Component, ...props }) {
const isAuthenticated = localStorage.getItem("isAuthenticated");
return (
<Route {...props}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
export default IsLoggedIn;
In App.js
function App(){
return(
<BrowserRouter>
<Route exact path="/login" component={Login} />
<IsLoggedIn exact path="/" component={Page1} />
</BrowserRouter>
)
}
you can use the below
private-route component
import React, { useEffect } from 'react';
import { Route, Redirect, RouteProps, useHistory } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from 'app/config/store';
import { SecureRoute, useOktaAuth } from '#okta/okta-react';
import { authenticationSuccess } from '../reducers/authentication-slice';
export const PrivateRouteComponent = ({ component: Component, ...rest }: IOwnProps) => {
//reducer
const isAuthenticated = useAppSelector(state => state.authentication.isOktaAuthenticated);
const { authState, oktaAuth } = useOktaAuth();
const dispatch = useAppDispatch();
useEffect(() => {
if (!isAuthenticated && authState && authState.isAuthenticated) {
oktaAuth.token.getUserInfo().then(info => {
//action dispatch
dispatch(authenticationSuccess({ name: info.name, email: info.email }));
});
}
}, [authState, oktaAuth]); // Update if authState changes
const renderRedirect = props => {
return isAuthenticated && <Component {...props} />;
};
return <SecureRoute {...rest} render={renderRedirect} />;
};
export default PrivateRouteComponent;
in app.js
<Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
<Switch>
<Route path="/callback" component={LoginCallback} />
<PrivateRoute path="/" exact={true} component={Home} />
<PrivateRoute path="/home" exact={true} component={Home} />
</Switch>
</Security>

React.js how do i show pages depending on the status?

I want to implement a case where when users are logged in that they need to able to access the AppStack and the AuthStack. And if they not logged in, only the AuthStack will be accessible. Also when they try to access the AppStack they should get redirected. In react native I could handle this with a AuthStack and if something changed the user status he could see the one stack or the other one. But I don't know how to do it with reactjs
AuthNavigator
import React, { useState, useEffect } from 'react';
import app from './firebase';
import AuthStack from './stacks/AuthStack';
import AppStack from './stacks/AppStack';
function AuthNavigator() {
const [initializing, setInitializating] = useState(true);
const [user, setUser] = useState(true);
function onAuthStateChanged(result) {
setUser(result);
if (initializing) setInitializating(false);
if (result) {
app
.auth()
.currentUser.getIdToken(/* force Refresh */ true)
.then((idToken) => {
result.idToken = idToken;
setUser(result);
})
.catch((error) => {
console.log(error.message);
});
}
}
useEffect(() => {
const authSubscriber = app.auth().onAuthStateChanged(onAuthStateChanged);
return authSubscriber;
});
if (initializing) {
return null;
}
return user ? <AppStack user={user} /> : <AuthStack user={user} />;
}
export default AuthNavigator;
AppStack here i tried it like if there is a user then you can render the appStack else you get redirected to the authstack.
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Overview from '../pages/app/Overview';
import Sidebar from '../components/Sidebar/Sidebar';
function AppStack({ user }) {
return this.props.user ? (
<Router>
<Switch>
<Sidebar user={user} />
<Route path="/app-melior/overview" exact component={Overview}></Route>
</Switch>
</Router>
) : (
<Redirect path="/auth/signin"></Redirect>
);
}
export default AppStack;
AuthStack
import React from 'react';
import Navbar from '../components/Navbar/Navbar';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import SignIn from '../pages/auth/SignIn';
function AuthStack({ user, app }) {
return (
<>
<Router>
<Switch>
<Route path="/auth">
<Navbar />
<Route path="/auth/signin" app={app} exact component={SignIn} />
</Route>
</Switch>
</Router>
</>
);
}
export default AuthStack;
I usually create a Private route and hanlde it like this
import React, { useState, useEffect } from 'react';
import app from './firebase';
import AuthStack from './stacks/AuthStack';
import AppStack from './stacks/AppStack';
function AuthNavigator() {
const [initializing, setInitializating] = useState(true);
const [user, setUser] = useState(true);
const PrivateRoute = ({ component, ...rest }) => {
return (
<Route
{...rest}
render={(props) =>
user ? (
<Component {...props}></Component>
) : (
<Redirect to="/auth"></Redirect>
)
}
></Route>
);
};
function onAuthStateChanged(result) {
setUser(result);
if (initializing) setInitializating(false);
if (result) {
app
.auth()
.currentUser.getIdToken(/* force Refresh */ true)
.then((idToken) => {
result.idToken = idToken;
setUser(result);
})
.catch((error) => {
console.log(error.message);
});
}
}
useEffect(() => {
const authSubscriber = app.auth().onAuthStateChanged(onAuthStateChanged);
return authSubscriber;
});
if (initializing) {
return null;
}
return
<Router>
<Switch>
<PrivateRoute path="/app>
<Sidebar user={user} />
<PrivateRoute path="/app-melior/overview" exact component={Overview}></PrivateRoute>
</PrivateRoute>
<Route path="/auth">
<Navbar />
<Route path="/auth/signin" app={app} exact component={SignIn} />
</Route>
</Switch>
</Router>;
}
export default AuthNavigator;

How to redirect to a different page after authenticated with React

I have bought a template in envato which is quite complex, I know a bit of react, I am not an expert yet, however I managed to integrate Azure AD Authentication into the app.
I used this procedure:
https://github.com/salvoravida/react-adal
My app is like this:
DashApp.js
import React from "react";
import { Provider } from "react-redux";
import { store, history } from "./redux/store";
import PublicRoutes from "./router";
import { ThemeProvider } from "styled-components";
import { LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import themes from "./settings/themes";
import AppLocale from "./languageProvider";
import config, {
getCurrentLanguage
} from "./containers/LanguageSwitcher/config";
import { themeConfig } from "./settings";
import DashAppHolder from "./dashAppStyle";
import Boot from "./redux/boot";
const currentAppLocale =
AppLocale[getCurrentLanguage(config.defaultLanguage || "english").locale];
const DashApp = () => (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<DashAppHolder>
<Provider store={store}>
<PublicRoutes history={history} />
</Provider>
</DashAppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
Boot()
.then(() => DashApp())
.catch(error => console.error(error));
export default DashApp;
export { AppLocale };
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';
import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';
const DO_NOT_LOGIN = false;
runWithAdal(authContext, () => {
ReactDOM.render(<DashApp />, document.getElementById('root'));
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./dashApp.js', () => {
const NextApp = require('./dashApp').default;
ReactDOM.render(<NextApp />, document.getElementById('root'));
});
}
},DO_NOT_LOGIN);
registerServiceWorker();
and router.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { ConnectedRouter } from 'react-router-redux';
import { connect } from 'react-redux';
import App from './containers/App/App';
import asyncComponent from './helpers/AsyncFunc';
const RestrictedRoute = ({ component: Component, isLoggedIn, ...rest }) => (
<Route
{...rest}
render={props => isLoggedIn
? <Component {...props} />
: <Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>}
/>
);
const PublicRoutes = ({ history, isLoggedIn }) => {
return (
<ConnectedRouter history={history}>
<div>
<Route
exact
path={'/'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<Route
exact
path={'/signin'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<RestrictedRoute
path="/dashboard"
component={App}
isLoggedIn={isLoggedIn}
/>
</div>
</ConnectedRouter>
);
};
export default connect(state => ({
isLoggedIn: state.Auth.get('idToken') !== null,
}))(PublicRoutes);
The authentication is working fine, but its redirected to a custom login page the template has, I want the redirect to be to /dashboard instead.
Thats the question, should be easy, but new to react routing
Please Give a try. (I'm not 100% sure - I just made a guess from the given code)
router.js
const PublicRoutes = ({ history, isLoggedIn }) => {
return (
<ConnectedRouter history={history}>
<div>
<Route
exact
path={'/'}
render={() => <Redirect to="/dashboard" />}
/>
<Route
exact
path={'/signin'}
component={asyncComponent(() => import('./containers/Page/signin'))}
/>
<RestrictedRoute
path="/dashboard"
component={App}
isLoggedIn={isLoggedIn}
/>
</div>
</ConnectedRouter>
);
};

Categories

Resources