Protected page using react-router-dom doesn't work - reactjs

I am trying to implement only logged in user dashboard page.
App.js
<BrowserRouter history={hist}>
<Route path="/sign-in" exact component={SignIn} />
<PrivateRoute path="/dashboard">
<Dashboard />
</PrivateRoute>
</BrowserRouter>
PrivateRoute.js
export function PrivateRoute({children, ...props}) {
return (
<Route {...props} render={( {location} ) =>
isAuthenticated ? children : <Redirect to="/sign-in" />
}
/>
);
}
const isAuthenticated = () => {
const userToken = JSON.parse(sessionStorage.getItem("user-token"));
return userToken ? true : false'
}
I am checking sessionStorage for user-token, if it is null return false or true.
Event though it returns "false", it redirect to Dashboard, not Sign-in page.
What is my problem?

The reason why router redirects to dashboard even not authenticated user is that isAuthenticated is a function. Since that, you need to call this function:
export function PrivateRoute({children, ...props}) {
return (
<Route {...props} render={( {location} ) =>
isAuthenticated() ? children : <Redirect to="/sign-in" />
}
/>
);
}
const isAuthenticated = () => {
const userToken = JSON.parse(sessionStorage.getItem("user-token"));
return userToken ? true : false'
}
If you don't want to invoke function each time route changes, you can implement an immediately invoked function:
const isAuthenticated = (() => {
const userToken = JSON.parse(sessionStorage.getItem("user-token"));
return userToken ? true : false'
})()
export function PrivateRoute({children, ...props}) {
return (
<Route {...props} render={( {location} ) =>
isAuthenticated ? children : <Redirect to="/sign-in" />
}
/>
);
}

In PrivateRoute.js do this..
<Route {...props} render={( {location} ) =>
isAuthenticated() ? children : <Redirect to="/sign-in" />
}
/>
The reason is that you are not calling the isAuthenticated. If the problem still persists, feel free to discuss.

Related

How to fix public route in react router v6 showing the login for a spli second

I have a problem with public and private route. In which when the user is already authenticated the private and public routes work. But for some reason when I refresh to the homepage it shows the login page for a split second.
How do i fix this?
PRIVATE ROUTE
const PrivateRoute = () => {
const auth = useAuth();
if (!auth.user) {
return <Navigate to="/login" />;
}
return <Outlet />;
};
PUBLIC ROUTE
const PublicRoute = () => {
const auth = useAuth();
if (auth.user) {
return <Navigate to="/" />;
}
return <Outlet />;
};
APP
<Routes>
<Route element={<PublicRoute />}>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Route>
<Route element={<PrivateRoute />}>
<Route path="/" element={<Home />} />
<Route path="/message" element={<Message />} />
</Route>
</Routes>
You should ensure that your "unauthenticated" state value doesn't match the "I don't know the authentication status" state value. What this means for example, if auth.user being true or some defined user object equals a user being considered "authenticated" and if a auth.user being false or null equals a user being considered "unauthenticated" then you shouldn't use an initial auth.user that is either true|<user object> or false|null.
In other words you want to use some indeterminant value to indicate the authentication status has yet to be determined, and in this indeterminant state you can render null or some loading indicator until the authentication status resolves.
You tagged with firebase so I'll be assuming that somewhere in your code is an onAuthStateChanged call that returns either the user object or null when there is no authenticated user. In this case the user comparison will be against undefined. Just ensure that the default auth.user value is undefined.
Assuming something similar:
export default function useAuth() {
const [auth, setAuth] = useState({}); // user undefined
const user = useFirebase();
useEffect(() => {
const unsubscribe = onAuthStateChanged(
user.auth,
(user) => setAuth({
user: user ?? false
})
);
return unsubscribe;
}, [user]);
return auth;
}
Example:
const PrivateRoute = () => {
const auth = useAuth();
if (auth?.user === undefined) {
return null; // or loading indicator/spinner/etc
}
return auth.user
? <Outlet />
: <Navigate to="/login" replace />;
};
const PublicRoute = () => {
const auth = useAuth();
if (auth?.user === undefined) {
return null; // or loading indicator/spinner/etc
}
return auth.user
? <Navigate to="/" replace />
: <Outlet />;
};

