Private Route jsx in react JS - javascript

Problem: I am trying to authenticate a user through isAuth() helpers but it is acting weird. I want it to look for access token if any or call for access token from backend if refresh token is available, and though it works perfectly and sets access token cookie, the issue is if called from PrivateRoutes.jsx, it does not sees the tokens at all and sends the user to login page.
Adding required code for refs:
isAuth():
export const isAuth = () => {
if (window !== undefined) {
const accessCookieChecked = getCookie("_mar_accounts_at");
const refreshCookieChecked = getCookie("_mar_accounts_rt");
if (accessCookieChecked) {
return true;
} else if (refreshCookieChecked) {
console.log(refreshCookieChecked);
axios({
method: "POST",
url: `${API_URL}/api/token`,
data: { refresh_token: refreshCookieChecked },
}).then((res) => {
console.log(res);
setCookie("_mar_accounts_at", res.data.accessToken);
return true;
});
} else {
return false;
}
} else {
return false;
}
};
PrivateRoutes.jsx
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { isAuth } from "../helpers/auth";
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={(props) =>
isAuth() ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
></Route>
);
export default PrivateRoute;
Can someone please see this? And help!

You are running into an async issue most likely, when you make the call in axios, the return true; in the callback never actually returns to your funciton call in the PrivateRoute. Instead, you need to use a Promise/setState/useEffect:
export const isAuth = () => {
if (window === undefined) {
return Promise.resolve(false);
} else {
const accessCookieChecked = getCookie("_mar_accounts_at");
const refreshCookieChecked = getCookie("_mar_accounts_rt");
if (accessCookieChecked) {
return Promise.resolve(true);
} else if (refreshCookieChecked) {
console.log(refreshCookieChecked);
return new Promise(resolve => {
axios({
method: "POST",
url: `${API_URL}/api/token`,
data: { refresh_token: refreshCookieChecked },
}).then((res) => {
console.log(res);
setCookie("_mar_accounts_at", res.data.accessToken);
resolve(true);
});
})
} else {
return Promise.resolve(false);
}
}
};
import React, { useState, useEffect } from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuth } from '../helpers/auth';
const PrivateRoute = ({ component: Component, ...rest }) => {
const [isAuthTrue, setIsAuthTrue] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
isAuth().then(res => {
setIsAuthTrue(res);
setLoading(false);
})
})
return (
<>
{loading ? (
<div>some loading state</div>
) : (
<Route
{...rest}
render={(props) =>
isAuthTrue ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: '/login', state: { from: props.location } }}
/>
)
}
/>
)}
</>
);
};
export default PrivateRoute;

Related

Reactjs useEffect does not change state value when fetching data

I'm trying to implement a Protected Route, which firstly tries to get an authentification(api call) so it can display the Route.
But somehow the state value doesnt change..
Do you got any idea?
const ProtectedRoute = ({ component: Component, ...rest }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const fetch = async () => {
const result = await axios.get("http://localhost:5000/auth/", {
withCredentials: true,
});
if (result.status >= 200 && result.status < 300) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
}
};
useEffect(() => {
fetch();
}, []);
return (
<Route
{...rest}
render={(props) => {
if (isAuthenticated) {
return <Component {...props} />;
} else {
return <Redirect to={"./loginUser"} />;
}
}}
/>
);
};
export default ProtectedRoute;
What you can do is to return something when the api call is still loading. Something like this :
const ProtectedRoute = ({ component: Component, ...rest }) => {
const [isAuthenticated, setIsAuthenticated] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const fetch = async () => {
const result = await axios.get("http://localhost:5000/auth/", {
withCredentials: true,
});
if (result.status >= 200 && result.status < 300) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
}
};
useEffect(() => {
setIsLoading(true);
fetch();
setIsLoading(false);
}, []);
return (
<Route
{...rest}
render={(props) => {
if(isLoading) { // do something }
else if (isAuthenticated !== undefined && !isLoading) {
return <Component {...props} />;
} else {
return <Redirect to={"./loginUser"} />;
}
}}
/>
);
};
export default ProtectedRoute;

