ReactJS protected route with API call - javascript

I'm trying to protect my routes in ReactJS.
On each protected routes I want to check if the user saved in localStorage is good.
Below you can see my routes file (app.js) :
class App extends Component {
render() {
return (
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/signup" component={SignUp} />
<Route path="/contact" component={Contact} />
<ProtectedRoute exac path="/user" component={Profile} />
<ProtectedRoute path="/user/person" component={SignUpPerson} />
<Route component={NotFound} />
</Switch>
<Footer />
</div>
);
}
}
My protectedRoute file :
const ProtectedRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
AuthService.isRightUser() ? (
<Component {...props} />
) : (
<Redirect to={{
pathname: '/login',
state: { from: props.location }
}}/>
)
)} />
);
export default ProtectedRoute;
And my function isRightUser. This function send a status(401) when the data aren't valid for the user logged :
async isRightUser() {
var result = true;
//get token user saved in localStorage
const userAuth = this.get();
if (userAuth) {
await axios.get('/api/users/user', {
headers: { Authorization: userAuth }
}).catch(err => {
if (!err.response.data.auth) {
//Clear localStorage
//this.clear();
}
result = false;
});
}
return result;
}
This code is not working and I don't know really why.
Maybe I need to call my function AuthService.isRightUser() with a await before the call and put my function async ?
How can I update my code to check my user before accessing a protected page ?

I had the same issue and resolved it by making my protected route a stateful class.
Inside switch I used
<PrivateRoute
path="/path"
component={Discover}
exact={true}
/>
And my PrivateRoute class is as following
class PrivateRoute extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
isLoading: true,
isLoggedIn: false
};
// Your axios call here
// For success, update state like
this.setState(() => ({ isLoading: false, isLoggedIn: true }));
// For fail, update state like
this.setState(() => ({ isLoading: false, isLoggedIn: false }));
}
render() {
return this.state.isLoading ? null :
this.state.isLoggedIn ?
<Route path={this.props.path} component={this.props.component} exact={this.props.exact}/> :
<Redirect to={{ pathname: '/login', state: { from: this.props.location } }} />
}
}
export default PrivateRoute;

When you annotate a function with async like you did in AuthService.isRightUser() it returns a Promise and you are not treating the response of the method accordingly.
As you suggested, you could call the method AuthService.isRightUser() with await and annotate the function you are passing to the render property with async.
Or you could treat the response of AuthService.isRightUser() with a .then() and .catch() instead of the ternary operator

Related

React Router doesn't access Protected Route

In my typescript react project i have created a protected route to check for authenticationbefore rendering a component.
export const ProtectedRoute: React.FC<ProtectedRouteProps> = props =>{
const currentLocation = useLocation();
let redirectpath = props.redirectPathOnAuthentication;
console.log(redirectpath)
console.log('in protected route')
if(!props.isAuthenticated){
props.setRedirectPathOnAuthentication(currentLocation.pathname);
redirectpath = props.authenticationPath;
}
if(redirectpath !== currentLocation.pathname){
const renderComponent = () => <Redirect to={{pathname: redirectpath}} />;
return <Route {...props} component={renderComponent} render={undefined} />
}else{
return <Route {...props} />
}
}
I pass in the props to make conditional rendering based, on rather the user is authenticated or not.
I have the tries to access a ProtectedRoute, the user will be redirected to the route where login is possible ( named as /home route), and then redirected back to the original route.
export const RoutingHandler: React.FC = (props: Props) => {
const location = useLocation()
const [sessionContext, updateSessionContext] = useSessionContext()
console.log(location)
const setRedirectPathOnAuthentication = (path: string) =>{
updateSessionContext({...sessionContext, redirectPathOnAuthentication: path})
}
const defaultProtectedRouteProps: ProtectedRouteProps = {
isAuthenticated: !!sessionContext.isAuthenticated,
authenticationPath: '/home',
redirectPathOnAuthentication: sessionContext.redirectPathOnAuthentication || '',
setRedirectPathOnAuthentication
}
console.log(defaultProtectedRouteProps)
return (
<div>
<Navbar />
<Switch>
<ProtectedRoute {...defaultProtectedRouteProps} path="/dashboard" component={Dashboard} />
<Route exact path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/help" component={Help} />
<Redirect from="/" to="/home" />
</Switch>
</div>
)
}
Whenever I do `history.push('/dashboard');` the `Dashboard` component is never rendered.
For desired implementation, you can try this approach, that works fine:
export const PrivateRoute = ({component: Component, isAuthenticated, ...rest}) => (
<Route {...rest}
render={
props => (
isAuthenticated ? (
<Component {...props} />
):
(
<Redirect to={{ pathname: '/login', state: { from: props.location }}}/>
)
)
}
/>
)
PrivateRoute.propTypes = {
isAuthenticated: PropTypes.bool.isRequired
}
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps)(PrivateRoute)
so, in DefaultsRoutes:
<Switch>
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<PrivateRoute component={Home} />
<Route path="*" component={NotFound404} />
</Switch>
in App:
let AppRedirect = ({ loadingApp }) => {
return loadingApp ? <Loading /> : <DefaultRoutes />;
};
const mapState = (state) => ({
loadingApp: state.app.loadingApp,
});
AppRedirect = connect(mapState)(AppRedirect);
export class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppRedirect />
</Provider>
);
}
}
export default App;
When user successfully logged in dispatch an action to change state: loadingApp: true -> loadingApp: false

