How do I get this protected route working? - node.js

So I am working on setting up a protected route in react. I am using Express and Passport for my authentication. I setup the following code to setup the protected route, but the checkLogin() function (which produces true if logged in and false if not) at the top which does work if I take it out of this code and execute it manually does not execute in this use case. (I think its because the promise is still pending, but I am not sure how to make it async I just keep getting errors)
The Code:
import React from 'react';
import { Route, Redirect} from 'react-router-dom';
import axios from 'axios';
const RouteGuard = ({ component: Component, ...rest }) => {
const checkLogin = () => {
axios({
type: 'get',
url: 'http://localhost:5000/checklogin',
withCredentials: true,
}).then((response) => {
console.log(response);
}).catch((error) => {
console.log(error);
})
}
return(
<Route
{...rest}
render={(props) =>
checkLogin() === true ? (
<Component {...props} />
) : (
<Redirect to='/' />
)
}
/>
)
};
export default RouteGuard;
Any ideas on getting this to work, or a better way to go about it? I just want to send a post to my backend and return true or false to check that the user is logged in and then protect routes based on that.
Update:
I now changed it to use useEffect() as well as set some state as suggested, but the isLoggedIn state is not being set before the if statement tries to check if its set to true.
Updated Code:
import React, { useState, useEffect } from 'react';
import { Route, Redirect} from 'react-router-dom';
import axios from 'axios';
const RouteGuard = ({ component: Component, ...rest }) => {
const [isLoggedIn, setIsLoggedIn] = useState()
useEffect(() => {
const getStatus = async () => {
const result = await axios({
type: 'get',
url: 'http://localhost:5000/checklogin',
withCredentials: true,
});
setIsLoggedIn(result);
};
getStatus();
}, []);
return (
<Route
{...rest}
render={(props) =>
isLoggedIn === true ? (
<Component {...props} />
) : (
<Redirect to='/' />
)
}
/>
)
};
export default RouteGuard;

Related

API GET Response is not working in front-end

The code works well in Postman. But when fetching it in the front-end the response is not working. I can't find the solution. I am new in MERN stack. It gives me error.
product.action.js:7 Uncaught (in promise) AxiosError {message: 'timeout of 1000ms exceeded', name: 'AxiosError', code: 'ECONNABORTED', config: {…}, request: XMLHttpRequest}
The back end code :- router.get("/products/:slug", getProductsBySlug);
exports.getProductsBySlug = (req, res) => {
const { slug } = req.params;
Category.findOne({ slug: slug })
.select("_id")
.exec((error, category) => {
if (error) {
return res.status(400).json({ error });
}
if (category) {
Product.find({ category: category._id }).exec((error, products) => {
if (error) {
return res.status(400).json({ error });
}
res.status(200).json({ products });
});
}
});
};
Front end code: product.action.js
import axiosInstance from "../helpers/axios";
export const getProductsBySlug = (id) => {
return async (dispatch) => {
const res = await axiosInstance.get(`/products/${id}`);
console.log(res);
};
};
`
The above response is occure error.
`
productListPage:
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { getProductsBySlug } from "../../actions";
import Layout from "../../components/Layout";
import { useParams } from "react-router-dom";
const ProductListPage = (props) => {
const dispatch = useDispatch();
const slug = useParams();
useEffect(() => {
console.log(slug);
dispatch(getProductsBySlug(slug));
}, []);
return <Layout>Product List Page</Layout>;
};
export default ProductListPage;
In App.js
function App() {
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" exact element={<HomePage />} />
<Route path=":slug" element={<ProductListPage />} />
</Routes>
</BrowserRouter>
</div>
);
Tell me the way please.
In your App.js, change the Route declaration to (add the leading /):
<Route path="/:slug" element={<ProductListPage />} />
In your productListPage component, you should deconstruct the property from useParams:
const { slug } = useParams();
Clear the local storage key/value things, should be working.

React express jwt, Where to call the api to check if a user if logged in

