React Router and Authentication - javascript

I'm new to react and learning how to react-router to achieve routing. As a part of my sample app I was trying to put an authenticator on every path that's a part of the site. I'm following this example: https://github.com/reactjs/react-router/blob/master/examples/auth-flow/app.js
And here is my sample code:
render(
<Router history={browserHistory}>
<Route path="/" component={App} onEnter={requireAuth}>
<IndexRoute component={Home}/>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
<Route path="about" component={About}/>
<Route path="inbox" component={Inbox}/>
</Route>
</Router>,
document.getElementById("app"));
And here is what requireAuth does:
function requireAuth(nextState, replace) {
if(!auth.isUserLoggedIn()) {
replace({
path: "/login",
state: {nextPathname: nextState.location.pathname}
});
}
}
The difference between what I'm doing and what the example does is that the example puts the auth functionality only on one route (dashboard), where as I want to put it on every route. But the example stops working and starts throwing error
Maximum call stack size exceeded
and I think rightly so because /login is a subroute of / and because requireAuth is redirecting to the sub route, it just try to authenticate again and keeps trying for ever. I'm probably missing a simple fix here to get this to work, but am not able to figure what is the best way to do it.
What can I do to enable authentication at every level of the site?

Answer of #DemoUser is almost correct and #KumarM how you can login if you need to be authenticated to see login page ? here is what you can do
render(
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
<Route path="/dashboard" onEnter={requireAuth} >
<IndexRoute component={Home}/>
<Route path="about" component={About}/>
<Route path="inbox" component={Inbox} />
</Route>
</Route>
</Router>,
document.getElementById("app"));
whats happening here ?
login and logout page need not to be authenticated
all child components of Dashboard will automatically be authenticated

I think you must've figured it out by now but I stumbled across this today, so I thought of sharing this:
If you are adamant on securing every resource of your application, I'd suggest thinking twice. What #abhirathore2006 said is: users of your application shouldn't be authenticated to access the login component. Should they?
You are correct in assuming that the 'Maximum call stack size increased' is because of infinite redirects happening and you can try to avoid it by doing a null check or something.
The point is, you'd just need to place the onEnter hook on the /logout, /inbox & /about components and leave the /login as is. Otherwise you could try wrapping these components in a HOC or use the newer react-router4 approach to get what you need.
Hope this helps!
Cheers!

You could change your route so that, the pages that do not require login, can be rendered without need of authentication, as:
render(
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
<Route path="about" component={About}/>
<Route path="inbox" component={Inbox} onEnter={requireAuth}/>
</Route>
</Router>,
document.getElementById("app"));
If user is already loggedin requireAuth does not do anything, and your Inbox component is rendered.

Related

Re-render header after user logs in or logs out in react

so the question may seem a little confusing so let me explain,
this is my index file
<Router>
<Header />
<Switch>
<Route exact path="/" component={HomePage} >
</Route>
<Route exact path="/Login/:step?" component={Login}>
</Route>
</Switch>
</Router>
and here is basically how i handle getting to homepage after user logs in
this.props.history.push("/")
so the problem here is when i do that the header component doesn't re-render and cause of that i can't call my api inside componentDidMount() inside of header to update user information.
Put Header in HomePage
and if you need put also in Login page.. but I don't think the login need the same header like in Homepage.
The main router component should be clean.
<Router>
<Switch>
<Route exact path="/" component={HomePage} >
<Header />
</Route>
<Route exact path="/Login/:step?" component={Login}>
<Header />
</Route>
</Switch>
</Router>
If your app is getting big for - update user information - think of using state management like Redux

Pass URL params in route - ReactJS

