React Router 4 authentification and redirection - javascript

I'm trying to implement an authentification system to my app with React Router.
But i'm having trouble to find a correct way to do this.
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
logged : false
}
}
isUserConnected(){
// Contact server to know if user is connected or not
}
render() {
return (
<Router>
<Switch>
<Route exact path="/" component={Homepage} />
<Route path="/privatePart" component={Application} />
<Route path="/login" component={LoginRedirection} />
<Route component={NotFound} />
</Switch>
</Router>
)
}
}
In the LoginRedirection component, there is a login form that make a POST to connect the user. But how this component can set logged to true in the App component ?
Maybe I should learn redux ? It may be simplier to implement authentification with it ?

Redux could be a solution, but you can do this with React and react-router.
Using react-router, you can use the property onEnter. For example, you can make the privatePart URL accessible only to logged users with something like:
<Route path="/privatePart" component={Application} onEnter={requireAuth}/>
requireAuth should be a function that checks if the user has logged in or not. I think that using App's state isn't the best way to do this, since that information will be lost if the web is reloaded. You could store the login token or just a flag in the localStorage when the POST returns OK in the Login component. Then, redirect the user to privatePart and he will only be able to access it if requireAuth returns true (checking the localStorage). It could be something like:
if (localStorage.getItem("logged") === null) {
return false;
} else {
return true;
}
where username is the key you would add to the localStorage when the login is successful.

Related

AuthStatus is not changing from configuring state using amplify ui react

I'm using AWS amplify UI react to create the authentication flow in the react application. I followed the document and made the navigation flow using the doc below.
https://ui.docs.amplify.aws/react/guides/auth-protected.
However, after logging in, I was able to see the login page flickering each time when I access any other route. To solve this, I followed one of the answers from the below question.
Flicker of login page on Authentication - useAuthenticator - React - Amplify Auth.
Unfortunately now, the page is always stuck in the "configuring" state and the authStatus never getting changed at all. How do I handle this scenario to automatically redirect to the login page if not authenticated and not show the login page each time user refreshes the page?
NOTE: This question is related to amplify-ui react with Authenticator.provider component.
RequireAuth.tsx - all routes are wrapped inside this
import { useLocation, Navigate } from "react-router-dom";
import { useAuthenticator } from "#aws-amplify/ui-react";
import PageLoader from "../../components/loader/page-loader";
export function RequireAuth({ children }: any) {
const location = useLocation();
const { authStatus, user } = useAuthenticator((context) => [
context.authStatus,
]);
console.log("authStatus:::", authStatus);
console.log("user:::", user);
if (authStatus === "authenticated") {
return <>{children}</>;
} else if (authStatus === "unauthenticated") {
return <Navigate to="/login" state={{ from: location }} replace />;
} else if (authStatus === "configuring") {
return <PageLoader />;
} else {
return <Navigate to="/login" state={{ from: location }} replace />;
}
}
And few routes in appRoutes.
<Route
path="/"
element={
<RequireAuth>
<AppLayout />
</RequireAuth>
}>
<Route
index
element={
<RequireAuth>
<Home />
</RequireAuth>
}
/>
Unfortunately, this appears to be a known bug with <Authenticator.Provider> and <Authenticator/>.
Until the bug is fixed, there is a known workaround that involves always including the <Authenticator/> component within the active dom structure and then hiding it using CSS. It's pretty terrible, but, worked for me:
[data-amplify-authenticator] {
display:none;
}
In the future, I am planning to write a custom UI and handle the authentication in my backend before dropping these components altogether.

Unable to redirect to reset password link without being logged