Condition based routing in react

I want to render routes based on the user login status.So, I made a function to check if the user is authenticated on the backend using axios and call that function in useEffect in App component and store the respone in the useState. And then I use condition in the Route component's element. If the user is authenticated, redirect the user to home page. If not, redirect to login page. But the problem is when I try to enter the route from url bar, I always get redirected to login page even I'm authenticated.
these are my codes(I removed some unrelated codes to look cleaner)
function App() {
const navigate = useNavigate();
const [isLoggedIn, setIsLoggedIn] = useState("NOT_LOGGED_IN");
const [user, setUser] = useState({});
const verifyLogin = async () => {
const res = await axios({
url: "http://localhost:5000/isloggedin",
method: "get",
withCredentials: true
})
if (res.data.isLoggedIn && isLoggedIn === "NOT_LOGGED_IN") {
setIsLoggedIn("LOGGED_IN");
setUser(res.data.user)
} else if (!res.data.isLoggedIn && isLoggedIn === "LOGGED_IN") {
setIsLoggedIn("NOT_LOGGED_IN");
setUser({})
}
}
useEffect(() => {
verifyLogin()
}, [])
return (
<div className="App">
<Routes>
<Route path='/' element={isLoggedIn === "LOGGED_IN" ? <Home isLoggedIn={isLoggedIn} user={user} /> : <Navigate to="/login" />} exact />
<Route path='/register' element={<Register handleRegister={handleRegister} registerError={registerError} />} />
<Route path='/register/:userId/info' element={isLoggedIn === "LOGGED_IN" ? <RegisterInfo handleRegister={handleRegister} registerError={registerError} /> : <Navigate to={"/register"} />} />
<Route path='/login' element={<Login isLoggedIn={isLoggedIn} handleLogin={handleLogin} logout={logout} />} />
</Routes>
</div >
);
}
I'm sorry if my writing made you confused. I'm not so good at English.
Issue
The issue is that your initial isLoggedIn state matches your unauthenticated state. When the app initially loads and you are trying to access any route the app uses this "NOT_LOGGED_IN" initial isLoggedIn state value and determines the user is not logged in and redirects accordingly.
Solution
The common solution is to start from an "indeterminant" state that is neither authenticated nor unauthenticated and conditionally render nothing, or a loading indicator, etc... anything but the routed component or the redirect.
Example:
function App() {
const navigate = useNavigate();
const [isLoggedIn, setIsLoggedIn] = useState(); // <-- initially undefined
const [user, setUser] = useState({});
useEffect(() => {
const verifyLogin = async () => {
const res = await axios({
url: "http://localhost:5000/isloggedin",
method: "get",
withCredentials: true
});
if (res.data.isLoggedIn && isLoggedIn === "NOT_LOGGED_IN") {
setIsLoggedIn("LOGGED_IN");
setUser(res.data.user);
} else if (!res.data.isLoggedIn && isLoggedIn === "LOGGED_IN") {
setIsLoggedIn("NOT_LOGGED_IN");
setUser({});
}
};
verifyLogin();
}, []);
if (isLoggedIn === undefined) { // <-- check if undefined
return null; // or loading indicator, etc...
}
return (
<div className="App">
<Routes>
<Route
path='/'
element={isLoggedIn === "LOGGED_IN"
? <Home isLoggedIn={isLoggedIn} user={user} />
: <Navigate to="/login" />
}
/>
<Route
path='/register'
element={(
<Register
handleRegister={handleRegister}
registerError={registerError}
/>
)}
/>
<Route
path='/register/:userId/info'
element={isLoggedIn === "LOGGED_IN"
? (
<RegisterInfo
handleRegister={handleRegister}
registerError={registerError}
/>
)
: <Navigate to={"/register"} />
}
/>
<Route
path='/login'
element={(
<Login
isLoggedIn={isLoggedIn}
handleLogin={handleLogin}
logout={logout}
/>
)}
/>
</Routes>
</div >
);
}
Further Suggestions
Abstract the isLoggedIn state and auth verification into a React context.
Abstract the protected routing logic into a wrapper or layout component.
This makes your code quite a bit more DRY.