I have a URL
https://website.com/enable/code=react/string=true
My React router looks like this
<Router history={history}>
<Route
path="/"
component={App}
>
<IndexRoute component={Page1} />
<Route path="/enable/language='dynamicVal'/string='dynamicVal'>{page2()}</Route>
</Route>
</Router>
Now the Route path i need to get the dynamic value for language and string
I have tried this.
/enable/language=(/:react)/string=(/:true)
How and where to define the dynamic values?
You can go with the path param approach and structure your Routes like
<Router history={history}>
<Route
path="/"
component={App}
>
<IndexRoute component={Page1} />
<Route path="/enable/:language/:codeString'>{page2()}</Route>
</Route>
</Router>
and in your component you get get the path params like
this.props.params.language and this.props.params.codeString if you are using react-router-v3 or below
and
this.props.match.params.language and this.props.match.params.codeString if you are using react-router-v4
Assuming react-router v4 (not tested on v3, which is what you seem to be using?), you can define a Route as such:
<Route
exact path="/enable/language=:language/string=:stringVal"
component={YourComponent} />`
In YourComponent, both params will be available under this.props.match.params.language and this.props.match.params.stringVal, respectively.
See this CodeSandbox for a working example.

React Router - How to IndexRedirect to dynamic route

I have a situation in my React app to look like something as follows using react-router. I want the index of the users/:userId endpoint to redirect to another endpoint that includes a :userId params. What is a good way to do this? Right now the user from currentUser will return null because the code is only executed once in the beginning when the App is loaded the user is not yet authenticated. I'm guessing I will need to force react-router to reload the history and certain variables, or change the order of when I do authentication?
routes.js
{ /* Routes requiring login */ }
<Route onEnter={requireLogin}>
<Route path="users" component={Users}>
<Route path=":userId" component={User}>
<IndexRedirect to={`/users/${currentUser().id}/profile`} />
<Route path="profile" component={UserProfile} />
<Route path="settings" component={UserSettings} />
<Route path="activity" component={UserActivity} />
</Route>
</Route>
</Route>
If I understand it correctly and you want to redirect from /users/1 to /users/1/profile and /users/2 to /users/2/profile, you can simply replace ${currentUser().id} with :userId.
So your code would looks like this:
<Route onEnter={requireLogin}>
<Route path="users" component={Users}>
<Route path=":userId" component={User}>
<IndexRedirect to="profile" />
<Route path="profile" component={UserProfile} />
<Route path="settings" component={UserSettings} />
<Route path="activity" component={UserActivity} />
</Route>
</Route>
</Route>
Because the redirect is in the context of the route :userId: you can simply define your target route relative.

React-router + Auth0 login callback not being recognized

I'm currently trying to integrate Auth0's custom login form as shown on their website here. On successful sign on I get a access token in my url yet the parseAuthHash method is never called.
localhost:8080/#access_token=xxx&id_token=xxx
My router looks like this.
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={Landing}/>
<Route path="/login" component={Login} auth={auth}/>
<Route path="/dashboard" component={Dashboard} onEnter={requireAuth} />
<Route path="access_token=:token" onEnter={parseAuthHash} />
</Router>
</Provider>,
app);
I found one way of solving the problem and that involved manually parsing the url on page load, which is something I don't want to have to do.

IndexRoute sub-routes

I have a web site with few ordinary pages and a page with Google Map. When map marker is clicked a panel with marker details is displayed next to the map. This detail has own URL so that users can link to it:
<Route path="/" component={App}>
<IndexRoute component={Welcome} />
<Route path="map" component={Map}>
{/* Detail is a child component of Map,
it only adds detail panel markup to Map. */}
<Route path="detail/:id" component={Detail} />
</Route>
<Route path="about" component={About} />
</Route>
This works fine. But let's get rid of Welcome page and display Map right on the web root so that:
/ renders App > Map components
/detail/:id renders App > Map > Detail components
/about renders App > About components
<Route path="/" component={App}>
{/* Map has to be IndexRoute so that it is displayed at root URL. */}
<IndexRoute component={Map}>
<Route path="detail/:id" component={Detail} />
</IndexRoute>
<Route path="about" component={About} />
</Route>
But this doesn't work because IndexRoute can't have subroutes.
This is the best solution I have found:
<Route path="/" component={App}>
<Route component={Map}>
<IndexRoute component={EmptyComponent} />
<Route path="detail/:id" compoent={Detail} />
</Route>
<Route path="about" component={About} />
</Route>
But I don't like the empty component.
Am I missing something? Am I doing something unusual? Why it is not possible to do it the first more intuitive way?
Move the / path
<Route component={App}>
<Route path="/" component={Map}>
<Route path="detail/:id" component={Detail}/>
</Route>
<Route path="/about"/>
</Route>
Your solution looks largely fine to me – the only caveat is that you don't need to specify the component in that case; just do <IndexRoute />.
By design, index routes terminate matching, but it's easy to insert trivial routes.
Maybe I am wrong but it seems as if your tried to set another route in:
<IndexRoute component={Map}>
<Route path="detail/:id" component={Detail} />
</IndexRoute>
So your basic structure is something like:
<IndexRoute>
<Route> </Route>
</IndexRoute>
According to your error it is not allowed that there is a <Route> inside of your <IndexRoute>... At the beginning you did not do that mistake because you closed the <IndexRoute> before you opened the next <Route>-Tag.
So if you want your code to work again you should not open another <Route> inside of your <IndexRoute>. You managed to fix this by adding an Dummy-IndexRoute. So if you want to set your Map component as IndexRoute you will have to change your HTML structure so that there is no Detail component inside of your map component because then you will have the same problem again that you got a <Route> inside your <IndexRoute>

Categories