Implementing PrivateRoute in React.js - javascript

I'm having some problems with implementing PrivateRoute in React. Here is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentUser: null,
loadingUser: true
}
}
componentDidMount() {
this.onAuth();
};
onAuth = () => {
getCurrentUser().then((json) => {
console.log(json);
this.setState({
currentUser: json,
loadingUser: false
})
}).catch((error) => {
this.setState({
currentUser: null,
loadingUser: false,
})
})
};
logout = () => {
logout();
this.setState({
currentUser: null,
loadingUser: false
});
this.props.history.push("/");
toast.info("Succesfully logout.");
};
render() {
return (
<div className="body">
<ToastContainer closeOnClick={false}/>
<ApplicationHeader currentUser={this.state.currentUser} logout={this.logout}/>
<Grid>
<div className="app-content">
<Switch>
<Route exact path="/vote/:id" render={(props) => <Vote currentUser={this.state.currentUser} {...props}/>}/>
<Route exact path="/login" render={() => <Login onAuth={this.onAuth} />}/>
<PrivateRoute authed={this.state.currentUser != null} exact path="/vote" component={NewProcess} />
<PrivateRoute authed={this.state.currentUser != null} exact path="/items" component={NewItems} />
<Route component={NotFound}/>
</Switch>
</div>
</Grid>
<Footer/>
</div>
);
}
}
const PrivateRoute = ({component: Component, authed, ...rest}) => {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />} />
)
}
When user posts credentials (or App main component gets rendered) onAuth method gets invoked and sets (or not) currentUser property of App's state. This property is null (when user is not authenticated) and represents userdetails such like id and username (when user is authenticated). Then, in PrivateRoute based on that property component gets rendered or application redirects user back to the login page. And that doesn't work well. I mean when i'm already authenticated and try to access any of private route, i am redirected to proper component. Problem occurs in 2 situations:
right after logging in - application doesnt redirect me to component
i want to access, insted i stay on the login page.
refreshing page (in browser) corresponded to private route.
It seems like PrivateRoute component doesnt get refreshed when currentUser property gets changed, which is kinda weird because i'm using similar approach in ApplicationHeader to display username when user is authenticated (and that is refreshed correctly).
So, what am i doing wrong here?

I did it rendering the route or the redirect depending on the condition and it worked for me.
Something like the following:
class PrivateRouteComponent extends React.Component{
render(){
return (
this.props.isAuthenticated===true?
(
<Route path={this.props.path} render={this.props.component} />
):
(<Redirect to={{
pathname: '/login',
state: { from: this.props.path }
}} />));
}
}

Related

How to preserve auth status with firebase in react component