How to navigate react page on ternary operators?

I'm trying to navigate to admin page if LoggedIn and admin is true while sending props to /admin but this isn't working. Can you help please?
export default function Auth() {
function login(e) {
e.preventDefault();
const data = { email, password };
axios
.post("http://localhost:3001/api/Login", data, { withCredentials: true })
.then((response) => {
if(!!response.data.loggedIn && !!response.data.admin){ return( <Navigate to="/admin" loggedIn={"response.data.loggedIn"} replace/> )}
else if(!!response.data.loggedIn && ! !!response.data.admin){ window.location.href = "https://www.dummyweb.com/webmail";}
else{return(alert("Username or Password is not valid!"))}
});
}
return (
<div>
<LogginForm/>
</div>
)
}
To conditionally render content or redirect then you should use the following:
react-router-dom
Since you are trying to use the useNavigate hook I'll assume you are using react-router-dom v6 and will start there.
Version 6
The Redirect component was removed in react-router-dom v6. Use the Navigate component and also specify the replace prop to do a redirect instead of a normal navigation.
export default function Admin(props) {
return props.isLoggedIn ? (
<div>
<Row className={stylesadmin.root}>
<Uploader/>
<ToastContainer/>
</Row>
</div>
) : (
<Navigate to="/Auth" replace />
);
}
Version 5
Use the Redirect component to redirect to "/Auth".
export default function Admin(props) {
return props.isLoggedIn ? (
<div>
<Row className={stylesadmin.root}>
<Uploader/>
<ToastContainer/>
</Row>
</div>
) : (
<Redirect to="/Auth" />
);
}
Update
Using imperative navigation/redirect.
Import the useNavigate (v6) / useHistory (v5) hook and issue imperative redirect.
export default function Auth() {
const navigate = useNavigate(); // v6
// const history = useHistory(); // v5
function login(e) {
e.preventDefault();
const data = { email, password };
axios
.post("http://localhost:3001/api/Login", data, { withCredentials: true })
.then((response) => {
if (!!response.data.loggedIn && !!response.data.admin) {
navigate(
"/admin",
{
replace: true,
state: { loggedIn: response.data.loggedIn },
}
);
// history.replace(
// "/admin",
// {
// loggedIn: response.data.loggedIn,
// }
// );
} else if (!!response.data.loggedIn && ! !!response.data.admin) {
window.location.href = "https://www.dummyweb.com/webmail";
} else {
alert("Username or Password is not valid!");
}
});
}
return (
<div>
<LogginForm/>
</div>
);
}
You can use react-router Redirect component
return <>{props.isLoggedIn ? (<div>
<Row className={stylesadmin.root}>
<Uploader/>
<ToastContainer/>
</Row>
</div>) : <Redirect to='/Auth'/> }</>
}
you can use either Redirect component or use useHistory hook from react-router-dom
import { useNavigate } from 'react-router-dom';
export default function Auth() {
let navigate = useNavigate();
function login(e) {
e.preventDefault();
const data = { email, password };
axios
.post("http://localhost:3001/api/Login", data, { withCredentials: true })
.then((response) => {
if(!!response.data.loggedIn && !!response.data.admin){ return( navigate( "/admin" , { state:{loggedIn:response.data.loggedIn}} )}
else if(!!response.data.loggedIn && ! !!response.data.admin){ window.location.href = "https://www.dummyweb.com/webmail";}
else{return(alert("Username or Password is not valid!"))}
});
}
return (
<div>
<LogginForm/>
</div>
)
}
and on the admin page I handle it as follows
export default function Admin() {
const {state} = useLocation();
const { loggedIn} = state;
return(
loggedIn?
(<whatever/>):(<whatever2/>)
)
}
This works perfectly

ReactJS : Maximum dept exceeded / Private Routes

Private route /profile rendering me an error.
My app works until I try to go on /profile.
I have never seen this error yet, so while Im searching Im posting here, maybe someone has an idea.
Also Im looking for a good documentation/tutorial on how to make private routes using jwt and js-cookie.
error
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
App.js
import { useState } from 'react';
import Axios from "axios";
import Cookies from "js-cookie";
import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom";
import ProtectedRoute from './components/ProtectedRoute';
import Profile from "./pages/Profile";
const App = () => {
const [emailRegistration, setEmailRegistration] = useState("");
const [passwordRegistration, setPasswordRegistration] = useState("");
const [emailLogin, setEmailLogin] = useState("");
const [passwordLogin, setPasswordLogin] = useState("");
const [isAuth, setIsAuth] = useState(false);
const register = () => {
Axios.post('http://localhost:3000/api/signup', {
user:{
email: emailRegistration,
password: passwordRegistration
}
}).then((response) => {
console.log(response);
});
};
const login = () => {
fetch("http://localhost:3000/api/login", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user: {
email: emailLogin,
password: passwordLogin,
},
}),
})
.then((res) => {
if (res.ok) {
console.log(res.headers.get("Authorization"));
localStorage.setItem("token", res.headers.get("Authorization"));
setIsAuth(true)
return res.json();
} else {
return res.text().then((text) => Promise.reject(text));
}
})
.then((json) => console.dir(json))
.catch((err) => console.error(err));
};
const getArticles = () => {
fetch("http://localhost:3000/articles", {
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (res.ok) {
return res.json();
} else if (res.status == "401") {
throw new Error("Unauthorized Request. Must be signed in.");
}
})
.then((json) => console.dir(json))
.catch((err) => console.error(err));
}
const logout = () => {
fetch("http://localhost:3000/api/logout", {
method: "delete",
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (res.ok) {
setIsAuth(false)
return res.json();
} else {
return res.json().then((json) => Promise.reject(json));
}
})
.then((json) => {
console.dir(json);
})
.catch((err) => console.error(err));
};
const testBearer = () => {
let cookie = Cookies.get('token');
console.log(cookie)
}
return (
<Router>
<div className="app">
{isAuth ?
(
<button onClick={logout} > logout </button>
)
:
(
<div className="auth">
<div className="registration">
<h1> Registration </h1>
<label> Email </label>
<input
type="email"
onChange={(e) => {
setEmailRegistration(e.target.value);
}}
/>
<label> Password </label>
<input
type="password"
onChange={(e) => {
setPasswordRegistration(e.target.value);
}}
/>
<button onClick={register} > Register </button>
</div>
<div className="login">
<h1> Login </h1>
<label> Email </label>
<input
type="email"
onChange={(e) => {
setEmailLogin(e.target.value);
}}
/>
<label> Password </label>
<input
type="password"
onChange={(e) => {
setPasswordLogin(e.target.value);
}}
/>
<button onClick={login} > Login </button>
</div>
</div>
)
}
<br />
<hr />
<br />
<button onClick={getArticles} >getArticles </button>
<button onClick={testBearer} >testCookie </button>
<button onClick={logout} > logout </button>
</div>
<Route path="/">
</Route>
<ProtectedRoute exact path="/profile" component={Profile} isAuth={isAuth} />
</Router>
)};
export default App;
Profile.js
import React from 'react';
import {withRouter} from "react-router-dom";
const Profile = () => {
return (
<div>
if you see this you entered the auth.
</div>
);
};
export default withRouter(Profile);
ProtectedRoute.js
import React from 'react';
import {Route, Redirect} from "react-router-dom";
const ProtectedRoute = ({isAuth: isAuth, component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => {
if (isAuth) {
return <Component />
} else {
return (
<Redirect to={{pathname: "/profile", state: {from: props.location }}} />
);
}
}}/>
);
};
export default ProtectedRoute;
Let your ProtectedRoutes to have a type based on their auth capabilities such as LoginRoute and PrivateRoute. Your Profile route acts as the login page. Therefore, it doesn't need to be user authenticated to view that page. Then create another route as dashboard, which will be the page user able to view in a successful log in. Now, keep your routes as follows.
<Route path="/"></Route>
<ProtectedRoute
exact
path="/profile"
component={Profile}
isAuth={isAuth}
type="LoginRoute"
/>
<ProtectedRoute
exact
path="/dashboard"
component={() => <div>User Entered into Dashboard</div>}
isAuth={isAuth}
type="PrivateRoute"
/>
Then, you need to handle the type based authentication functionality inside your ProtectedRoute component as follows.
import React from "react";
import { Route, Redirect } from "react-router-dom";
const getRouteVariables = (type, isAuth) => {
switch (type) {
case "LoginRoute":
return [!isAuth, "/dashboard"];
case "PrivateRoute":
return [isAuth, "/profile"];
default:
return [isAuth, "/profile"];
}
};
const ProtectedRoute = ({ type, isAuth, component: Component, ...rest }) => {
const [isRouteChanged, redirectPath] = getRouteVariables(type, isAuth);
return (
<Route
{...rest}
render={(props) => {
if (isRouteChanged) {
return <Component />;
} else {
return (
<Redirect
to={{ pathname: redirectPath, state: { from: props.location } }}
/>
);
}
}}
/>
);
};
export default ProtectedRoute;
getRouteVariables function return isRouteChanged and redirectPath. isRouteChanged means whether route needs to be changed based on isAuth and redirectPath means which path needs to be taken if redirection happens.
For LoginRoute type /profile route only comes up when isAuth becomes false. If isAuth is true, then moving to /profile route will redirect to /dashboard.
For PrivateRoute type /dashboard route only possible to access when isAuth becomes true. If isAuth is false, then moving to /dashboard route will redirect to /profile.

