Validating React route with axios async function - javascript

So I am creating a web app with server (node+express) and client (cra) sides.
I have an issue with validating user according to the jwt token I set as a cookie. The validation api endpoint on server side works as it should (tested it with postman) but the problem is the async check function that returns the promise therefore the route doesn't really know is it validated since the response is pending.
Here is the api endpoint on server side:
/api/token.js
router.get('/',
jwt({secret:'token-secret' }),
function (req,res) {
console.log(req);
if(!req.user) return res.sendStatus(401);
res.sendStatus(200);
}
)
and here is the app.js on the client side: src/app.js that handles routing ( /dashboard should be available only for validated users)
function App() {
function checkToken() {
let token = Cookies.get('Access Token')
axios.get('http://localhost:9000/api/token', {
headers: {
'Authorization': `bearer ${token}`
}
}).then(res => {
return res.status;
}).catch(err => console.log(err));
}
const handleToken = async () => {
const result = await checkToken();
return result;
}
return (
<BrowserRouter>
<Route exact={true} path='/' render={() => (
<div className="App">
<Home />
</div>
)}/>
<Route exact={true} path='/dashboard' render={() => (
<div className="App">
{console.log('checktoken log', handleToken())}
{checkToken() ? <Dashboard /> : <Login />}
</div>
)}/>
<Route exact={true} path='/login' render={() => (
<div className="App">
<Login />
</div>
)}/>
</BrowserRouter>
);
}
At this point I am aware that perhaps I shouldn't be doing a validation in this way since there is probably no way I can get the return before the render, perhaps it should be done in a lifecycle hook componentWillMount but I haven't been able to introduce it to this file (or everything should be done in an entirely different file).
Thanks
p.s. I've omitted all imports and export defaults since that is not relevant here

Well, I've made it with a couple of substantial changes. First, in order to use history.push I had to refactor BrowserRouter part so now it looks like this
app.js
render() {
return (
<Router history={history}>
<Route exact path='/' component={Home} />
<Route exact path='/dashboard' component={Dashboard} />
<Route exact path='/login' component={Login} />
</Router>
);
}
Then I've decided not to use api/token.js. Instead of this api endpoint I've created a Higher Order Component that will check for cookies that have been set during login. Part that gave me most trouble is asynchronous nature of fetching cookies. That was solved with setTimeout inside getCookie function, I've called this function in componentDidMount lifecycle.
src/components/withAuth.js
state = {
data: false,
open: false,
auth: false
}
componentDidMount() {
this.getCookie();
}
getCookie(){
this.setState({
open: true,
})
setTimeout(() => {
const cookie = Cookies.get('Access Token')
if(cookie) {
this.setState({
data: true,
open: false,
auth: true
})
} else if (cookie === undefined) {
this.setState({
auth: true,
open: false
})
}
}, 700)
}
In the end, in order to protect the route I've wrapped the component with HOC
src/Views/Dashboard.js
import requireAuthentication from '../components/withAuth';
class Dashboard extends Component {
render() {
return (
<div>
<DashboardContent />
</div>
);
}
}
export default requireAuthentication(Dashboard);

Related

protected routes in react js app always returns undefined

here is my protectedroute component
am using react-router-dom v6 and accessing the token from localStorage
and either ways user is always returning undefined
import { Outlet, Navigate} from "react-router-dom";
import axios from "axios";
const ProtectedRoute = () => {
const userAuth = () => {
axios.get("http://localhost:5000/isUserAuth", {
headers: {
"x-access-token": localStorage.getItem("token")
}}).then((response) => {
console.log(response.data)
if(response.data.auth) {
console.log(true)
return true;
} else {
console.log(false)
return false;
}
})
}
let auth = userAuth()
console.log("auth",auth)
return (
auth? <Outlet/> : <Navigate to="/"/>
)
}
export default ProtectedRoute
my app.js
function App() {
return (
<BrowserRouter>
<ToastContainer position='top-center'/>
<Routes>
<Route element={<ProtectedRoutes/>}>
<Route exact path='/home'
element={< Home />}/>
<Route exact path='/add'
element={< AddCust />} />
<Route exact path='/update/:id'
element={< AddCust />} />
<Route exact path='/view/:id'
element={< View />} />
<Route exact path='/table'
element={< Table />} />
<Route exact path='/edit-order/:id'
element={< Table />} />
<Route exact path='/orders'
element={< Orders />} />
</Route>
<Route exact path='/' element={< Login />} />
</Routes>
</BrowserRouter>
);
}
export default App;
this is what is consoled logged
enter image description here
which is weired whether a token exists or not auth is always undefined
Nothing is actually returned from the userAuth function, so auth is undefined. While you could return the axios Promise object, this will make userAuth an asynchronous function and not usable as a ternary condition to conditionally render the Outlet component or redirect.
A solution then is to convert auth to a React state, updated in the GET request flow, and conditionally render null or a loading indicator until the auth status resolves.
Example:
const ProtectedRoute = () => {
const { pathname } = useLocation();
const [auth, setAuth] = React.useState(); // initially undefined
React.useEffect(() => {
const checkAuth = async () => {
try {
const response = await axios.get(
"http://localhost:5000/isUserAuth",
{
headers: {
"x-access-token": localStorage.getItem("token")
},
}
);
setAuth(!!response.data.auth);
} catch(error) {
// handle error, log, etc...
setAuth(false);
}
};
checkAuth();
}, [pathname]); // trigger auth check on route change
if (auth === undefined) {
return null; // loading indicator/spinner/etc
}
return auth
? <Outlet/>
: <Navigate to="/" replace state={{ from: pathname }} />;
};