Children Component undefined

See the below function i am creating the Auth routes and getting the children undefined and shows blank page. In App.js i am using the private route as you can see below and when i use simple Route instead of PrivateRoute its shows the Login component
<PrivateRoute exact path="/" name="Login" render={props => <Login {...props}/>} />
Here is is My PrivateRoute.js. When i console the children its shows undefined
function PrivateRoute({ children, ...rest }) {
const token = cookie.get('token');
return (
<Route
{...rest}
render={({ location }) =>
!token ? (
children
) : (
<Redirect
to={{
pathname: "/dashboard",
state: { from: location }
}}
/>
)
}
/>
);
}
export default Private Route;
I usually use something like it. Let me know if it works for you.
Here is my privateRoute.js component:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthenticated } from 'auth';
export default function PrivateRoute({ component: Component, ...rest }) {
return (
<Route {...rest} render={
props => (
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
)} />
);
}
Here I have another file in the root auth.js:
export const isAuthenticated = () => {
return Boolean(localStorage.getItem('jwttoken'));
}
export const login = ({email, password}) => {
// Logic
localStorage.setItem('jwttoken', 'jkdsalkfj');
return true;
}
export const logout = () => {
localStorage.removeItem('jwttoken');
}
You can call it like:
<PrivateRoute
component={ProjectPage}
path="/project/:id"
/>
Children refer to what's inside the tag
<PrivateRoute>
<PrivateComponent/>
</PrivateRouter>
Here your children would be <PrivateComponent/>
You're not passing anything inside your PrivateRoute in your example. So you'll have undefined

Proper way to pass down state to another component

I'm adding some authorization checks to different pages in an app, but running into some errors passing down a piece of state.
I have
const RequireAuth = Component => {
return class App extends React.Component {
state = {
isAuthenticated: false,
isLoading: true
};
_updateLogin = () => {
this.setState({ isAuthenticated: true, isLoading: false });
};
render() {
const { isAuthenticated, isLoading } = this.state;
if (isLoading) {
return <div>Placeholder for animation</div>;
}
if (!isAuthenticated) {
return <Redirect to="/auth/login-page" />;
}
return <Component {...this.props} />;
}
};
};
And I am trying to pass down the _updateLogin function to the AdminLayout component, where I have a login page.
ReactDOM.render(
<Router history={hist}>
<Switch>
<Route
path="/admin"
component={RequireAuth(AdminLayout)}
login={() => {this._updateLogin}}
/>
<Route path="/auth" component={AuthLayout} />
<Redirect from="/" to="/admin/dashboard" />
</Switch>
</Router>,
document.getElementById("root")
);
If I run the above code, I get
Line 43:23: Expected an assignment or function call and instead saw an expression no-unused-expressions
If I change the login to
login={this._updateLogin}
it returns
"Cannot read property '_updateLogin' of undefined.
Any tips?

Unable to implement auth routes