React-router-dom not re rendering Switch when state is change

I am using aws-amplify, react-hook in my project. The app have some private Routes has been define below:
const ProtectedRoute = ({render: C, props: childProps, ...rest}) => {
return (
<Route
{...rest}
render={rProps =>
(childProps) ? (
<C {...rProps} {...childProps} />
) : (
<Redirect
to={`/login?redirect=${rProps.location.pathname}${
rProps.location.search
}`}
/>
)
}
/>
);
}
In App.js, we change childProps to define whether user is login or not. But when childProps change, Switch not re rendering. What is the way to force React re rendering its Route because isAuthenticated is change but ProtectedRoute is not rerender.
const [isAuthenticated, userHasAuthenticated] = useState(null);
useEffect(() => {
onLoad();
}, []);
async function onLoad() {
try {
let user = await Auth.currentSession();
if (user.accessToken.payload) {
userHasAuthenticated(user.accessToken.payload);
}
} catch (e) {
if (e !== 'No current user') {
alert(e);
}
}
}
.....
const childProps = isAuthenticated;
return (
<ApolloProvider client={client} >
<div className="App">
<BrowserRouter>
<Route path='/'>
<div>
<Switch>
<Route path='/login' render={props => <Login {...props}/>} exact/>
<ProtectedRoute
exact
path='/admin/:name'
render={()=> <Admin />}
props={childProps}
/>
<Route path='/' render={props => <User {...props} />}/>
</Switch>
</div>
</Route>
</BrowserRouter>
</div>
</ApolloProvider>)
The route only renders again when you enter that URL again. You are doing a Redirect, meaning it will never have a chance to enter the same URL after authentication is complete. You should delay rendering the protected route until you have confirmed authentication:
useEffect(() => {
async function onLoad() {
try {
let user = await Auth.currentSession();
userHasAuthenticated(!!user.accessToken.payload);
} catch (e) {
if (e !== 'No current user') {
alert(e);
}
}
}
onLoad();
}, []);
...
const ProtectedRoute = ({render: C, props: childProps, ...rest}) => {
if (childProps === null) {
// app still waiting authentication
return 'Loading...';
}
return (
<Route
{...rest}
render={rProps =>
(childProps) ? (
<C {...rProps} {...childProps} />
) : (
<Redirect
to={`/login?redirect=${rProps.location.pathname}${
rProps.location.search
}`}
/>
)
}
/>
);
}

React App redirects to route "/" when I try to navigate by changing url manually

