Private Route React Router And Redux - reactjs

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)

Related

How to restrict access to routes in react-router-dom based on session?

I have a React front-end and Python back-end with user session management using flask-login's HttpOnly Session Cookies. How can I restrict react-router-dom routes based on this type of session management? I've created a ProtectedRoute component:
import { Route, Redirect } from 'react-router-dom';
class ProtectedRoute extends Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
}
}
render() {
const { component: Component, ...props } = this.props
return (
<Route
{...props}
render={props => (
this.state.authenticated ?
<Component {...props} /> :
<Redirect to='/login' />
)}
/>
)
}
}
export default ProtectedRoute;
Is it possible to set this.setState({authenticated: true}) based on the existing session?
Why not pass authenticated (or isEnabledin my example) as a prop? ProtectedRoute will rerender when its props change. This is what I use in my React applications:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const ProtectedRoute = ({isEnabled, ...props}) => {
return (isEnabled) ? <Route {...props} /> : <Redirect to="/login"/>;
};
export default ProtectedRoute;
And then you can just use this like so:
<ProtectedRoute path="/dashboard" isEnabled={isAuthenticated()} />
I know this question is old but I was looking for the same thing. Here's my routes.js file:
import auth from './services/auth'
const PrivateRoute = ({isAuthenticated, ...props}) => {
return (isAuthenticated) ? <Route {...props} /> : <Redirect to="/login"/>;
};
class Routes extends React.Component {
constructor(){
super();
this.state = {
isAuthenticated: false
}
}
componentDidMount(){
auth.get('')
.then( async (response) => {
const status = await response.status
if (status === 200) {
this.setState({isAuthenticated: true})
} else {
this.setState({isAuthenticated: false})
}
})
.catch( async (error) => console.log(error))
}
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/login" exact component={Login} />
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/" component={() => "barra"}/>
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/home" component={() => "home"}/>
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/profile" component={() => "profile"}/>
</Switch>
</BrowserRouter>
)
};
}
And auth import is:
const axios = require('axios');
axios.defaults.withCredentials = true;
const auth = axios.create({
baseURL: "http://localhost:5000/auth"
})
export default auth;
So, basically I have a Flask app with Flask-Login running on another local server (with CORS enabled THIS IS VERY IMPORTANT) and if 200 is returned, user on react is authenticated.

React router private route not getting props from state (e.g. authentication state)

