How to secure edit form route? - reactjs

I have this route in my React app:
<Route path="/solution/:id/edit" element={user ? <SolutionEditForm /> : <Navigate to="/" />}/>
I would like to know how to secure this route, right now if a user is logged in they can access this route, but they can also access someone's solution edit route as well if they are logged in. This route should be accessible only by those who submitted the solution.

You could use a Private Route
// Private Route file
import { Navigate, useParams } from "react-router-dom";
const PrivateRoute = ({ user, children }) => {
const { id } = useParams();
// check for user first
if (!user) {
return <Navigate to="/" />;
}
// logic to check if `solution` id matches with user id
// and if not then navigate to "/"
return children;
};
export default PrivateRoute;
Now wrap your route with the above Private Route
<Route path="/solution/:id/edit" element={<PrivateRoute user={user}><SolutionEditForm /></PrivateRoute>}/>

Related

My Private React Route (Router v6) Not Redirecting To My Login Page [duplicate]

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 9 months ago.
I am trying to make my private route redirect the user to the login page when he or she is not authenticated with tokens. When I try to go to home page by changing the url without logging in or registering it goes to a blank white screen instead of going to the login page. This is my code, I am using React Router v6. How can I fix my private route?
//My Private Route
const PrivateRoute = ({ children }) => {
const navigate = useNavigate();
const auth = JSON.parse(localStorage.getItem('token'));
return auth.user ? children : navigate("/login");
}
export default PrivateRoute;
//Render App
function App() {
return (
<>
<ToastContainer/>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login/>}
/>
<Route path="/" element={
<PrivateRoute>
<Home/>
</PrivateRoute>
}
probably best to return the Navigate component rather than the useNavigate hook:
import {
Navigate,
BrowserRouter,
Routes,
Route,
} from 'react-router-dom';
//My Private Route
const PrivateRoute = ({ children }) => {
const auth = JSON.parse(localStorage.getItem('token'));
return auth?.user ? children : <Navigate to="/login" />;
};

How can I prevent user from going back to Login route in ReactJs?

This is a private route for allowing only authenticated users to navigate to dashboard "/".
The user can still go back to "/login" after successfully authenticated, how can I prevent this using a private route?
import React from 'react'
import { Redirect, Route } from 'react-router'
import { useAuth } from '../contexts/AuthContext'
export default function PrivateRoute({component: Component, ...rest}) {
const{ currentUser } = useAuth()
return (
<Route
{...rest}
render={props =>{
return currentUser? <Component {...props} /> : <Redirect to ="/login" />
}}>
</Route>
)
}
You can save your login state in a global state, then you are going to put a condition for rendering the login route,
const globalLoginState = true // -> you are logged in
<BrowserRouter>
<Router>
{ globalLoginState ? (
<Route exact path"/" component={DummyPageComponent} />
) : <Route path="signIn" component={DummySignInComponent} />
}
</Router>
</BrowserRouter>
I think this is gonna work for you

Conditional Redirection to a URL in react

I am building a "Forgot password" page. Following is the flow of my code:
A page with url /auth/forgot-password opens on clicking on Forgot Password.
It takes input as email and sends OTP to the registered email(if it exists in DB).
After sending OTP, it redirects to a new URL /auth/new-password.
Here, the remaining details are entered(ex. OTP,new password etc.)
Due to this flow, the user can access the path /auth/new-password by searching for it. But I don't want that to happen. User should only reach this url via /auth/forgot-password. User should be redirected to /auth/forgot-password URL if user searches for the prior one.
Currently in my Routes page I am doing this:
<ContentRoute path="/auth/forgot-password" component={ForgotPassword}/>
<ContentRoute path="/auth/new-password" component={NewPassword} />
Due to some restrictions I can't change the existing flow of the code.
How can I change this to exhibit the behavior explained above?
The easiest way is to create a HOC (Higher Order Component).
When I want a user to authenticate before accessing a sites' page, I always create a HOC called AuthRoute like this.
AuthRoute.js
import { connect } from "react-redux";
import { Redirect, Route } from "react-router-dom";
const AuthRoute = props => {
const { authUser, location } = props;
if(!authUser) {
return <Redirect to={{
pathname: "/",
state: { prevLoc: location.pathname }
}} />
}
return <Route {...props} />;
};
function mapStateToProps({ authUser }) {
return {
authUser
}
}
export default connect(mapStateToProps)(AuthRoute);
Then include that to the App component like this.
App.js
import { Fragment } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import AuthRoute from './components/AuthRoute'; // AuthRoute Import
import Dashboard from './components/Dashboard';
const App = () => {
return (
<Router>
<Switch>
{/* Include the AuthRoute component for the relevant page */}
<AuthRoute path="/home" component={Dashboard} />
<Route path="/" component={Login} />
</Switch>
</Router>
)
}
This implementation will check whether the user entered their email address on /auth/forgot-password page.
Completed project with HOC implementation - https://github.com/yushanwebdev/reactnd-would-you-rather

React save old URL before route redirect

I am currently using the following code to check if the user is authenticated to allow them to a specific page and if they are not, to redirect them to the login page.
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
const AuthRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={
(props) => authenticated === "false" ? <Redirect to='/login' /> : <Component {...props} />
}
/>
)
export default AuthRoute
The implementation above works as it should. However, I was wondering if it is possible to preserve the old url, the one before the redirect to /login - lets say an user, that is not logged in, attempts to access /posts which is protected and gets redirected to the /login page.
What I want to do is to redirect the user to the page, prior to the /login, /posts, after successful authentication
Is such implementation possible?
Redirect component to prop can receive string or object so your auth route can look like this:
const AuthRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={
(props) => authenticated === "false" ? <Redirect to={{ pathname: '/login' state: { from: props.location }}} /> : <Component {...props} />
}
/>
)
And then in your login component you can access that state as:
const { from } = this.props.location.state;
Here is documentation for Redirect.