I am creating a React app, that stores the users role (there are two possible roles 0, and 1 which are being used for conditional rendering) in a "global" React Context. The role gets assigned upon login. I am also using React Router to handle Routing, and wrote a ProtectedRoute component. My Problem is the following: When I am navigating via the NavBar all works perfectly fine, but when I enter e.g. /home into the url I get redirected to the LoginPage(which is the standard route when the role is not set to 0 or 1) and I can not access the other Routes anymore. However, the user does not get logged out (The session in the database is not being deleted), the app just seems to forget the global state "role", which is used to determine whether the user is allowed to access the individual routes. I am afraid my knowledge of the DOM and Router is too limited to solve this problem.
function App() {
return (
<AuthProvider>
<Router>
<NavigationBar />
<AuthContext.Consumer>
{context => (
<React.Fragment>
{!context.isAuthenticated() ? <Jumbotron/> : null}
</React.Fragment>
)}
</AuthContext.Consumer>
<Layout>
<Switch>
<Route exact path="/" component={() => <LandingPage />} />
<ProtectedRoute path="/home" component={Home} />
<ProtectedRoute path="/about" component={About}/>
<ProtectedRoute path="/account" component={Account} />
<ProtectedRoute path="/calender" component={Calender} />
<ProtectedRoute path="/xyz" component={Xyz} />
<ProtectedRoute path="/wasd" component={wasd} role={0} />
<Route component={NoMatch} />
</Switch>
</Layout>
</Router>
</AuthProvider>
);
}
export default App;
export const ProtectedRoute = ({ component: Component, ...rest }) => {
const { isAuthenticated, getRole } = useContext(AuthContext);
if (rest.role === 0) {
return (
<Route
{...rest}
render={props =>
getRole() === 0 ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/404",
state: {
from: props.location
}
}}
/>
)
}
/>
);
} else if (rest.role === 1) {
return (
<Route
{...rest}
render={props =>
getRole() === 1 ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/404",
state: {
from: props.location
}
}}
/>
)
}
/>
);
} else {
return (
<Route
{...rest}
render={props =>
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/",
state: {
from: props.location
}
}}
/>
)
}
/>
);
}
};
import React from "react";
const AuthContext = React.createContext();
export default AuthContext;
class AuthProvider extends Component {
constructor() {
super();
this.state = {
role: 2, //none=2
name: "",
email: ""
};
}
render() {
return (
<AuthContext.Provider
value={{
state: this.state,
isAuthenticated: () => {
if (this.state.role === 1 || this.state.role === 0) {
return true;
}
return false;
},
setRole: newRole =>
this.setState({
role: newRole
}),
getRole: () => {
return this.state.role;
},
setName: newName =>
this.setState({
name: newName
}),
getName: () => {
return this.state.name;
},
setEmail: newEmail =>
this.setState({
email: newEmail
}),
getEmail: () => {
return this.state.email;
},
}}
>
{this.props.children}
</AuthContext.Provider>
);
}
}
export default AuthProvider;
If you are entering the url directing into the browser, React will reload completely and you will lose all state whether 'global' or otherwise. The most likely scenario is that your router is trying to validate your ability to view a component before you have your auth data.
You don't include how you get your auth session from the database, but even if you refetch the auth session, there is going to be a period where your app has mounted, but you don't have the response yet. This will cause your protected route to believe you are unauthorized, and redirect to the fallback path.
Try adding a check either inside your protected route or before the router itself, that blocks rendering until your auth data is loaded. Although upon reading your question again, it seems like you may not be refetching your logged in user at all.

How to send variable along with Redirect in Reactjs?

I have the following in my react app:
<PrivateRoute
path="/profile"
component={ Profile }
authenticated={ this.state.authenticated }
/>
<PrivateRoute/> is basically just the below:
const PrivateRoute = ({ component : Component , authenticated : auth , ...rest }) => {
return (
<Route {...rest} render={ (props) => {
return (
auth ? <Component {...props} />
: <Redirect
to={{
pathname : '/login',
state : {
from : props.location
}
}} />
)
} } />
)
}
As you can see in the above code there is a auth variable , how do i send this variable to now i would like to send this variable alog with the <Redirect /> which basically loads the <Login /> component , but how exactly do i send the auth variable alog with the Redirect component ?
You can pass props data with Redirect like this:
const PrivateRoute = ({ component : Component , authenticated : auth , ...rest }) => {
return (
<Route {...rest} render={ (props) => {
return (
auth ? <Component {...props} />
: <Redirect
to={{
pathname : '/login',
state : {
from : {auth : auth}
}
}} />
)
} } />
)
}
and this is how you can access it:
this.props.location.state.auth
Use this.props.history.push and access that variable by this.props.location

Resources