How to effectively protect routes in combination with react-router and passport on the backend - node.js

I have React and Node.js with passport.js on the backend which implements my app auth. My react makes a call to my backend and fetches the authorized user via action reducer. Everything works fine but there is a problem with the route guards. This is how I am protecting the routes if the user is not logged in
if(!this.props.auth) return
The problem is when the user is logged in, if page is refreshed, the code above executes faster than mapStateToProps returns the authorized user and the loggedIn user is redirected to the index page. This is bad user experience. Please help me how to resolve this issue and I would appreciate help and advice.
I think what I need to do is to ensure that store is updated first before DOM is rendered but I am not sure how to do it.
Here is dashboard
class Dashboard extends Component {
render() {
if(!this.props.auth) return <Redirect to='/' />
if (!this.props.auth.googleUsername) {
return <div className='container'> Loading ... </div>;
} else {
return (
<div className='container' style={{ margin: '10px 10px' }}>
{this.props.auth.googleUsername}
</div>
);
}
function mapStateToProps({auth}) {
return {auth};
}
export default connect(mapStateToProps)(Dashboard);
Here is App.js
import { connect } from 'react-redux';
import { fetchUser } from './store/actions/index';
import Home from './components/layout/Home';
import Dashboard from './components/layout/Dashboard';
class App extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/dashboard' component={Dashboard} />
</Switch>
</div>
</BrowserRouter>
</div>
);
}
}
export default connect(null,{ fetchUser })(App)
Action reducer
import axios from 'axios';
import { FETCH_USER } from './types';
export const fetchUser = () => async dispatch => {
const res = await axios.get('/api/current_user');
dispatch({ type: FETCH_USER, payload: res.data });
};
Auth Reducer
import { FETCH_USER } from '../actions/types';
export default function(state = false, action) {
switch (action.type) {
case FETCH_USER:
return action.payload;
default:
return state;
}
}

For those who has this issue, I managed to solve the probable. The issue was that I need to persist redux store across my app. I used a third party library called 'redux-persist'
Here is the set I used in my index.js
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './store/reducers/rootReducer';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { PersistGate } from 'redux-persist/integration/react';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer, applyMiddleware(thunk));
const persistor = persistStore(store);
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root'));

Related

How to forward page from react js frontend to node js backend using proxy

I created a simple react app that has two pages ("/", "/show-stocks") using Routes to switch between the two. I created a node/express js backend that has an end point called "/show-stocks/btc", I setted the proxy in the package.json to "proxy": "http://localhost:4000/", Now I want to be able to forward to the backend the page "/show-stocks/btc" (the preset end point).
I tried this solution but it didn't worked. It does take the user to /show-stocks/btc but in the localhost:3000 and sends an error that says that this path does not exsist.
I saw on the internet that if the page does not exists its automatically checks if the proxy has it.
(React listening on port 3000, Node listening on port 4000)
Does someone know how can I implement this?
App.js
import React from "react";
import Navbar from "./components/Navbar";
import "./App.css";
import Home from "./components/pages/Home";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import ShowStocks from "./components/pages/ShowStocks";
function App() {
return (
<>
<Router>
<Navbar />
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/show-stocks" element={<ShowStocks />} />
</Routes>
</Router>
</>
);
}
export default App;
ShowStocks.js:
(incase user goes to "localhost:3000/show-stocks")
import React from "react";
import "../../App.css";
import GetStocks from "../../GetStocks";
export default function ShowStocks() {
return (
<>
<GetStocks />
</>
);
}
GetStocks:
import React, { Component } from "react";
import axios from "react-axios";
import { Link } from "react-router-dom";
export default class GetStocks extends Component {
constructor() {
super();
this.state = {
BTCprice: "Noy Yet Gotten",
};
}
getBTC = () => {
// here I am trying to get response from the proxy /show-stocks/btc end point.
axios.get("/btc").then((response) => {
console.log(response.data.price);
this.setState({
BTCprice: response.data.price,
});
});
};
render() {
return (
<div>
<h1>BTC price: {this.state.BTCprice}</h1>
<Link to="/show-stocks/btc" className="btn-mobile">
<button className={`btn--test`} onClick={this.getBTC}>
Get price
</button>
</Link>
</div>
);
}
}
I fixed it
the problem where at GetStocks.js
this is the new code
import React, { Component } from "react";
import axios from "axios";
export default class GetStocks extends Component {
constructor() {
super();
this.state = {
BTCprice: "Noy Yet Gotten",
};
}
getBTC = () => {
console.log("jey");
axios.get("/show-stocks/btc").then((response) => {
console.log(response.data.price);
this.setState({
BTCprice: response.data.price,
});
});
};
render() {
return (
<div>
<h1>BTC price: {this.state.BTCprice}</h1>
<button onClick={this.getBTC}>Get price</button>
</div>
);
}
}