This is how I redirect a user depending if he is logged in or not. I am just checking if there is a cookie or not in this example.
import { Route, Redirect } from "react-router-dom";
import cookies from "js-cookies";
export const PrivateRoute = ({ children, ...rest }) => {
const userSignedIn = () => {
return cookies.getItem("jwt");
};
return (
<Route
{...rest}
render={({ location }) =>
userSignedIn() ? (
children
) : (
<Redirect to={{ pathname: "/login", state: { from: location } }} />
)
}
></Route>
);
};
I am trying to improve my code by checking if a user is logged in with my backend.
What is the proper way to check if a user is logged in with react express and JWT ?
Where should I call my api ?
I have been stuck with this problem for a while now...
Thanks
When you logged in then you set a variable auth true in global state and set localstorage.setItem("auth", true) as like. You need to understand about private route and public route and define private route and public route, when you logged in then get auth true then call private route otherwise call public route.In this time you set again global state from localstorage.getItem("auth") because when you reload this page then auth variable true get in localstorage, hope you understand thanks
So this is how I solved my question.
Might not be the best...
import { Route, Redirect } from "react-router-dom";
import { useEffect, useState } from "react";
import { Loading } from "../Loading/Loading";
import axios from "axios";
export const PrivateRoute = ({ children, isAuthenticated, ...rest }) => {
const [isLoading, setIsLoading] = useState(true);
const [isAuth, setIsAuth] = useState(false);
const getIsAuthenticated = async () => {
try {
const res = await axios.get("http://localhost:5000/api/user");
console.log(res);
setIsAuth(true);
} catch (error) {
console.log(error);
setIsAuth(false);
}
setIsLoading(false);
};
useEffect(() => {
getIsAuthenticated();
return () => {};
}, [isAuth]);
if (isLoading) {
return <Loading />;
}
return (
<Route
{...rest}
render={({ location }) =>
isAuth ? (
children
) : (
<Redirect to={{ pathname: "/login", state: { from: location } }} />
)
}
></Route>
);
};
backend code
const user_GET = async (req, res) => {
res.status(400).send("wrong token");
//res.status(200).send("right token");// DEBUG
};

React protected route using axios & JWT

I am trying to make a protected route with Reatjs, nodejs and JWT. The problem is that my component renders before my API checked the client token. This is the code I am trying :
import React, {useState, useEffect} from 'react';
import { Route, Redirect } from 'react-router-dom';
import AuthAPI from './../utils/AuthAPI';
const ProtectedRoute = ({children, ...rest}) => {
const [isAuth, setIsAuth] = useState(false);
const fetchData = async () => {
await AuthAPI.isAuth((res)=>{ //API call
setIsAuth(res);
});
}
useEffect(()=>{
fetchData();
},[]);
return (
<Route {...rest}
render={(props)=>{
return(
isAuth ? children : <Redirect to='/' />
);
}}
/>
);
};
And this is the API call :
static isAuth(callback){ //static method from the class 'AuthAPI' imported above
const url = 'http://localhost:5000/api/Auth/checking';
const options = {
method: 'GET',
url: url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
},
data: {}
}
return axios(options)
.then((response)=>{
callback(true);
}).catch((err)=>{
callback(false);
});
}
When I load the page, it directly redirects since the state isAuth is set to false by default.
I already used this model of code to display a list of things gotten from an API and it worked fine. I assume it is not the best way to do that but most of the examples I have found are not using an actual API but just fake auth without using promises.
EDIT 1.2 :
I've tried this code, from Udendu Abasili :
import React, {useState, useEffect} from 'react';
import { Route, Redirect } from 'react-router-dom';
import AuthAPI from './../utils/AuthAPI';
const ProtectedRoute = ({children, ...rest}) => {
const [isAuth, setIsAuth] = useState(false);
const [isLoaded, setIsLoaded] = useState(false)
useEffect(()=>{
let mounted = true;
AuthAPI.isAuth().then(()=>{
if (mounted) {
console.log("Worked"); //display Worked
setIsLoaded(true); // This line 1
setIsAuth(true); // This line 2
}
}).catch(()=>{
if (mounted) {
console.log("Failed");
setIsLoaded(true);
setIsAuth(false);
}
});
return () => {
mounted = false;
}
},[]);
return (
!isLoaded ?
<h5>Loading</h5> : (
<Route {...rest}
render={(props)=>{
console.log("--->",isAuth,",",isLoaded); // displays false, true
return(
isAuth ? children : <Redirect to='/' />
);
}}
/>
)
);
};
export default ProtectedRoute;
I have found a weird bug. If I swap the lines commented as 'line 1' and 'line 2', it works otherwise it doesn't.
The way react js lifecycle works, the return component gets called before useEffect(which the hook equivalent of componentDidMount on the first mount). So you need to create a form of loader component ( replace the <Text>Loading</Text> with an actual CSS loader ) that waits for your isAuth function to finish.
const ProtectedRoute = ({children, ...rest}) => {
const [isAuth, setIsAuth] = useState(false);
const [loaded, setLoaded] = useState(false);
const fetchData = async () => {
//you need to add try catch here
await AuthAPI.isAuth((res)=>{ //API call
setIsAuth(res);
setLoaded(true)
});
}
useEffect(()=>{
fetchData();
},[]);
return (
loaded ?
<Text>Loading</Text> : (
<Route {...rest}
render={(props)=>{
return(
isAuth ? children : <Redirect to='/' />
);
}}
)
/>
);
};
As you rightfully said, this is not the best way to do it. I won't recommend calling a function to check authentication in the protected route component. Typically, I just pass an isAuthenticated paramter to ProctectedRoute component which gets updated with help of Redux. You should look it up

Attempted import error: my file does not contain a default export?

