React Router Authentication Redirection - reactjs

I am currently implementing an authentication/login flow with React and React Router V4. But I am struggling to find a working redirect "schema" to implement my idea.
The situation is as follows:
If a user is not logged in to my system he/she should get redirect
to "/login", and a specific login page component is rendered. This should be a requirement of the system, so that I can share register and login links for my application.
If the user is logged in he/should should be redirected to the route the user originally wants to visit.
My current implementation:
Entry Component
export default class Entry extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Router>
<Routes />
</Router>
);
}
}
Routes Component (authentication checking takes place here)
class Routes extends PureComponent {
componentDidMount() {
this.props.loadUser(); // async method (redux) loads the actual logged in user
}
render() {
return (
<Switch>
<Route path="/login" render={() => (!this.props.user.username ? <LoginPage {...this.props}/> : <Redirect to="/app" />)} />
<Route path="/app" render={() => (this.props.user.username ? <AppPage {...this.props} /> : <Redirect to="/login" />)} />
<Route exact path="/" render={props => <Redirect to="/app" />} />
</Switch>
);
}
}
export default Routes;
App component (nested routing here)
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const { match, user, logout } = this.props;
return (
<Switch>
<Route path={`${match.path}/about`} component={About} />
<Route path={`${match.path}`} component={Home} />
</Switch>
);
}
}
The Problem now occurs when the following is happening:
The user is logged in but has closed the tab of my application
The user now wants to visit /app/about
The routes component is loading but this.props.user.username is null
User gets redirected to /login
Now the async method this.props.loadUser() has updated the redux store and this.props.user.username is not null anymore and then the user gets redirected to /app but he originally wanted to visit /app/about.
So the line which makes me headaches is
<Route path="/login" render={() => (!this.props.user.username ? <LoginPage {...this.props}/> : <Redirect to="/app" />)} />
How should I handle this specific approach so that the user gets redirected to the URL he/she originally wanted to visit ?
Maybe my overall approach is a little bit weird.
Thanks in advance, I appreciate every help :)

Since you're using react-router v4 I recommend going to their great docs about this topic. Here
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)}/>
)
const AuthExample = () => (
<Router>
<div>
<AuthButton/>
<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>
);

Related

React Router Redirect not working in Private Route

I have this private route component that is used to render a component only is the user is logged in, else it should redirect to the login page.
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
authToken()
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
export default withRouter(PrivateRoute);
and this is my main app:
<BrowserRouter>
<div className="wrapper">
<Switch>
<Route path="/login" component={LoginPage} />
<>
<div className="dashboard">
<SideBar />
{permittedEvents &&
<div className="content-area">
<PrivateRoute exact path="/" component={Dashboard} />
<PrivateRoute exact path="/calendar" component={Calendar} />
</div>
}
</div>
</>
</Switch>
</div>
</BrowserRouter>
for some reason the redirect is being ignored completely and when the user is not logged in, the Sidebar gets rendered but nor the content or the login page get rendered.
I have tried returning only the redirect in te Private route to force te redirect and check whether it was something wit my authentication. But the redirect doesn't seem to be working no matter where its included.
You don't need Route
class PrivateRoute extends React.Component {
constructor(props) {
super(props);
}
render() {
const { component: Component, ...rest } = this.props;
const redirectPath = (<Redirect to={{
pathname: "/login",
state: {
from: this.props.location.pathname
}
}}/>);
if (!ok) {
return redirectPath;
}
return <Component {...this.props} />;
}
};
export default withRouter(PrivateRoute);

How to show 404 Page when user is typing any URL on login page if user is not authenticated?