REACT - how can wait until api call finish to load a path?

I use the axios post to request to the back-end if the user have access to the application. The problem is the axios returns undefined and then true or false . Have a private Route to manage what to do in case returns true or false (in this case undefined = false) ,is axios the problem or is there some other way? like wait until returns true or false
IsLogin.jsx
import React from 'react'
const axios = require('axios');
export const AuthContext = React.createContext({})
export default function Islogin({ children }) {
const isAuthenticated =()=>{
try{
axios.post('/api/auth').then(response => {
var res = response.data.result;
console.log(res)
return res
})
} catch (error) {
console.error(error);
return false
}
}
var auth = isAuthenticated()
console.log(auth);
return (
<AuthContext.Provider value={{auth}}>
{children}
</AuthContext.Provider>
)
}
privateRoute.js
import React, { useContext } from 'react';
import { Route, Redirect } from 'react-router-dom';
import {AuthContext} from '../utils/IsLogin';
const PrivateRoute = ({component: Component, ...rest}) => {
const {isAuthenticated} = useContext(AuthContext)
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /unauth page
<Route {...rest} render={props => (
isAuthenticated ?
<Component {...props} />
: <Redirect to="/unauth" />
)} />
);
};
export default PrivateRoute;
app.js
class App extends Component {
render() {
return (
<>
<BrowserRouter>
<Islogin>
<Header/>
<Banner/>
<Switch>
<PrivateRoute exact path="/index" component={Landing} />
<PrivateRoute path="/upload" component={Upload} exact />
<PublicRoute restricted={false} path="/unauth" component={Unauthorized} exact />
</Switch>
</Islogin>
</BrowserRouter>
</>
);
}
}
You don't want to return anything in your post request. You should be updating your context store
const isAuthenticated = () => {
try {
axios.post('/api/auth').then(response => {
var res = response.data.result;
console.log(res)
// update your context here instead of returning
return res
})
} catch (error) {
console.error(error);
return false
}
}
In your private route, have a componentDidUpdate style useEffect hook to check for changes in authentication status and update an internal flag on an as-needed basis
const PrivateRoute = ({ component: Component, ...rest }) => {
const { isAuthenticated } = useContext(AuthContext)
const [validCredentials, setValidCredentials] = React.useState(false)
React.useEffect(() => {
if (typeof isAuthenticated === 'boolean') {
setValidCredentials(isAuthenticated)
}
}, [isAuthenticated])
return (
// Show the component only when the user is logged in
// Otherwise, redirect the user to /unauth page
<Route {...rest} render={props => (
validCredentials ?
<Component {...props} />
: <Redirect to="/unauth" />
)} />
);
};
I am curious as to why you didn't use 'async await',lol.
You are making a post request to the endpoint '/api/auth',but you didn't give it any data to post,like:
try{
axios.post('/api/auth',{username,password}).then(response => {
var res = response.data.result;
console.log(res)
return res
})
} catch (error) {
console.error(error);
return false
}