I am having an issue with a react project im working on
my authHelper is not being imported correctly into a privateRoute component im making. Im a bit of a newb and going off a book I purchased called full stack react projects from Pakkt. I noticed the book has issues all over it with incorrect imports and exports
here is the code below
import { Route, Redirect } from 'react-router-dom';
import auth from '../../helpers/authHelper.js';
const PrivateRoute = ({ component: Component, ...rest }) => {
<Route {...rest} render={props => {
auth.isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/authentication/card',
state: { from: props.location }}} >
</Redirect>
)
}} />
}
export default PrivateRoute
here is the authHelper
import axios from "axios"
const signout = async () => {
try {
let response = await axios.get('auth/signout', {method: 'GET' })
return await response.json()
} catch (err) {
console.log(err)
}
}
const authenticate = (jwt, cb) => {
if(typeof window !== "undefined")
sessionStorage.setItem('jwt', JSON.stringify(jwt))
cb()
}
const isAuthenticated = () => {
if(typeof window == "undefined")
return false
if (sessionStorage.getItem('jwt'))
return JSON.parse(sessionStorage.getItem('jwt'))
else
return false
}
const clearJWT = (cb) => {
if(typeof window !== "undefined")
sessionStorage.removeitem('jwt')
cb()
signout().then((data) => {
document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"
})
}
export { isAuthenticated, authenticate, clearJWT}
Solved.
in PrivateRoute.js I changed
import auth from '../../helpers/authHelper.js'
to
const auth = require('../../helpers/authHelper.js')
There was also another error in PrivateRoute.js
const PrivateRoute = ({ component: Component, ...rest }) => { //this curly brace needs to be ( instead of {
<Route {...rest} render={props => { this needs to be a ( instead of {
auth.isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/authentication/card',
state: { from: props.location }}} >
</Redirect>
)
} //this Curley brace to left needs to be a ) instead of } //} />
} // this needs to be changed to a ) instead of }
export default { isAuthenticated, authenticate, clearJWT }
or
export const ()=>{}
And
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => {
auth.isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/authentication/card',
state: { from: props.location }}} >
</Redirect>
)
}} />
)

Passport-Local with Gatsby log out does not completely log out

i'm using Passport-local for authentication and Gatsby on front end
Generally, the code works fine. When I click on signout, the server returns a 200 call and I get a response "User sign out successfully". I'm then navigated to the signin page. From there, I am unable to access my Post page which is private route. My signin and post page are client side routes
The issue comes when I click on the home page (which is a static page). From there, when I click on the post link, I'm navigated to the post page which supposedly is inaccessible now that I have signed out. My fetchuser action creator runs and is able to fetch the user detail even though I have already signed out from my app
Anyone knows how to resolve this issue? Thanks in advance
SERVER
signout api
router.get("/signout", (req, res) => {
req.logout();
res.send("Sign Out Successfully");
});
me api
router.get("/me", (req, res) => {
res.send(req.user);
});
CLIENT
app
const App = () => {
useEffect(() => {
store.dispatch(fetchUser())
}, [])
return (
<Layout>
<Alert />
<Router basepath="/app">
<Signin path="/signin" />
<Signup path="/signup" />
<PrivateRoute path="/post" component={Post} />
{/* <Default path="/" /> */}
</Router>
</Layout>
)
}
export default App
fetchUser action creator
export const fetchUser = () => async dispatch => {
try {
const res = await axios.get("http://localhost:5000/api/users/me", {
withCredentials: true,
})
dispatch({
type: FETCH_USER,
payload: res.data,
})
} catch (err) {
console.log(err)
dispatch({
type: AUTH_ERROR,
})
}
}
signout action creator
export const signOut = () => async dispatch => {
const res = await axios.get("http://localhost:5000/api/users/signout")
console.log(res)
dispatch({
type: SIGNOUT,
})
navigate("/app/signin")
}
I think your approach is correct and valid, despite personally thinking that handling it with cookies or localStorage could be easily maintained.
Your <PrivateRoute> component should handle your logic and perform some actions depending on the user state (logged or not), something like:
import React from "react"
import { navigate } from "gatsby"
import { isLoggedIn } from "../services/auth"
const PrivateRoute = ({ component: Component, location, ...rest }) => {
if (!isLoggedIn() && location.pathname !== `/app/login`) {
navigate("/app/login") // or your desireed page
return null
}
return <Component {...rest} />
}
export default PrivateRoute
Your auth service, should handle your requests, in this case using localStorage but it can be replaced for your API requests:
export const isBrowser = () => typeof window !== "undefined"
export const getUser = () =>
isBrowser() && window.localStorage.getItem("gatsbyUser")
? JSON.parse(window.localStorage.getItem("gatsbyUser"))
: {}
const setUser = user =>
window.localStorage.setItem("gatsbyUser", JSON.stringify(user))
export const handleLogin = ({ username, password }) => {
if (username === `john` && password === `pass`) {
return setUser({
username: `john`,
name: `Johnny`,
email: `johnny#example.org`,
})
}
return false
}
export const isLoggedIn = () => {
const user = getUser()
return !!user.username
}
export const logout = callback => {
setUser({})
callback()
}

Resources