socket.io broadcasting not working with React

I am currently trying to build a connection between a Node.js application in the backend and a React application in the frontend. The connection from the frontend to the backend seems to work without any problems. Unfortunately, the React application, on the other side, cannot accept any data.
The socket.on(...) function throws an error:
dashboard.js:20 Uncaught TypeError: Cannot read properties of null (reading 'on')
I can not explain where the error lies.
app.js (mounting point of the React app):
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import io from 'socket.io-client';
import Dashboard from "./compontents/views/dashboard/dashboard";
function App() {
const [socket, setSocket] = useState(null);
useEffect(() => {
const newSocket = io(`http://${window.location.hostname}:8040`);
setSocket(newSocket);
return () => newSocket.close();
}, [setSocket]);
return (
<Router>
<div className="app">
<div className="app__view">
<Switch>
<Route exact path="/">
<Dashboard socket={socket} />
</Route>
</Switch>
</div>
</div>
</Router>
);
}
export default App;
dashboard.js (child component):
import React, { useEffect, useState } from 'react';
import { Link } from "react-router-dom";
import FeatherIcon from 'feather-icons-react';
import LargeButton from "../../buttons/largeButton/largeButton";
function Dashboard({ socket }) {
function toggleLight(type) {
if(type) {
// this function works fine
socket.emit("toggle light", type);
console.log(type);
}
}
useEffect(() => {
// this line is causing the error
socket.on('toggle button', (type) => {
console.log(type);
});
}, [socket]);
return(
<div className="view">
<div className="all">
<LargeButton icon="sun" text="Alles einschalten" event={toggleLight} />
<LargeButton icon="moon" text="Alles ausschalten" event={toggleLight} />
</div>
</div>
)
}
export default Dashboard;
It seems like your <Dashboard/> component are mounting before the socket instance are ready to go. Socket connection is an a async procedure so you must take this on mind when you use it.
Try change your app.js to this:
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import io from 'socket.io-client';
import Dashboard from './compontents/views/dashboard/dashboard';
function App() {
const [socket, setSocket] = useState(null);
useEffect(() => {
const newSocket = io(`http://${window.location.hostname}:8040`);
setSocket(newSocket);
return () => newSocket.close();
}, [setSocket]);
if (!socket) {
// catch and show some loading screen
// while the socket connection gets ready
return <div>Loading...</div>;
}
return (
<Router>
<div className="app">
<div className="app__view">
<Switch>
<Route exact path="/">
<Dashboard socket={socket} />
</Route>
</Switch>
</div>
</div>
</Router>
);
}

Browser Reload: Does not get back to selected page, goes to default homepage - ReactJs

