I am trying to create a server-side auth guard for react route. My flow is like... Whenever a route is hit on the front-end, a backend call is made to check if the user's session is present in the Database (since we store session in the database to keep track of sessions).
Here is my app component code:
export default function App() {
const routeComponents = routes.map(({ path, component }) => <AuthenticatedRoute exact path={path} component={component} props={'exact'} key={path} />);
return (
<div>
{window.location.origin === constant.PRODUCTION_DOMAIN_NAME && <Route
path="/"
render={({ location }) => {
if (typeof window.ga === 'function') {
window.ga('set', 'page', location.pathname + location.search);
window.ga('send', 'pageview');
}
return null;
}}
/>}
<Switch>
<Route path="/login" component={LoginPage} />
{routeComponents}
</Switch>
<ToastContainer autoClose={constant.TOASTER_FACTOR} closeButton={false} hideProgressBar />
</div>
);
}
App.propTypes = {
component: PropTypes.any,
};
I have segregated the authenticated route into a separate class component
like:
export class AuthenticatedRoute extends React.Component {
componentWillMount() {
//I call backend here to check if user is authenticated by session Id in DB
}
render() {
const { component: Component, ...rest } = this.props;
return (
<Route exact {...rest} render={(props) => this.state.isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />} />
);
}
}
AuthenticatedRoute.propTypes = {
component: PropTypes.any,
....
};
const mapStateToProps = (state) => ({
reducer: state.get('myReducer'),
});
export default connect(mapStateToProps, { func })(AuthenticatedRoute);
But I face an issue where the login page is redirected twice. Could someone let me know a better approach for this?
Related
I am trying to build a simple React app with auth flow and private route, I am first checking if the user signed in and redirect it to specify a private route, but after the app goes to the private route she doesn't have the user info here is my code :
App.Js
class App extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
console.log('AUTH:' , this.props.signedIn);
return (
<div>
<BrowserRouter>
<Switch>
<Route exact path="/" render={() => <Redirect to={this.props.signedIn ? '/dashboard' : '/login'}/>}/>
<Route exact path="/login" component={Login} />
<PrivateRoute exact path="/dashboard" ><DashboardLayout/></PrivateRoute>
</Switch>
</BrowserRouter>
</div>
);
}
}
const mapStateToProps = state => ({
signedIn: state.auth.signedIn
});
const mapDispatchToProps = dispatch => ({
fetchUser: () => dispatch(fetchUser())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
PrivateRoute.js
const PrivateRoute = ({children, signedIn, ...rest}) => {
const checkToken = (location) => {
console.log("SignedIn", signedIn);
if(signedIn) {
return children;
}
else {
return <Redirect to={{pathname: '/login', state: {from : location}}}/>
}
};
return <Route
{...rest}
render={({location}) => checkToken(location)}
/>;
};
const mapStateToProps = state => ({
signedIn: state.auth.signedIn
});
export default connect(mapStateToProps, null)(PrivateRoute)
So, in my google console, I see my console log AUTH is set to false from the App.js and also my console log Signed In is set to false, but after a rerender my console log AUTH from the app is set to True but doesn't go to the PrivateRoute again
I use 3 returns for PrivateRoute
children - for authorized users
Redirect to login - for not authorized users
and some LoadingComponent - for waiting of fetching user
We've prepared list of Loading components with different structure of placeholders, they look like placeholders in youtube on page loading
const PrivateRoute = ({children, signedIn, isAuthReady, customPlaceholder, ...rest}) => {
return (
<Route {...rest} render={(location) => {
if (!isAuthReady) {
return customPlaceholder || <Placeholder />
}
if (signedIn) {
return children;
}
return <Redirect to={{pathname: '/login', state: {from : location}}} />
}} />
)
}
const mapStateToProps = state => ({
signedIn: state.auth.signedIn,
isAuthReady: state.auth.isAuthReady
});
export default connect(mapStateToProps)(PrivateRoute)
I'm new in React.js and in this I want to create a simple login app, so after the login is success, I want to update state value in Parent component and redirect to other page using history.push. But here I have a problem because push is undefined.
I plan to use the state value of isLoggedIn to hide some element before the user login.
Here is the code in Home.jsx
class Home extends Component {
state = {
isLoggedIn: false,
};
handleIsLoggedIn = (value) => {
this.setState({
isLoggedIn: value,
});
};
render() {
return (
<Router>
<Fragment>
<div className="navigation">
<img src="../../drawable/jdl_logo.png" />
<Link to="/member-list">Member</Link>
<Link to="/override-list">Override</Link>
<Link to="/">Login</Link>
</div>
<Route path="/" exact component={() => <Login onLogin={() => this.handleIsLoggedIn()} />} />
<Route path="/member-list" component={MemberDashboard} />
<Route path="/override-list" component={OverrideDashboard} />
</Fragment>
</Router>
);
}
}
And this is how to handle login in Login.jsx
loginUser = () => {
Axios.post("http://private-6fdd31-intern1.apiary-mock.com/interns/login", this.state.user).then(
(res) => {
if (res.data.role === "admin") {
this.onSuccessLogIn();
this.props.history.push("/member-list");
}
},
(err) => {
console.log(err);
}
);
};
onSuccessLogIn = () => {
this.props.onLogin(true);
};
Login isn't receiving the history prop from Route.
You can either pipe it through via the route props:
<Route
path="/"
exact
component={
routeProps => <Login {...routeProps} onLogin={this.handleIsLoggedIn} />
}
/>
Or you can wrap Login with the withRouter HOC and default export it for useage.
I am new in React and I was trying to route the authenticated user to the main page. Unfortunately the user remains on the login page, although he logged in successfully. Here the relevant snippet of main.js which handles the routing part.
render(){
const {user} = this.props
{console.log("Logged in user: " + JSON.stringify(this.props.user.email))}
{console.log("Logged in : " + JSON.stringify(this.props.user.loggedIn))}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
this.props.user.loggedIn === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
}} />
)} />
)
<BrowserRouter>
<main>
<Button icon='log out' floated='right' color='blue' onClick={()=>this.userLogout(user)}/>
<Switch>
<PrivateRoute exact path='/' component={withRouter(StudentsMain)}/>
<Route exact path='/about' component={About}/>
<Route exact path='/login' component={withRouter(Login)}/>
<PrivateRoute exact path='/settings' component={withRouter(Settings)}/>
<PrivateRoute exact path='/assessment/:id' component={Assessment}/>
</Switch>
</main>
</BrowserRouter>
And here the relevant part of the login component...
onValidSubmit = (formData) => {
console.log("logging in")
this.props.logInUser(formData)
this.setState({loggedIn: true})
};
onFormInfo = () => {
this.setState({
showInfo:true
})
}
.....
render(){
if (this.state.loggedIn === true) {
console.log('Redirecting to Main Page')
return <Redirect to='/' />
}
Perhaps somebody can give me a hint, why the user remains on the login page. If I change the url in the browser manually the routing works as expected. Thanks, Arnold
In general, you should lift the state up so that you'll trigger a re-render in the right place. In this example, you can see the difference between triggering the state in different places.
import React, { Component } from 'react';
import { render } from 'react-dom';
class Child extends React.Component {
state = {
loggedIn: false,
}
toggle = () => {
this.setState({ loggedIn: !this.state.loggedIn })
}
render() {
return <p onClick={this.props.onClick || this.toggle}>I am {this.state.loggedIn ? 'in' : 'out'}</p>
}
}
class App extends Component {
state = {
loggedIn: false,
}
toggle = () => {
this.setState({ loggedIn: !this.state.loggedIn })
}
render() {
return (
<React.Fragment>
<Child onClick={this.toggle} />
<Child />
</React.Fragment>
);
}
}
render(<App />, document.getElementById('root'));
Here is a live example.
I'm using react router v4 with redux and i want to make use of private and protected route to redirect if user is not logged in.
i have this Routes component:
class Routes extends Component {
render() {
const { auth } = this.props;
// so checks against isAuthenticated can pass
if (auth.isAuthenticated !== undefined) {
return (
<div>
<Route exact component={() => <Home />} />
<PublicRoute
authed={() => auth.isAuthenticated}
path="/login"
component={(routerProps) => <Login {...routerProps} />}
/>
<PublicRoute
authed={() => auth.isAuthenticated}
path="/register"
component={(routerProps) => <Register {...routerProps} />}
/>
<PrivateRoute
authed={() => auth.isAuthenticated}
path="/dashboard"
component={Dashboard}
/>
</div>
);
} else {
return <div></div>
}
}
}
function mapStateToProps(state) {
return {
auth: state.auth
}
}
export default withRouter(connect(mapStateToProps)(Routes));
it is implemented like this:
class Main extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
store.dispatch(checkAuth());
}
render() {
return (
<Provider store={store}>
<Router>
<Theme>
<Routes />
</Theme>
</Router>
</Provider>
);
}
}
This is the PrivateRoute:
export function PrivateRoute({ component: Component, authed, ...rest }) {
const isAuthenticated = authed();
return (
<Route
{...rest}
render={props =>
isAuthenticated === true ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
What is the best way to pass that auth prop, is it ok if i connect the Routes component and pass the auth from there, or should i be passed from somewhere else, maybe connect each component that needs it and read from there? Also how would this approach play with nested routes, like /dashboard/settings? Thanks
Actually, it is ok to use this type of private route in react, but you should check two moments:
I should check, that you do not have exact attribute, so all your routes like /dashboard/panel1, /dashboard/panel2 will be private to
auth.isAuthenticated}
path="/dashboard"
component={Dashboard}
/>
You will have some problem with connect. There is a simple fix for that:
export default connect(mapStateToProps, null, null, {
pure: false,
})(PrivateRoute);
more information here:
React router private routes / redirect not working
I am using React routing v4 for a application that has a login and a home page once a dumb auth is done.
As of this point I have this LoadComponent.jsx in my index.js file:
class LoadComponent extends Component {
state = {
isLoggedOn: false,
};
onLoginCheck = (name, password) => {
console.log(name, password);
if (name && password) {
setTimeout(name, 100); // fake async
console.log('set the timeout');
}
this.setState({
isLoggedOn: true,
});
};
checkAuth = () => {
const { isLoggedOn } = this.state;
console.log('checking auth: ', isLoggedOn);
return (isLoggedOn);
};
render() {
return (
<BrowserRouter >
<Switch>
<Header isLogged={this.checkAuth()} />
<Route path="/login" render={props => <Login isLoggedOn={this.state.isLoggedOn} onLoggedInCheck={this.onLoginCheck} {...props} />} />
<PrivateRoute path="/" component={App} authenticated={this.state.isLoggedOn} />
</Switch>
</BrowserRouter>
);
}
}
My privateRouter looks like the following :
const PrivateRoute = ({
component, exact = false, path, authenticated,
}) => {
console.log('here : ', authenticated);
return (
<Route
exact={exact}
path={path}
render={props => (
authenticated ? (
React.createElement(component, props)
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
)}
/>
);
};
export default PrivateRoute;
The only thing that is rendered on the page is the Header component which makes sense, but the PrivateRoute component is not functioning since at first the Login component should be displaying. Im not sure what I am doing wrong here since I have followed the react router Redirect Auth example to some degree.