I'm new to react router and implementing a log in system, however, since i didn't build the whole app i'm not quite sure why all the Routes redirect to /#/ if the user is not logged in..
I'm using React Router and I want someone to be able to click on a link on an email that takes them to
http://localhost:3001/auth/forgot8b8dc686ee38320f7f5dd7b858e62a402cd2f0bb#/
This is how i'm referencing the link in react router
` {
path: "/forgot",
component: loadable(() => import("./layout/forgot/forgot")),
pure: false,
exact: true
},`
`<ConnectedRouter history={history}>
<IndexLayout>
<Switch>
<Route exact path='/forgot' component={Forgot} />
<Route exact path="/" render={() => <Redirect to="/foo/all" />} />
{routes.map(route => (
<Route
path={route.path}
component={route.component}
key={route.path}
exact={route.exact}
/>
))}
<Route component={NotFound404} />
</Switch>
<ToastContainer autoClose={8000} hideProgressBar={true} />
</IndexLayout>
</ConnectedRouter>`
If i'm not logged in the above just redirects me to my home page and I cannot even see that URL being called in the console, however if i am logged in, I can see that component being rendered on my dashboard (i don't want it to be rendered inside my dashboard I want it to be a page non logged in users can use to reset their password similar to my home page)
Essentially I want a route non logged in users can go to without it being inside my dashboard.
My forgot page with the field where users can input their new password is located in the .layout folder that is also the location of my IndexLayout
`import IndexLayout from "./layout";`
My dashboard is only supposed to get render if the user is logged in
`return this.props.user.authenticated && this.props.user ? (
<Dashboard children={children} user={user} />
) `
However I don't see why this would affect what i can and can't see.
I normally use standard express and pug to build my apps and i would just do a app.get('forgot:token'(req,res,next)=>{etc}) etc..
Given that your URL is http://localhost:3001/auth/forgot8b8dc686ee38320f7f5dd7b858e62a402cd2f0bb#/
The path pattern /forgot doesn't match this. Which is why you're not landing on the Forgot component.
You could change your URL structure to something like:
.../forgot/8b8dc686ee38320f7f5dd7b858e62a402cd2f0bb
Then you can retrieve the token using React Router URL Params like this:
<Route exact path='/forgot/:token' component={Forgot} />

How to make component not re-render when navigating pages?

In my application there is a main home page that gets loaded and there is another page (Page2.js) I can navigate to via the navbar I have implemented. The problem is that when I go to Page2.js and then go back to the main page, it will re-render all of the components on the main page. I am wondering if there is a way to check the last pathname and say if it is equal to a certain value then all of the components should stay the same?
I have tried using the shouldComponentUpdate method in the navbar (since I do not want it to change) and it is pretty faulty in that it would not read prevProps.location as a value but undefined:
shouldComponentUpdate = (prevProps) => {
if (this.props.location !== prevProps.location) {
return false;
}
}
App.js holds the code for my routing in the application:
// imports here
class App extends Component {
render() {
const {location} = this.props;
return (
<Switch >
<DefaultLayoutRoute exact path="/">
<Redirect to={HomePage}/>
</DefaultLayoutRoute>
<Route exact path={SIGNOUT} render={() => {
auth0Client.signOut();
window.location.href = homeRedirect;
return null
}}>
</Route>
<Route exact path={LOGIN_SUCCESS} component={Callback}/>
<DefaultLayoutRoute exact path={HomePage} location={location.pathname} component={HomePage}
pageName="Office Production"/>
<DefaultLayoutRoute
exact
path={AGENTRANKING}
component={AgentRankings}
pageName="Agents"
/>
<DefaultLayoutRoute exact path={SETTINGS} component={Settings}/>
</Switch>
);
}
}
export default withRouter(App);
Expected Result: To be able to navigate to Page2.js and then back to the main page with all of the components rendered the way it was unless I manually refresh
Actual Results: I go back to the main page and all the components start to re-render.

React Router re-render route instead of re-render component