I am new in using ReactJS and learning it bit by bit. I have 3 pages: homepage, contacts and moviesDetails. When I travel through contacts or moviesDetails and hit browser's reload, it gets me back to homepage which I do not want. I want it to stay on the same page which I am in.
If I am in contacts page, and hit browser's reload, I want it to stay on contacts page. I do not want it to go to homepage.
I don't know how to store the opened page's path in localStorage. I need help here as I cannot figure out where I am going wrong.
Following is App.js code.
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Homepage from "./homepage/Homepage";
import Contacts from "./contacts/Contacts";
import PrivateRoute from "./private-route/PrivateRoute";
import MoviesDetails from "./MoviesDetails/MoviesDetails";
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Switch>
<PrivateRoute exact path="/homepage" component={Homepage} />
<PrivateRoute exact path="/contacts" component={Contacts} />
<PrivateRoute exact path="/moviesDetails" component={MoviesDetails} />
</Switch>
</div>
</Router>
);
}
}
export default App;
Following is contacts.js code: (All respective components are being imported)
import React, { Component } from "react";
import { connect } from "react-redux";
import Container from '#material-ui/core/Container';
import { changeHeaderName} from '../../actions/homepageActions';
class contacts extends Component {
constructor() {
super();
this.state = {
data:"",
value: 0,
date: "",
errorList: {}
};
}
componentDidMount() {
this.props.header("Contacts");
}
render() {
const { classes } = this.props;
return (
<Container>
<TabPanel value={this.state.value}>
<Grid container
justify="flex-start"
alignItems="center"
>
<Grid xs={6}>
<Typography>
(Names here)
</Typography>
</Grid>
</Grid>
<Grid xs={3}>
<Typography>
Contacts
</Typography>
</Grid>
<Grid xs={5}>
<Typography>
(All the contacts are listed here)
</Typography>
</Grid>
</TabPanel>
</Container>
);
}
}
const mapStateToProps = state => {
return {mainUser: state.auth.mainUser}
};
export default connect(mapStateToProps, {header})(configurations);
Following is store.js code:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
(b&&a()) ||compose)
);
export default store;
And store exported above is been used in index.js file.
Given the above code, I do not want my loaded page go back to the homepage. I want to stay on the same page. Browser reload gets me back to "/homepage" instead of "/contacts". Browser reload gets me back to "/homepage" instead of "/moviesDetails".
I am not using any hooks here. So I don't want my code to be in hooks. Just a simple react.js code.
EDIT NO: 1
Following is my PrivateRoute.js code
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const PrivateRoute = ({component: Component, authentic, ...rest}) => (
<Route
{...rest}
render={ props =>
authentic.isAuthenticated === true ? (
<div>
<Component {...props} />
</div>
) : (
<Redirect to="/" />
)
}
/>
);
PrivateRoute.propTypes = {
authentic: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
authentic: state.auth
});
export default connect(mapStateToProps)(PrivateRoute);
EDIT NO: 2
Following is redux store provider : (this is in index.js file)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
EDIT NO: 3
Following is MoviesDetails component
import React, { Component } from "react";
import Container from '#material-ui/core/Container';
import { connect } from "react-redux";
import Table from '#material-ui/core/Table';
import TableCell from '#material-ui/core/TableCell';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import TableBody from '#material-ui/core/TableBody';
import TableContainer from '#material-ui/core/TableContainer';
import { moviesList } from "./actions/moviesActions";
class MoviesDetails extends Component {
constructor() {
super();
this.state = {
skip: 0,
limit: 10,
pageNumber: 0,
value: '',
nameMovie:"",
genre:"",
ratings:"",
numberOfSongs:"",
releaseDate:"",
};
}
componentDidMount() {this.fetchRecords();}
fetchRecords=async()=>{
let payload ={
nameMovie:this.state.nameMovie,
genre:this.state.genre,
ratings:this.state.ratings,
numberOfSongs :this.state.numberOfSongs ,
releaseDate : this.state.releaseDate,
skip : this.state.limit * this.state.pageNumber,
limit: this.state.limit,
}
await this.props.moviesList(payload);
}
render() {
const { classes } = this.props;
return (
<div>
<div />
<Container >
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Movie Name</TableCell>
<TableCell>Genre</TableCell>
<TableCell>Song Count</TableCell>
<TableCell>Ratings</TableCell>
<TableCell>Release Date</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.props.movies.moviesList.map((movie, index) => {
return (
<TableRow >
<TableCell>
{nameMovie}
</TableCell>
<TableCell>{genre}</TableCell>
<TableCell>{numberOfSongs}</TableCell>
<TableCell>{ratings}</TableCell>
<TableCell>{releaseDate}</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
);
}
}
const mapStateToProps = state => {
return {
movie: state.movie,
adim: state.auth.admin,
}
};
export default connect(mapStateToProps, {moviesList })(MoviesDetails);
I handle my moviesDetails tab like this:
handleMovies = (e) => { e.preventDefault();
this.props.history.push("/moviesDetails"); }
You should persist your redux state to local storage when it updates, and initialize your store from local storage when app loads.
Minimal Redux Store Persistence Example:
Create a "middle" component to handle persisting state updates to localStorage.
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux];
const StorePersister = ({ children }) => {
const state = useSelector(state => state);
useEffect(() => {
localStorage.setItem('myState', JSON.stringify(state));
}, [state]);
return children;
};
index - wrap the App component with the store persister.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './components/App';
import StorePersister from './components/StorePersister';
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<StorePersister>
<App />
</StorePersister>
</Provider>,
document.getElementById('root')
);
Initialize state from local storage. If there is no "myState" key or the parsing returns null then the empty object ({}) will be used as a fallback.
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = JSON.parse(localStorage.getItem('myState')) || {};
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
(b&&a()) || compose
)
);
export default store;
There is also a redux-persist package out there that offers a bit of customization for what gets persisted to and initialized from the persistence.

Why is react admin login page only shown for one second?

