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
Related
Is it possible in React to conditionally render a inside a using this kind of component?
import PropTypes from 'prop-types'
import { useApplication } from 'ice-components'
const canEdit = (currentUser) => {
if (currentUser.settings.canEditSpecRec.value === 'False' || !currentUser.location.isSpecimenReceptionType) {
return false
}
return true
}
const Permissions = ({ children }) => {
const { currentUser } = useApplication()
if (!canEdit(currentUser)) {
return null
}
return children
}
Permissions.propTypes = {
children: PropTypes.node,
}
export { Permissions as default, canEdit }
And the route is made in this way:
<Switch>
<Route exact path component={component_1} />
<Route path='/anotherPath' component={component_2} />
<Switch>
I tried to wrap the Permissions component around a single Route component but it breaks. Any ideas?
A good way to conditionally render a Route is the one provided by the section Auth of the react-router documentation:
https://reacttraining.com/react-router/web/example/auth-workflow
What's different in your application is that you're not authorizing based on authentication but on some user permission, you could do something like this:
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
canEdit(currentUser) ? (
children
) : (
<Redirect
to={{
pathname: "/",
}}
/>
)
}
/>
);
}
Wrapping the Route in a PrivateRoute component that will return your route if user has permission to or something else if not (maybe a redirect).
export default function AuthExample() {
return (
<Router>
<div>
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
<PrivateRoute path="/protected">
<ProtectedPage />
</PrivateRoute>
</Switch>
</div>
</Router>
);
}
When a user tries to view a private page(/products/1/edit), he will be redirected to the login component.
After login i want to redirect the user to the same product edit page. **The problem is that I am unable to get the location props in the component. It is returning as undefined.
** The code is as follows.
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<AppRouter />
</div>
);
}
}
export default App;
AppRouter.js
import { ConnectedRouter } from 'react-router-redux';
...
class AppRouter extends Component {
componentDidMount() {
this.props.checkAlreadyLoggedIn();
}
render() {
const { token, location } = this.props;
return ((
<ConnectedRouter history={history}>
<Layout style={{ minHeight: '100vh' }}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" render={ () => token && <Redirect to=
{
location.state && location.state.from ? location.state.from.pathname :
"/dashboard"
} /> || <Login />} />
<PrivateRoute path="/dashboard" component={Dashboard} />
...
</Switch>
</Layout>
</Layout>
</ConnectedRouter>
));
}
}
history.js
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
export default history;
i am already defined the private route as follows
class PrivateRoute extends PureComponent {
static propTypes = {
token: PropTypes.string,
component: PropTypes.any
}
render() {
const { component: Component, ...rest } = this.props;
let token = utils.getUserToken();
return (
<Route
{...rest}
render={ () =>
(token && <Component {...this.props} />) ||
<Redirect to={{
pathname: '/login',
state: { from: this.props.location }
}}/>
}
/>
)
}
}
Only if i get the location.state.from.pathname, i will be able to redirect the user to the page that he tried to access prior to login.
Any idea on how to fix this?
This is the PrivateRoute implementation I use and it performs redirect:
import React from 'react';
import { Route, Redirect } from "react-router-dom";
import { isLoggedIn } from '../../utils/authHelper';
class PrivateRoute extends React.Component {
renderComponent = () => {
const { path, component: Component } = this.props;
return (
isLoggedIn()
? <Component {...this.props} />
: <Redirect to={{
pathname: '/login',
state: { from: path }
}} />
)
}
render (){
const { component: Component, ...rest } = this.props;
return (
<Route {...rest} render={this.renderComponent} />
)
}
}
export default PrivateRoute;
And usage is like:
<Switch>
<Route path='/login' component={LoginScreen} />
<PrivateRoute path='/profile' component={ProfileComponent} />
</Switch>
And on /login route you will get the original url via this.props.location.state.from:
...
doLogin(username, password)
.then(() => {
window.location.href = `/${this.props.location.state.from}`;
});
Using react hooks useHistory() method history.goBack()
...
login(userData);
history.goBack();
I have a simple route.js file
const PrivateRoute = ({ component, ...rest }) => {
const isAuthed = localStorage.getItem('Authorization')
return (
<Route {...rest} exact
render = {(props) => (
isAuthed ? (
<div>
{React.createElement(component, props)}
</div>
) :
(
<Redirect
to={{
pathname: '/login',
state: { from: props.location }
}}
/>
)
)}
/>
)
}
class App extends Component {
componentWillMount() {
if (localStorage.getItem('Authorization')) {
history.push(`${history.location.pathname}`)
}
}
render() {
return (
<Router history={history}>
<div className="App-pageContainer">
<Route exact path="/" render={() => <Redirect to="/login" />} />
<Route path={'/login'} component={Login} />
<PrivateRoute path={'/dashboard'} component={Dashboard} />
</div>
</Router>
)
}
}
export default App
What I need is to put condition if the user has a key in localStorage(Authentication) then I want to redirect it to /dashboard if it doesn't contain Authentication in localStorage then I want to redirect it to /login.
I am totally stuck with this from past few days. Please help!!!
I think this kind of questions is too broad for an answer.
However, you could follow this amazing post to be able to implement that functionality.
Protected routes and authentication with React Router v4
This is what you got after finishing
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom'
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
const Public = () => <h3>Public</h3>
const Protected = () => <h3>Protected</h3>
class Login extends React.Component {
render() {
return (
<div>
Login
</div>
)
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/login' />
)} />
)
export default function AuthExample () {
return (
<Router>
<div>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path='/protected' component={Protected} />
</div>
</Router>
)
}
I cant seem to understand the proper way of dealing with guest and authorized routes. I checked out the given example https://reacttraining.com/react-router/web/example/auth-workflow but that dosnt block the authorized user to open /login route and it seems all of the given examples do the same.
I tried doing something like this:
export default function requireGuest(WrappedComponent) {
class Guest extends Component {
render() {
if (this.props.authenticated) {
return <Redirect to="/agenda"/>
}
return <WrappedComponent {...this.props}/>
}
}
function mapStateToProps(state) {
return {authenticated: isLoggedInSelector(state)}
}
const withConnect = connect(mapStateToProps, null, null, {pure: false});
return compose(
withConnect,
withRouter
)(Guest);
}
class Authorized extends Component {
render() {
if (!this.props.authenticated) {
return <Redirect to="/login"/>
}
return <WrappedComponent {...this.props}/>
}
}
function mapStateToProps(state) {
return {authenticated: isLoggedInSelector(state)}
}
const withConnect = connect(mapStateToProps, null, null, {pure: false});
return compose(
withConnect,
withRouter
)(Authorized);
}
And then in the main container do something like this:
<Route path='/login' component={RequireGuest(LoginPage)}/>
<Route path='/register' component={RequireGuest(RegisterPage)}/>
<Route path="/dashboard" component={RequireAuthorization(DashboardPage)}/>
But when i try to navigate anywhere else the path updates and the state updates too, but there is no rerenders and no redirects.
Then I tried something like this:
class GuestRoutes extends Component {
render() {
const {loggedIn} = this.props;
return (
<Route
render={props =>
loggedIn ? (
<Redirect
to={{
pathname: "/agenda",
state: {from: props.location}
}}
/>
) : (
<Switch>
<Route path='/login' component={LoginPage}/>
<Route path='/register' component={RegisterPage}/>
<Redirect to='/login'/>
</Switch>
)
}
/>
);
}
}
class AuthorizedRoutes extends Component {
render() {
const {loggedIn} = this.props;
return (
<Route
render={props =>
loggedIn ? (
<Switch>
<Route exact path="/dashboard" component={DashboardPage}/>
<Redirect to="/dashboard"/>
</Switch>
) :
(<Redirect
to={{
pathname: "/login",
state: {from: props.location}
}}
/>)
}
/>
);
}
}
And in the main container
<Switch>
<GuestRoutes/>
<AuthorizedRoutes/>
</Switch>
But then if the user is logged in, the guest routes keep getting rendered until it gets the depth error.
Im been trying to figure this out for quite a few hours so im getting desperate here. So any examples or anything that has routes protected based on the authorization state
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?