I have a firebase onAuthStateChange and a set of private routes to be rendered with react router v6
useEffect(() => {
const fetchData = async () =>
await auth.onAuthStateChanged(async authUser => {
console.log('here in authuser')
if (authUser) {
await dispatch(setUser('SET_USER', authUser))
} else {
await dispatch(setUser('SET_USER', null))
}
})
fetchData()
}, [])
<Route path='/' element={<PrivateRoute user={users} />}>
<Route path='/courses/:id' element={<CourseDetails />} />
<Route
path='/'
element={<Courses emailId={users?.user?.email} />}
/>
<Route path='/enrolled' element={<Enrolled />} />
<Route path='/authored' element={<Authored />} />
<Route path='/users' element={<Users />} />
</Route>
In the protected route component I am checking if user is null then redirect user to login page else render the children.
if (user === null || user.user === null) {
console.log('Entered!!!!!')
return <Navigate to='/login' replace />
} else {
return children ? children : <Outlet />
}
On page refresh if I am logged in also I am redirected to login route because onauthstatechange has not finished executing. So user is null inside the
What is the right way to handle this situation and make sure that user gets navigated to the same page where reload happened.
You can create a component that handles the check for you.
So in your route you wrap your PrivateRoute with your route component like this :
<Route path='/courses/:id' element={<PrivateRoute><CourseDetails /></PrivateRoute> } />
So what this component does is check if the user is authed. If it is authed it will render the child component that is your route. If not it will redirect you to /login
they are talking about it in the docs
const PrivateRoute =({ children }: { children: JSX.Element }) => {
let auth = useAuth();
let location = useLocation();
if (!auth.user) {
// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
// along to that page after they login, which is a nicer user experience
// than dropping them off on the home page.
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}

Redirect user to protected route if user already logged in causes login page to re-render and display for sometime before redirection

I have a react + firebase application which has protected routes. I face issue if a logged in user accesses the login page. The issue is that the login page gets displayed for a second and then redirects to the home page i.e protected route. I feel the issue is because the value retrieved from context in the login page to check if user is authenticated gets updated after the route is resolved. Can someone give me pointers on how should I fix this. Ideally I would not want the user to see the login page for sometime if the user is already authenticated.
//App.js
render() {
return (
<AuthProvider>
<Router>
<Switch>
<PrivateRoute exact path="/" component={Home}></PrivateRoute>
<Route exact path="/login" component={LoginPage}></Route>
<Route exact path="/signup" component={SignUp}></Route>
</Switch>
</Router>
</AuthProvider>
);
}
}
//AuthProvider
import React, { useEffect, useState } from "react"
import { fire } from "./Fire"
export const AuthContext = React.createContext();
//this component will maintain the current user throughout the app
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null)
//empty array as second arg to useEffect hook as we only want to trigger it once
useEffect(() => {
console.log("useEffect")
fire.auth().onAuthStateChanged(setCurrentUser)
}, [])
return (
<AuthContext.Provider value={{ currentUser }}>{children}</AuthContext.Provider>
)
}
//PrivateRoute
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
//useContext hook makes it very easy to retrieve the value
const { currentUser } = useContext(AuthContext)
return (
<Route {...rest} render={
routeProps => {
console.log("currentUser" + currentUser);
return !!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/login"} />
)
}
} />
)
}
//login
render() {
if (this.props.context.currentUser)
return <Redirect to="/" />
return (
<Login email={this.state.email} password={this.state.password} inputHandler={this.onInputChange} loginHandler={this.onLoginClick} />
)
}
You should probably add a loading check in your PrivateRoute file which will just show a Loading... or a loader if the user is not loaded yet.
For that you will have to do some minor changes in your AuthProvider file and in PrivateRoute.
//PrivateRoute
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
//useContext hook makes it very easy to retrieve the value
const { currentUser } = useContext(AuthContext)
return (
<Route {...rest} render={
routeProps => {
console.log("currentUser" + currentUser);
return !!currentUser ? (
<RouteComponent {...routeProps} />
) : currentUser === 'loading' ? <h1>Loading...</h1>
:(
<Redirect to={"/login"} />
)
}
} />
)
}

If statement not working based on Component props, see Edit

I am currently trying to implement some basic authentication by wrapping a react Route element.
Here loggedIn is the hook value, and setLoggedIn is the "setter". The setter is passed by props to the "Login" component which correctly sets the hook's value. However, the "App" return function renders with the old value.
Functional Component:
function App() {
const [loggedIn, setLoggedIn] = useState(false);
return (
<Router>
<div className="App">
<Route path="/budget" component= {BudgetDisplay}/>
<Route path="/login" render={() => <Login loggedIn={loggedIn} setLoggedIn={setLoggedIn}/>}/>
<PrivateRoute path="/admin" loggedIn={loggedIn} component={Admin}/>
</div>
</Router>
);
}
export default App;
Route Component wrapper:
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => (
<Route {...rest} render={(props) => (
loggedIn === true
? <Component {...props}/>
: <Redirect to="/login"/>
)}/>
)
As the return utilises the previous value, the redirect component is rendered instead of the correct Component.
I am aware of the useEffect method, however I am not sure how to implement it such that "App" returns the correct value.
I have tried making the same Functional Component as a Class Component using setState instead, but ran into the same issue.
Edit:
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
console.log(loggedIn)
if ( loggedIn ) {
return <Route {...rest} render={(props) => <Component {...props}/>}/>;
} else {
return <Route {...rest} render={() => <Redirect to="/login"/>}/>;
};
};
The correct value is logged here, loggedIn = true, however the Redirect is returned.
The solution was removing the {...rest} from PrivateRoute, still don't know why though.
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
if ( loggedIn ) {
return <Route {...rest} render={(props) => <Component {...props}/>}/>;
} else {
return <Route render={() => <Redirect to="/login"/>}/>;
};
};