I'm working on an app that is using React Router and I noticed that when my Redux store changes state that the router is re-rendering the component the current route refers to rather than re-rendering the route itself.
To illustrate the problem; I have implemented a PrivateRoute that checks if a user is currently logged in. In it's most basic form it looks something like this:
const PrivateRoute = ({component: Component, ...rest}) => {
return <Route {...rest} render={(props) => {
const state = store.getState()
if (state.isAuthenticated) {
return <Component {...props}/>
}
else {
return <Redirect to={{pathname: '/login'}}/
}
}}/>
})
This works great since I can now say something like this:
<PrivateRoute path="/" component={HomePage}/>
However, I noticed that when the state of isAuthenticated changes that React Router is calling the render method on the HomePage component rather than that it re-renders the route. This means that the application will only do the authentication check when a user goes from some page to the home page but once on the home page, the check is no longer performed.
The only work around I have at the moment is to move the authentication check into the render function of the component (which is obviously not where it belongs).
How can I make React Router re-render the route rather than re-render the component the route refers to when the state changes?
I managed to solve the problem by using a Higher Order Component rather than implementing the authentication check in the route.
function withEnsureAuthentication(WrappedComponent) {
return class extends React.Component {
render() {
if (this.props.store.isAuthenticated === false) {
return <Redirect to={{pathname: '/login'}}/>
}
return <WrappedComponent {...this.props}/>
}
}
}
You can now use a normal Route but apply the withEnsureAuthentication to the component:
const HomePage = withEnsureAuthentication(Home)
<Route path="/home" component={HomePage}/>

React Router V4 protected private route with Redux-persist and React-snapshot

I'm implementing private route like so using React Router Route Component:
function PrivateRoute({component: Component, authed, emailVerified, ...rest}) {
return (
<Route
{...rest}
render={props =>
authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}/>
)
}
Expected Behavior:
authed is persisted through page refresh through using redux-persist
So on page refresh or reload, if authed prop is true then router should render <Component /> without ever going to path "/"
Actual Behavior which is the Problem:
With authed === true (persisted)
reloading the page or refreshing it leads to the following actions taking place(checked redux devtools)
the action:
"##router/LOCATION_CHANGE" runs and takes it to the correct secure route but then "##router/LOCATION_CHANGE" runs again and it redirects to "/" for a moment and finally
"##router/LOCATION_CHANGE" runs again and directs route back to the secure path, even though authed === true through all this in the redux devtools
Then: My guess was that this error has something to with my main App Component rendering before redux-persist has time to re-hydrate the Redux store.
So I tried doing the following:
I tried delaying my main App component render until my store is re-hydrated using redux-persist like so:
render() {
const {authed, authedId, location, rehydrationComplete} = this.props
return (
<div>
{ rehydrationComplete
? <MainContainer>
<Switch key={location.key} location={location}>
<Route exact={true} path='/' component={HomeContainer} />
<Route render={() => <h2> Oops. Page not found. </h2>} />
</Switch>
</MainContainer>
: <div>...Loading </div> }
</div>
)
}
This effectively fixes the issue above of the path changing when "##router/LOCATION_CHANGE" action runs(only Changes the path keys), However this leads to another Issue with React-snapshot Now: all the static generated html files from react-snapshot Now contain only ...Loading. I tried to set snapshotDelay of 8200 in the react-snapshot options but that didnt solve the issue.
Then:
I tried the following to delay React-snapshot call so that it renders html after the store has been rehydrated:
import {render as snapshotRender} from 'react-snapshot'
import {ConnectedRouter} from 'react-router-redux'
async function init() {
const store = await configureStore()
snapshotRender(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
}
init()
But now i get the error: that 'render' from react-snapshot was never called. Did you replace the call to ReactDOM.render()?
I know this is a loaded question, but I want to effectively use these 3 libs(React-Router V4, Redux-persist, React-snapshot) together to serve protected routes without the mentioned errors.
I have something similar to you. Here I use React-Router V4 and a persist-like library.
Your router/routes doesn't need to be aware of the persist. They should rely on your redux's store. The persist should rehydrate your store with all the data.
I didn't see where you are using the PrivateRoute component in your example. Where is it?

Categories

Resources