Is there a way to halt loading a react app until authentication status is resolved?

I'm loading a react app that has a root component App.js which is determining if an authenticated user exists through a central redux store, but the problem is it takes a fraction of a second to resolve. Until then the user is getting a flash of the login page even if the user is logged in, which I'm sure counts as bad user experience. Is there a way to not show anything at all until the the status is resolved. I'm attaching a code snippet.
function App(props) {
const cookies = new Cookies();
const { user } = props;
if (cookies.get("authToken") === "null" || cookies.get("authToken") === undefined) {
//console.log("no valid token");
} else {
if (user === null) {
props.fetchLoggedInUser(cookies.get("authToken"));
}
}
const isLoggedIn = user ? (
<div className="App s12 l12 m12">
<Navbar user={user}/>
<Switch>
<Route exact path="/" component={() => (<Home user={user} />)}></Route>
<Route exact path="/create_blog" component={() => (<CreateBlog user={user} />)}></Route>
<Route exact path="/edit_profile" component={() => (<EditProfile user={user} />)}></Route>
</Switch>
</div>
) : (
<div className="App">
<Navbar user={null}/>
<Switch>
<Route exact path="/" component={() => (<Home user={null} />)}></Route>
<Route exact path="/log_in" component={LogIn}></Route>
<Route exact path="/sign_up" component={SignUp}></Route>
</Switch>
</div>
);
return (
<BrowserRouter>
{isLoggedIn}
</BrowserRouter>
);
}
const mapStateToProps = (state) => {
return {
authError: state.auth.authError,
token: state.auth.token,
user: state.auth.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchLoggedInUser: (token) => dispatch(fetchLoggedInUser(token))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Create a state variable isLoading (or with ay name you want). set its initial value to true.
if (isLoading) {
return '';
} else {
return (
<BrowserRouter>
{isLoggedIn}
</BrowserRouter>
);
}
Once you get the value of isLoggedIn you can set back isLoading to false and react will rerender.

React cannot use the updated Redux state after having dispatched an action

I'm relatively new to React and Redux and learning them through my personal project.
The issue here is that isAuthed cannot use the updated Redux state after rest.dispatch(actions.isValidUser(json)) is executed. As far as I know, the Redux state is updated by the action. (But I don't see connect() is called after the update...I don't know if this is associated with this problem.)
Also I tried using Redux-thunk in my action file to fetch data from an API endpoint and using useEffect(), but it didn't solve the issue. Could you please help me out?
Thank you in advance.
**ProtedtedRoute.jsx**
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import * as actions from '../actions/actions';
function ProtectedRoute({ component: Component, isAuthed, ...rest }) {
async function verifyUser() {
// checking if a user is valid by checking JWT
const res = await fetch(ENDPOINT, reqOptions);
if (res.status === 200) {
const json = await res.json();
rest.dispatch(actions.isValidUser(json));
} else {
// failure handling
};
};
verifyUser();
return (
<Route
{...rest}
render={(props) => isAuthed == true ? <Component {...props} /> : <Redirect to={{ pathname: '/login', state: { from: props.location } }} />}
/>
);
};
export default connect(state => {
return {
isAuthed: state.isAuthenticated
}
})(ProtectedRoute);
**reducer.js**
const initState = {
data: {},
// when a user is valid, it will be ```true```
isAuthenticated: false
}
**App.js**
function App() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<div className="content">
<Switch>
<Route exact path="/" component={Home} />
<PublicRoute path="/login" component={LogIn} />
<PublicRoute path="/signup" component={SignUp} />
<ProtectedRoute path="/dashboard" component={Dashboard} />
</Switch>
...
**Login.jsx**
const res = await fetch(ENDPOINT, { reqOptions});
if (res.status === 200) {
props.history.push('/dashboard');
else{
// error handling
}
You don't want a function call like verifyUser(); just floating in the component. It needs to be inside a useEffect hook.
Your Login component fetches the endpoint before you redirect to Dashboard, so you should not need to fetch the endpoint again in order to access the Dashboard through PrivateRoute.
You can change your initialState to include isAuthenticated: undefined as in "we don't know if they are authenticate or not because we haven't checked yet."
Then in PrivateRoute, we only need to call verifyUser if the value of isAuthed is undefined meaning that we haven't checked yet. If it's true or false we just use that existing value.
We still have a bit of a problem with the aysnc flow because we don't want to to Redirect off of the PrivateRoute before verifyUser has finished. For that, we can conditionally render a loading state that shows while awaiting credentials.
I don't know that this is the most elegant solution but it should work
function ProtectedRoute({ component: Component, isAuthed, ...rest }) {
async function verifyUser() {
// checking if a user is valid by checking JWT
const res = await fetch(ENDPOINT, reqOptions);
if (res.status === 200) {
const json = await res.json();
rest.dispatch(actions.isValidUser(json));
} else {
// failure handling
}
}
useEffect(() => {
if (isAuthed === undefined) {
verifyUser();
}
}, [isAuthed]); //re-run when isAuthed changes
return (
<Route
{...rest}
render={(props) =>
isAuthed === undefined ? (
<Loading />
) : isAuthed === true ? (
<Component {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
);
}

React Router Redirect after Login not working properly

I'm developing a basic react application and included react-router.
I have a simple authentication control with the Local Storage.
After a user inputs username and password and clicks login, I do an HTTP call and take response from the server with Axios. Then I set the localStorage 'user' item.
To protect a route I implemented the PrivateRoute component where I check if 'user' in localStorage is set.
I already tried to move set Local Storage inside then() in the Axios HTTP call but nothing changed.
Api CALL
loginUser (username,password) {
return HTTP.post('/login', null, { params: {
username,
password
}})
Api.loginUser(username,password)
.then( (response) => {
console.log("Response DATA");
Api.saveUserData(response.data);
this.setState({ redirect: true });
})
RENDER METHOD
if (this.state.redirect === true) {
return <Redirect to='/home'/>;
}
APP COMPONENT
class App extends Component {
render() {
return (
<Router>
<Route path="/login" component={Login} />
<PrivateRoute path="/home" component={Home} />
</Router>
);
}
}
PRIVATE ROUTE COMPONENT
const PrivateRoute = ({ component: Component, ...rest }) => {
const isLoggedIn = AuthService.isAuthenticated();
return (
<Route
{...rest}
render={props =>
isLoggedIn ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)
}
/>
)
}
The problems seem to be: the local storage is set after the redirect because is null. So I get blank page instead of loading the Home Component. If i refresh the page, the code works fine.

Why wont my Auth0 find my callback route in react?

Auth0 redirects to http://localhost:3000/callback#/acccess-token=dxy
I'm getting a blank screen in my react app.
Heres my main app.js
render((
<HashRouter>
<Main />
</HashRouter>
), $('#app')[0]);
My main contains my routes.js component.
class Routes extends Component {
constructor(props, context) {
super(props, context);
this.state = { mainData: this.props.mainData };
this.handleAuthentication = this.handleAuthentication.bind(this)
}
componentWillReceiveProps(newProps) {
this.setState((previousState) => update(previousState, {
mainData: { $set: newProps.mainData },
}));
}
handleAuthentication(nextState, replace) {
if (/access_token|id_token|error/.test(nextState.location.hash)) {
this.props.auth.handleAuthentication();
}
}
render() {
return (
<div>
<Switch>
<Route path='/callback' render={props => {
this.handleAuthentication(props);
return <Callback {...props} />
}} />
<Route exact path='/' render={props => (
<Dashboard changeAppBar={this.props.changeAppBar} userProfile={this.state.mainData.userProfile} windowHeight={this.props.wh} windowWidth={this.props.ww} />)}
/>
<Route path='/settings' render={props => (
<Settings changeAppBar={this.props.changeAppBar} userProfile={this.state.mainData.userProfile} />)}
/>
</Switch>
</div>
);
}
}
export default Routes;
Heres my init of auth0
this.auth0 = new auth0.WebAuth({
clientID: 'oiEjW4Mf6Ju4BvRfHeuObQnMbghKs38g',
domain: 'cryptok1ng.auth0.com',
responseType: 'token id_token',
redirectUri: 'http://localhost:3000/callback'
})
Everything works fine until I get redirected from auth0 back to /callback. Simply doesn't find a screen /route and renders nothing.
Screenshot of the console. /callback breakpoint is never hit.
Thanks for any help I've been going through the docs and answers to no avail.
I am assuming you In Auth0 front end client configuration> you have added the callback URi as http://localhost:3000/callback and saved it.
And also in your callback.html file you added some tags to show up something once the token is authenticated properly.
If everything is fine and you still get blank error. Please post your console screenshot to have a look.

Categories