Warning: Can't call setState (or forceUpdate) on an unmounted component

I am getting this warning every time I sign in,
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
Here is my code:
authpage.js
handleLoginSubmit = (e) => {
e.preventDefault()
let { email,password } = this.state
const data = {
email : email,
password : password
}
fetch('http://localhost:3001/auth/login',{
method : 'post',
body : JSON.stringify(data),
headers : {
"Content-Type":"application/json"
}
}).then(res => res.json())
.then(data => {
if(data.success){
sessionStorage.setItem('userid',data.user.id)
sessionStorage.setItem('email',data.user.email)
}
this.setState({loginData : data,
userData : data,
email:"",
password:""})
if(data.token) {
Auth.authenticateUser(data.token)
this.props.history.push('/dashboard')
}
this.handleLoginMessage()
this.isUserAuthenticated()
})
}
export default withRouter(AuthPage)
use withRouter so I can access props which I use to navigate this.props.history.push('/dashboard')
if I didn't use withRouter I cannot access this.props
index.js
const PrivateRoute = ({ component : Component, ...rest }) => {
return (
<Route {...rest} render = { props => (
Auth.isUserAuthenticated() ? (
<Component {...props} {...rest} />
) : (
<Redirect to = {{
pathname: '/',
state: { from: props.location }
}}/>
)
)}/>
)
}
const PropsRoute = ({ component : Component, ...rest }) => (
<Route {...rest} render = { props => (
<Component {...props} {...rest} />
)}/>
)
const Root = () => (
<BrowserRouter>
<Switch>
<PropsRoute exact path = "/" component = { AuthPage } />
<PrivateRoute path = "/dashboard" component = { DashboardPage } />
<Route path = "/logout" component = { LogoutFunction } />
<Route component = { () => <h1> Page Not Found </h1> } />
</Switch>
</BrowserRouter>
)
I think the problem is with my withRouter,
how can we access this.props without using withRouter ?
It's async so
this.setState({
loginData : data,
userData : data,
email:"",
password:""
})
make error
You can use this._mount to check component is mounted or unmount
componentDidMount () {
this._mounted = true
}
componentWillUnmount () {
this._mounted = false
}
...
if(this._mounted) {
this.setState({
loginData : data,
userData : data,
email:"",
password:""
})
...
You can use _isMount with overloading the setState function:
componentWillUnmount() {
this._isMount = false;
}
componentDidMount() {
this._isMount = true;
}
setState(params) {
if (this._isMount) {
super.setState(params);
}
}
I had some problems using this.setState({ any }).
Every time the component was built, it called a function that used Axios and the response made a this.setState({ any }).
My problem was solved as follows:
In the componentDidMount() function I call another function called initialize() that I left as async and through it I can call my function that performs fetch and this.setState({ any }).
componentDidMount() {
this.initialize();
}
myFunction = async () => {
const { data: any } = await AnyApi.fetchAny();
this.setState({ any });
}
initialize = async () => {
await this.myFunction();
}

Categories