I'm trying to learn react, and am setting up routes in my application which require you to log in. I'm trying to adapt the example given here
The code I've written should either redirect the user or display the protected route. But when I log in I'm still being redirected.
I believe the issue is in my PrivateRoute class below. I pass it a authenticated property which is set in the parent class, but it doesn't appear to update.
In app.js we declare the authenticator, where we perform async login with our backend.
I pass the checkLoggedIn function to the login component, where we set the parent's authenticated state property to true. I'm console.log()ing the state just to check it's occurring, which it is.
When I then click the Link to /protected route I'm still being redirected.
app.js
// imports ...
let authenticator = new Authenticator();
class ProtectedComponent extends Component {
render() {
return (
<h1>Protected!</h1>
);
}
}
class App extends Component {
constructor(props){
super(props);
this.state = {
authenticator: authenticator,
authenticated: authenticator.isLoggedIn(),
}
}
checkLoggedIn() {
this.setState({authenticated: true});
console.log(this.state);
}
render() {
let routes, links = null;
links = <div className="links">
<Link to="/login">Login</Link>
<Link to="/protected">Protected</Link>
</div>;
routes = <div className="routes">
<Route
path="/login"
render={() =>
<Login
authenticator={this.state.authenticator}
loginCallback={this.checkLoggedIn} />
}
/>
<PrivateRoute
path="/protected"
component={ProtectedComponent}
authenticated={this.state.authenticated}
/>
</div>;
return (
<Router className="App">
{links}
{routes}
</Router>
);
}
}
export default App;
PrivateRoute.js
// imports ....
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route {...rest} render={props =>
authenticated === true
? (<Component {...props} />)
: (<Redirect to={{
pathname: "/login",
state: { from: props.location }
}} />
)
}/>
);
export default PrivateRoute;
Login.js
// imports ...
class Login extends Component {
constructor(props) {
super(props);
this.authenticator = props.authenticator;
this.loginCallback = props.loginCallback;
this.state = {
identifier: "",
password: "",
}
}
updateState = (e, keyName = null) => {
this.setState({[keyName]: e.target.value})
}
attemptLogin = (e) => {
this.authenticator.loginPromise(this.state.identifier, this.state.password)
.then(resp => {
if(resp.data.success === true) {
this.authenticator.setToken(resp.data.api_token);
this.loginCallback();
} else {
this.authenticator.removeToken()
}
})
.catch(err => {
console.error(err);
});
}
render(){
<button onClick={this.attemptLogin}> Log In </button>
}
}
export default Login;
I'm setting the authenticated state to true in the callback method, but when I go to the protected route (and run it's render method) it appears to be evaluating to false.
If I'm misunderstanding the react props system, let me know. If you'd like to see any more of the code let me know and I'll amend the question.
You have to create a PrivateRoute HOC component first:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('bpm-user')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
and wrap your routes that most be protected:
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/new/index" component={NewIndex} />
<PrivateRoute path="/jobs/index" component={JobsIndex} />
<PrivateRoute path="/unions/index" component={UnionsIndex} />
<PrivateRoute exact path="/" component={ListIndex} />
<PrivateRoute exact path="/charges" component={MunicipalCharges} />
</Switch>
and use Link
<Link to="/jobs/index">Jobs</Link>
my login reducer
import axios from 'axios';
import * as actionTypes from './AuthActionTypes';
export const login = (user) => {
return dispatch => {
// for example => dispatch({type:actionTypes.REQUEST_LOGIN_USER});
axios({
method: 'post',
url: '/api/auth/login',
data: { 'username': user.username, 'password': user.password },
headers: { 'Content-Type': 'application/json;charset=utf-8' }
})
.then(res => {
localStorage.setItem('bpm-user', JSON.stringify(res.data));
dispatch({
type: actionTypes.LOGIN_USER,
payload: res.data
})
})
.catch(error => {
// TODO... for example => dispatch({type:actionTypes.FAILD_LOGIN_USER, payload:error});
})
}
}
export const logout = () => {
localStorage.removeItem('bpm-user');
}
like the example codes that i copied from my own project

React router dom show a component after user signs in [duplicate]

I'm trying to write Authentication checking for my DashBoard. But the function itself is not getting called. Can anyone give me some solution for this? I'm developing in ReactJs.
This is the Route part :
<Router>
<div>
<Route exact path={"/"} component={Home} />
<Route path={"/SignUp"} component={SignUp} />
<Route path={"/SignIn"} component={SignIn} />
<Route path={"/Dashboard"} component={Dashboard} onEnter={this.requireAuth} />
</div>
</Router>
This is the function :
requireAuth (nextState, replace) {
console.log("?????????????",this.state.getToken);
if(!this.state.getToken) {
replace({pathname: '/'});
}
}
In react-router v4, you can make use of render prop to Route along with the lifecycle methods to replace the onEnter functionality existing in react-router v3.
See this answer for more details:
onEnter prop in react-router v4
However since all you want to do is authentication in the onEnter prop, you could easily create a HOC that does that
const RequireAuth = (Component) => {
return class App extends Component {
componentWillMount() {
const getToken = localStorage.getItem('token');
if(!getToken) {
this.props.history.replace({pathname: '/'});
}
}
render() {
return <Component {...this.props} />
}
}
}
export { RequireAuth }
and use it like
<Route path={"/Dashboard"} component={RequireAuth(Dashboard)}/>
Edit: In case you need to make a network call to find if the use if authenticated of not, you would write the HOC like
const RequireAuth = (Component) => {
return class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
const { isAuthenticated, isLoading } = this.state;
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
Update:
In addition to the HOC, you can also go for the PrivateRoute component like
const PrivateRoute = ({component: Component, isAuthenticated, isLoading, ...rest }) => {
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { PrivateRoute };
and you can use it like
class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
<Router>
<div>
<Route exact path={"/"} component={Home} />
<Route path={"/SignUp"} component={SignUp} />
<Route path={"/SignIn"} component={SignIn} />
<PrivateRoute path={"/Dashboard"} component={Dashboard} isAuthenticated={this.state.isAuthenticated} isLoading={this.isLoading}/>
</div>
</Router>
}
}

Categories

Resources