I'm trying to implement a private route component to redirect users that are not authenticated. The problem is when I render the component like <PrivateRoute authenticated={this.state.isAuthenticated} path='/private' component={Panel} currentUser={this.state.currentUser}, the private route redirects the authenticated users to the login page, instead of going to the panel.
In App.js I render all the routes, including <PrivateRoute/>, and I set the currentUser and isAuthenticated state variables in ComponentDidMount(), but I can't pass them to PrivateRoute.
App.js
//imports...
class App extends Component {
state = {
currentUser: null,
isAuthenticated: false,
isLoading: false
}
loadCurrentUser = () => {
this.setState({
isLoading: true
});
// imported method
getCurrentUser()
.then(response => {
this.setState({
currentUser: response,
isAuthenticated: true,
isLoading: false
});
})
.catch(error => {
console.log(error)
this.setState({
isLoading: false
});
});
}
componentDidMount() {
this.loadCurrentUser();
}
handleLogin = () => {
this.loadCurrentUser();
this.props.history.push("/");
}
render () {
return (
<React.Fragment>
<Navigation
currentUser={this.state.currentUser}
isAuthenticated={this.state.isAuthenticated}
handleLogout={this.handleLogout} />
<Switch>
<PrivateRoute
authenticated={this.state.isAuthenticated}
exact
path='/postulante'
component={Panel}
currentUser={this.state.currentUser} />
<Route
exact
path='/'
render={
(props) => <Landing {...props} />
} />
<Route
path="/login"
exact
render={
(props) => <Login onLogin={this.handleLogin} {...props} />
} />
</Switch>
</React.Fragment>
);
}
}
export default withRouter(App);
Note that the <Navigation /> component does get the state variables right.
PrivateRoute.js
//imports...
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={props =>
authenticated ? (
<Component {...rest} {...props} />
) : (
<Redirect
to={{
pathname: '/login',
state: { from: props.location }
}}
/>
)
}
/>
);
export default PrivateRoute
The problem has to do with the PrivateRoute component rendering the first-time without any updated props from the main App component.
If you were to navigate directly to the PrivateRoute path without first going into any other Route, you will be redirected back to /login. Your PrivateRoute tries to render before the parent App's componentDidMount() logic completes. So isAuthenticated is passed as false.
The opposite would occur if you start in lets say Home or Login, and you use a Link to go to the PrivateRoute.
Ultimately, this is why people use a state-management tool like redux to have the authenticated-state be shared globally instead of being passed through parent-components.
Although there is a workaround!
See sandbox for reference: https://codesandbox.io/s/intelligent-dan-uskcy
We can workaround this by using an additional state-value to check
whether the App component was ever intialized. We'll call this
wasInitialized
PrivateRoute will receive that as prop called wasInitialized, and if
we go directly to its component-path, wasInitialized will be false until App has a chance to complete its componentDidMount() logic.
If wasInitialized is falsey, we will not Redirect to /login,
instead we will just display an empty-string, giving the parent App's
componentDidMount() time to execute and update the
isAuthenticated value.
So now lets take a look at this line:
<Route {...rest} render={props => auth === true ? <Component
{...props} /> : !wasInitialized ? "" : <Redirect to="/login" />
}
In the next re-render, isAuthenticated will be either true or
false. If the user isAuthenticated, we render the expected component. If the user is not authenticated, we go to the next check. Now wasInitialized will have a value of true, so that check evaluates to false. Thus, since both checks do not pass, we redirect to /login.
App.js
class App extends Component {
state = {
currentUser: null,
isAuthenticated: false,
isLoading: false,
wasInitialized: false
}
loadCurrentUser = () => {
this.setState({
isLoading: true
});
// imported method
getCurrentUser()
.then(response => {
this.setState({
currentUser: response,
isAuthenticated: true,
wasInitialized: true,
isLoading: false
});
})
.catch(error => {
console.log(error)
this.setState({
isLoading: false,
wasInitialized: true
});
});
}
componentDidMount() {
this.loadCurrentUser();
}
handleLogin = () => {
this.loadCurrentUser();
this.props.history.push("/");
}
render () {
return (
<React.Fragment>
<Navigation
currentUser={this.state.currentUser}
isAuthenticated={this.state.isAuthenticated}
handleLogout={this.handleLogout} />
<Switch>
<PrivateRoute
authenticated={this.state.isAuthenticated}
path='/postulante'
component={Panel}
wasInitialized={this.state.wasInitialized}
currentUser={this.state.currentUser} />
<Route
exact
path='/'
render={
(props) => <Landing {...props} />
} />
<Route
path="/login"
exact
render={
(props) => <Login onLogin={this.handleLogin} {...props} />
} />
</Switch>
</React.Fragment>
);
}
}
export default withRouter(App);
Private
import React from "react";
import { Route, Redirect } from "react-router-dom";
const PrivateRoute = ({
component: Component,
auth,
wasInitialized,
...rest
}) => {
return (
<Route
{...rest}
render={props =>
auth === true ? (
<Component {...props} />
) : !wasInitialized ? (
""
) : (
<Redirect to="/login" />
)
}
/>
);
};
export default PrivateRoute;

React route authentication

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?

React Routing not rendering

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.

react router v4 authentication token keep redirecting to /