React Router Redirect after Login not working properly

I'm developing a basic react application and included react-router.
I have a simple authentication control with the Local Storage.
After a user inputs username and password and clicks login, I do an HTTP call and take response from the server with Axios. Then I set the localStorage 'user' item.
To protect a route I implemented the PrivateRoute component where I check if 'user' in localStorage is set.
I already tried to move set Local Storage inside then() in the Axios HTTP call but nothing changed.
Api CALL
loginUser (username,password) {
return HTTP.post('/login', null, { params: {
username,
password
}})
Api.loginUser(username,password)
.then( (response) => {
console.log("Response DATA");
Api.saveUserData(response.data);
this.setState({ redirect: true });
})
RENDER METHOD
if (this.state.redirect === true) {
return <Redirect to='/home'/>;
}
APP COMPONENT
class App extends Component {
render() {
return (
<Router>
<Route path="/login" component={Login} />
<PrivateRoute path="/home" component={Home} />
</Router>
);
}
}
PRIVATE ROUTE COMPONENT
const PrivateRoute = ({ component: Component, ...rest }) => {
const isLoggedIn = AuthService.isAuthenticated();
return (
<Route
{...rest}
render={props =>
isLoggedIn ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
)
}
The problems seem to be: the local storage is set after the redirect because is null. So I get blank page instead of loading the Home Component. If i refresh the page, the code works fine.

Why wont my Auth0 find my callback route in react?

Auth0 redirects to http://localhost:3000/callback#/acccess-token=dxy
I'm getting a blank screen in my react app.
Heres my main app.js
render((
<HashRouter>
<Main />
</HashRouter>
), $('#app')[0]);
My main contains my routes.js component.
class Routes extends Component {
constructor(props, context) {
super(props, context);
this.state = { mainData: this.props.mainData };
this.handleAuthentication = this.handleAuthentication.bind(this)
}
componentWillReceiveProps(newProps) {
this.setState((previousState) => update(previousState, {
mainData: { $set: newProps.mainData },
}));
}
handleAuthentication(nextState, replace) {
if (/access_token|id_token|error/.test(nextState.location.hash)) {
this.props.auth.handleAuthentication();
}
}
render() {
return (
<div>
<Switch>
<Route path='/callback' render={props => {
this.handleAuthentication(props);
return <Callback {...props} />
}} />
<Route exact path='/' render={props => (
<Dashboard changeAppBar={this.props.changeAppBar} userProfile={this.state.mainData.userProfile} windowHeight={this.props.wh} windowWidth={this.props.ww} />)}
/>
<Route path='/settings' render={props => (
<Settings changeAppBar={this.props.changeAppBar} userProfile={this.state.mainData.userProfile} />)}
/>
</Switch>
</div>
);
}
}
export default Routes;
Heres my init of auth0
this.auth0 = new auth0.WebAuth({
clientID: 'oiEjW4Mf6Ju4BvRfHeuObQnMbghKs38g',
domain: 'cryptok1ng.auth0.com',
responseType: 'token id_token',
redirectUri: 'http://localhost:3000/callback'
})
Everything works fine until I get redirected from auth0 back to /callback. Simply doesn't find a screen /route and renders nothing.
Screenshot of the console. /callback breakpoint is never hit.
Thanks for any help I've been going through the docs and answers to no avail.
I am assuming you In Auth0 front end client configuration> you have added the callback URi as http://localhost:3000/callback and saved it.
And also in your callback.html file you added some tags to show up something once the token is authenticated properly.
If everything is fine and you still get blank error. Please post your console screenshot to have a look.

Categories