I want user to be blocked by accessing invalid URL from login screen if user is not authenticated, for instance, consider user is on login screen and if user tries to access any random url localhost:3000/kanskd, he/she should be redirected to login screen. I am able to achieve what i need by placing NoMatch route component, however, it matches the route inside my application as well and it renders No match for those routes as well[Routes that i am mapping after NoMatch route does not work].
index.js
import Routes from './routes'
<Switch>
<Route exact path="/" render={() => {
if(!store.getState().login.isAvailable) {
return <Redirect to="/login"/>
} else {
return <Redirect to="/dashboard" />
}
}}
/>
<Route exact path="/login" component={Login} />
<Route component={NoMatch} />
{Routes.map((prop, key) => {
return <Route path={prop.path} key={key} component={prop.component}
/>;
})}
</Switch>
NoMatch.jsx
import React from 'react'
import { withRouter } from 'react-router-dom';
const NoMatch = ({ location }) => (
<div>
<h3>No match for <code>{location.pathname}</code></h3>
</div>
)
export default withRouter(NoMatch);
EDIT:
routes/index.js
import Dashboard from "Dashboard/Dashboard.jsx";
var Routes = [{ path: "/", name: "Dashboard", component: Dashboard }];
export default Routes;
Once the user logs in, it routes him to Dashboard and in Dashboard, there are other multiple routes.
So you have to solve 2 things here: show the NoMatch component when there is no match for a url and protect some routes from not logged users.
For the first one you should put your <Route component={NoMatch} /> just before the <Switch>closing tag, think of this like a switch in plain javascript, the last case is always the default case, if there is no other match the default will be executed, same as here.
The second problem requires a bit of extra code, you have to create a component that redirects the user if is not logged in, something like this (this is from the documentation react-router docs):
function PrivateRoute({ component: Component, isLoggedIn,...rest }) {
return (
<Route
{...rest}
render={props =>
isLoggedIn ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
}
Then use this component for protected routes:
<Switch>
<Route exact path="/" render={() => {
if(!store.getState().login.isAvailable) {
return <Redirect to="/login"/>
} else {
return <Redirect to="/dashboard" />
}
}}
/>
<Route exact path="/login" component={Login} />
{Routes.map((prop, key) => {
return <PrivateRoute path={prop.path} key={key} component={prop.component} isLoggedIn={isUserLoggedIn}
/>;
})}
<Route component={NoMatch} />
</Switch>
isUserLoggedIn is a made up variable, you should replace it for you logged in checks methods
Edit:
The path should be /dashboard:
import Dashboard from "Dashboard/Dashboard.jsx";
var Routes = [{ path: "/dashboard", name: "Dashboard", component: Dashboard }];
export default Routes;
if you want to maintain / as your path you should return the dashboard component inside your Route component instead of redirecting:
<Route exact path="/" render={() => {
if(!store.getState().login.isAvailable) {
return <Redirect to="/login"/>
} else {
return <Dashboard/>
}
}}
/>

React Router: Excluding a route from root path [duplicate]

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 5 months ago.
I'm currently setting up basic authentication for my React app. I have an AppWithAuth class which wraps the normal App, and a Login page. My goal is to redirect to /login when a user is not authenticated, but allow them to visit any other route if they are, such as / (dashboard path), /users, etc.
The problem I have is that the app wishes to render the root directory, but will redirect to the Login if not Authenticated. But since the login route is included in the root directory render, the app endlessly redirects. Any way to achieve the desired effect? Here's the gist of my code:
App:
class App extends React.Component {
render() {
return (
<Router> {/* The main routes of the app, e.g. /users... */}
...
</Router>
)
}
}
AppWithAuth:
class AppWithAuth extends React.Component {
isAuthenticated = () => {
// suppose this returns true if user is authenticated, false otherwise
}
render() {
return (
<Router>
<Route path='login' component={Login} />
<Route path='/' render={props => {
return this.isAuthenticated ? (
<App />
) : (
<Redirect to='/login' />
)
}} />
</Router>
)
}
}
If you use Switch then you can match paths exclusively. In that case order matters - the first path to match will break the switch, just like the switch statement. You can also leverage exact attribute to match a path exactly if you need to. For example:
<Router>
<Switch>
<Route exact path="/login" component={Login} />
<Route
path="/"
render={props => {
return this.isAuthenticated ? (
<App />
) : (
<Redirect to="/login" />
);
}}
/>
<Route path="/home" component={App} />
</Switch>
</Router>
what about try to this?
make ProtectedRoute for authenticated users
export default ({ isAuthenticated, component: C, ...rest }) => {
if (isAuthenticated) {
<Route {...rest} render={props => <C {...props} />} />
} else {
return <Redirect to="/login" />
}
}
use Router, Route in App.js
import ProtectedRoute from './ProtectedRoute'
class App extends React.Component {
isAuthenticated = () => {
// suppose this returns true if user is authenticated, false otherwise
}
render() {
return (
<Router>
<Route path='login' component={Login} />
<ProtectedRoute path='/' component={Main} isAuthenticated={this.isAuthenticated()} />
</Router>
)
}
}
The ProtectedRoute is only accept for authenticated users any others redirect to /login

How to properly set property(if user is logged) from cookie after page opens or F5 in React with router and redux

I would like to know what is the best practise in my case.
I am checking cookies in App constructor if there is stored a token. If it is there I am dispatching action which will set property isAuth to true.
My problem is that I will get redirected to login because isAuth is false from the beginning.
I came up with solution that I will set isAuth after checking session and I will not redner anything until then. Is it OK? And even if is, isn't there a better solution for handling this?
return (
<div>
<Router>
{isAuth ? (
<Switch>
auth routes...
</Switch>
)
: (
<Switch>
login and redirect to login
</Switch>
)}
</Router>
</div>
);
I usually create a PrivateRoute component that renders the Route or Redirect components after checking the login status, something like:
export const PrivateRoute = ({ component: Component, ...otherProps }) => (
<Route
{ ...otherProps }
render={ props => (
isLoggedIn()
? <Component {...props} />
: <Redirect to={ Paths.LOGIN } />
)}
/>
)
You should replace my isLoggedIn method and my Paths.LOGIN constants with your case.
Then you just use them as:
<PrivateRoute path="/admin/something" component={ MyAdminPage } />
<Route path="/non/logged/in/route" component={ MyNonLoggedInPage } />
<Route path="/login" component={ LogInPage } />

Layout routes with react router

I'm trying to do layouts with react-router.
When my user hits / I want to render some layout. When my user hits /login, or /sign_up I want the layout to render, with the relevant component for /login or /sign_up rendered.
Currently, my App.js looks like this
return (
<div className={className}>
<Route path="/" component={Auth} />
<ModalContainer />
</div>
);
My Auth.js looks like this
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
</AuthFrame>
);
So AuthFrame will get rendered when I hit /, and then react router looks for login or sign_up to render the other containers.
However, when I hit /, only the AuthFrame will render.
I would like for / to be treated as /login.
How do I achieve this?
The Switch component is useful in these cases:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Switch>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
{/* Default route in case none within `Switch` were matched so far */}
<Route component={LoginContainer} />
</Switch>
</AuthFrame>
);
see: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md
I think you're forced to introduce a prop/state which indicates the status of your viewer. This means is he signed in or just a guest of your website.
Your router can't obviously render /login if you you hit / but the router allows you to redirect to another page:
class AuthContainer extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
return <div>
<Route path="/login" component={LoginContainer}/>
<Route path="/sign_up" component={SignUpContainer}/>
</div>
}
}
class PublicHomePage extends React.Component {
render() {
return <div>
<Route path="/settings" component={SettingsComponent}/>
<Route path="/profile" component={ProfileComponent}/>
<Route path="/and_so_on" component={AndSoOnComponent}/>
</div>
}
}
class App
extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
const {loggedIn} = this.props;
if (loggedIn) {
return <PublicHomePage/>
}
return <Route exact path="/" render={() => (
<Redirect to="/login"/>
)}/>
}
}
I hope this code works for you. It isn't quite perfect but it should give you an idea how you could solve your problem.
In your case I would probably manipulate a bit with Routes in react-router. This code in AuthFrame should do the trick:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
{["/", "/login"].map((path, ind) =>
<Route exact key={ind} path={path} component={LoginContainer} />
)}
<Route exact path="/sign_up" component={SignUpContainer} />
</AuthFrame>);
Note the usage of exact on the routes, this is to prevent matching login component on /sign_up since it will also match / and prevent rendering both login and signup when accessing the root path (/).

Resources