React router, how to save transition and redirect upon successful login

I want to redirect the user to the login page it they attempt to access an endpoint that requires authentication, but I want to save the page they attempted to access so that once they login, I can redirect them to that page. In the older versions of react router, I believe you were able to do this, using a wrapper (example taken from auth0):
export default (ComposedComponent) => {
return class AuthenticatedComponent extends React.Component {
static willTransitionTo(transition) {
// This method is called before transitioning to this component. If the user is not logged in, we’ll send him or her to the Login page.
if (!LoginStore.isLoggedIn()) {
transition.redirect('/login', {}, {'nextPath' : transition.path});
}
}
...
}
}
And then in the action that gets called upon successfully authenticating with the API:
loginUser: (jwt) => {
var savedJwt = localStorage.getItem('jwt');
AppDispatcher.dispatch({
actionType: LOGIN_USER,
jwt: jwt
});
if (savedJwt !== jwt) {
var nextPath = RouterContainer.get().getCurrentQuery().nextPath || '/';
RouterContainer.get().transitionTo(nextPath);
localStorage.setItem('jwt', jwt);
}
}
I know that in the new react router API, the first part can be done with
router.push({ pathname, query, state })
However, where is the place to access the state (in this case, nextPath)? I believe the getCurrentQuery function on the router has been deprecated
Use onEnter hook in the route definition (demo).
The Login page should include a reference to router in the props (wrapping it with react-router withRouter HoC). In addition, the location prop should include the needed data to redirect back to the original location:
const Login = withRouter(({ router, location }) => (
<div>
<button type="click" onClick={() => {
LoginStore.logIn();
router.replace(location.state); // on login redirect back to the original location, by taking that location's details from the router state
}}>Click to Login</button>
</div>
));
The login onEnter handler should redirect to the Login page, and pass the original page details (nextState) in the the location state:
const redirectToLogin = (nextState, replace) => {
if (!LoginStore.isLoggedIn()) {
const { pathname, search, query, state } = nextState.location;
replace({ pathname: '/login', state: { pathname, search, query, state } });
}
};
Add onEnter={ redirectToLogin } to routes that require login:
ReactDOM.render((
<Router>
<Route path="/" component={MainLayout}>
<IndexRoute component={Home} />
<Route path="login" component={Login} />
<Route path="page1" component={Page1} onEnter={ redirectToLogin } />
<Route path="page2" component={Page2} onEnter={ redirectToLogin } />
</Route>
</Router>
), document.getElementById('root'))

Resources