First of all here is my code:
Routes.js
import React from 'react';
import PropTypes from 'prop-types';
import { Redirect, Route } from 'react-router-dom';
import { PUBLIC_ROUTE, LOGIN_ROUTE } from './utils/constants';
const routes = ({ component: Component, ...rest }) => {
let { requireAuth, isAuthenticated } = rest;
if (!requireAuth) {
requireAuth = false;
}
// If user authenticated
if (isAuthenticated) {
// If page need auth
if (requireAuth === true) {
return (
<Route {...rest} render={props => <Component {...props} />} />
);
} else if (requireAuth === 'partial') { // If the page is doesn't require auth but can't be access if auth true
return <Redirect to={PUBLIC_ROUTE} />;
}
// If page doesn't need auth
return <Route {...rest} render={props => <Component {...props} />} />;
}
// If user not authenticated //
// page doesn't require Auth
if (requireAuth === false || requireAuth === 'partial') {
return <Route {...rest} render={props => <Component {...props} />} />;
}
// If page require Auth redirect user to login routes
return (
<Route
{...rest}
render={props => (
<Redirect
to={{
pathname: `${LOGIN_ROUTE}`,
state: { from: props.location },
}}
/>
)}
/>
);
};
routes.propTypes = {
component: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.func,
]),
};
export default routes;
App.js
const history = createHistory();
const mapStateToProps = state => {
const { auth, global } = state;
const { authenticated, user } = auth;
const { loading } = global;
return { authenticated, user, loading };
};
const reduxConnector = connect(mapStateToProps, { ping });
class App extends Component {
state = {
isAuthenticated: false,
};
static propTypes = {
authenticated: PropTypes.bool.isRequired,
};
componentWillReceiveProps(nextProps) {
this.setState({ isAuthenticated: nextProps.authenticated });
}
render() {
const { authenticated, user, loading } = this.props;
const { isAuthenticated } = this.state;
if (loading) {
return (
<div style={style.center}>
<MDSpinner />
</div>
);
}
return (
<ConnectedRouter history={history}>
<div>
<NotificationsSystem theme={theme} />
<Header isAuthenticated={isAuthenticated} user={user} />
<NavMobile />
<SideMenu />
<Nav />
<Switch>
<Routes
requireAuth={false}
isAuthenticated={isAuthenticated}
exact
path="/"
component={Welcome}
/>
<Routes
requireAuth={true}
isAuthenticated={isAuthenticated}
path="/point"
component={Point}
/>
<Routes
requireAuth="partial"
isAuthenticated={isAuthenticated}
path="/login"
component={Login}
/>
<Routes render={() => <h3>No Match</h3>} />
</Switch>
<Footer />
</div>
</ConnectedRouter>
);
}
}
export default reduxConnector(App);
Login.js
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter, Link, Redirect } from 'react-router-dom';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import cookie from 'react-cookie';
import { Headline, Section, renderField } from 'components';
import { ping } from 'redux/modules/auth';
function mapStateToProps(state) {
const { authenticated } = state.auth;
return { authenticated };
}
const reduxConnector = connect(mapStateToProps, { ping });
const token = cookie.load('_t');
class Login extends React.Component {
static defaultProps = {
loggingIn: false,
authenticated: false,
};
componentDidMount(){
if(cookie.load('_t')){
this.props.ping();
}
}
render() {
const { handleSubmit } = this.props;
const { from } = this.props.location.state || {
from: { pathname: '/' },
};
if (this.props.authenticated) {
return <Redirect to={from} />;
}
return <div>
<Headline title="Login" />
</div>;
}
}
export default withRouter(reduxConnector(reduxFormDecorator(Login)));
Let say I already login to my app. And i want to navigate to /point (need auth to be access), app will redirect to /login after check the token (this.props.ping()):
1. If the token is valid: it will redirect me back to point page.
2. If the token is invalid: it will redirect me to login page.
The problem:
After checking and token is valid, the app always redirect to /.
I've console.log my const {from} the result is {pathname: "/point", search: "", hash: "", state: undefined, key: undefined}. It never return back to previous page (in this situation /point page).
If I access '/' it never redirect to login to call ping. I need this because I've header that show user full name. So I need set the state to redux.
Any solution?
First, you should understand that the Redirect works once after the component was mounted. You can not relay on it within the re-rendering. You may use history.push() API method instead.
Second, I'm not sure that I understood you solution properly, but seems you want to redirect somewhere just for setting some data to the redux store (can't find where are you dispatching a redux action). If it's true, it's a bad practice. You may do it within a dedicated action after logging in and you don't have to relate this logic to any component, then the redirection won't be required.

Resources