I have built a website with react admin. Now i want to add the basic login page from react admin.
For this I have added the simple authProvider which passes all username and password combinations. But now the login page is only shown for one second when I click on the logout button and then the website always jumps back to the dashboard.
I have tried a lot but can't find the error.
Maybe someone has an idea what it could be or had the same problem before?
Here is my code snippet from App.js:
function App() {
return(
<Admin
dashboard={Dashboard}
authProdiver={authProvider}
dataProvider={dataProvider}
customRoutes={customRoutes}
theme={theme}
layout={MyLayout}
>
<Resource
...
/>
...
</Admin>
);
}
export default App;
I added the basic authProvider from the tutorial:
const authProvider = {
// authentication
login: ({ username }) => {
localStorage.setItem('username', username);
// accept all username/password combinations
return Promise.resolve();
},
logout: () => {
localStorage.removeItem('username');
return Promise.resolve();
},
checkError: () => Promise.resolve(),
checkAuth: () =>
localStorage.getItem('username') ? Promise.resolve() : Promise.reject(),
getPermissions: () => Promise.reject('Unknown method'),
};
export default authProvider;
my own layout is:
MyLayout.js:
import React from 'react';
import TreeMenu from '#bb-tech/ra-treemenu';
import { Layout } from 'react-admin';
import MyAppBar from './MyAppBar';
import { ProfileProvider } from './MyProfile/Profile.js';
const MyLayout = (props) => (
<ProfileProvider>
<Layout {...props} appBar={MyAppBar} menu={TreeMenu} />
</ProfileProvider>
);
export default MyLayout;
MyAppBar.js:
import React from "react";
import { AppBar } from "react-admin";
import MyUserMenu from "./MyUserMenu";
const MyAppBar = props =>
<AppBar {...props}
userMenu={<MyUserMenu />}
/>;
export default MyAppBar;
MyUserMenu.js:
import React from 'react';
import { UserMenu, MenuItemLink} from 'react-admin';
import SettingsIcon from '#material-ui/icons/Settings';
const MyUserMenu = (props) => {
return (
<UserMenu {...props}>
<MenuItemLink
to="/my-profile"
primaryText="Mein Profil"
leftIcon={<SettingsIcon />}
/>
</UserMenu>
);
};
export default MyUserMenu;

How to use useEffect and the Context API to check if a user is logged in and protect a route?

I am trying to protect routes based on whether a user is logged in or not but I cannot get this to work properly since it seems that the information stored in my context provider is not available on the initial component load.
I am checking whether the user is authenticated within my App.js file by making a request to my node server through the useEffect hook. It tries to store this info within the context api which it successfully does but it appears that rendering other components will not wait for the context api to "catch up" or load first.
I am sure there is something simple I am missing or maybe I am using bad convention with checking if a user is authenticated. Any help would be appreciated!
App.js
import React, { useState, useEffect } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import Axios from 'axios';
import Header from './components/layout/Header';
import Home from './components/pages/Home';
import HiddenPage from './components/pages/HiddenPage';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import UserContext from './context/UserContext';
import ProtectedRoute from './components/auth/ProtectedRoute';
import './style.scss';
export default function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
});
useEffect(() => {
const checkLoggedIn = async () => {
let token = localStorage.getItem('auth-token');
if (token === null) {
localStorage.setItem('auth-token', '');
token = '';
}
const tokenResponse = await Axios.post(
'http://localhost:5000/users/tokenIsValid',
null,
{ headers: { 'x-auth-token': token } }
);
if (tokenResponse.data) {
const userResponse = await Axios.get('http://localhost:5000/users/', {
headers: { 'x-auth-token': token },
});
setUserData({
token,
user: userResponse.data,
});
}
};
checkLoggedIn();
}, []);
return (
<>
<BrowserRouter>
<UserContext.Provider value={{ userData, setUserData }}>
<Header />
<div className="container">
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<ProtectedRoute path="/hidden" component={HiddenPage} />
</Switch>
</div>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
ProtectedRoute.js
import React, { useContext } from 'react';
import { Redirect } from 'react-router-dom';
import UserContext from '../../context/UserContext';
export default function ProtectedRoute(props) {
const { userData } = useContext(UserContext);
const Component = props.component;
const isAuthenticated = !!userData.user;
console.log(isAuthenticated);
return isAuthenticated ? <Component /> : <Redirect to={{ pathname: '/' }} />;
}
add a loading state...
const [loading, setLoading] = useState(false)
after you check local storage and make axios call, update the state
if (loading) return null;
// else render the routes
return (
// the regular routes...
)
You basically want to account for a 3rd state.
Either:
there is a user
there is NO user
the effect has not yet completed
At whatever stage in your useEffect you can confirm that there is no user (unauthenticated) set the user to false.
Since undefined !== false, in your return you can test for both...
if (userData.user === undefined) return 'loading...'
Then you can use your ternary after this line with the knowledge that user has some value. Either false or some user object...

Resources