Authenticated routes with React and PassportJS - node.js

I am using the following stack :
React
React router v4
PassportJS
NodeJS backend with Express and
Express session
I have successfully setup PassportJS based login and registration authentication. All pages in my app are protected routes - they can only be viewed by a logged in user.
So, my question is, for each route, how do I check if the user is currently logged in or not. I understand that express session provides server-side session management, but I'm wondering if there's a way to avoid making an API request to the backend on each page load to verify if the session of the current user exists.
My App.js file:
import React, { Component } from 'react';
import './App.css';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import AsyncAuthPage from 'components/AsyncAuthPage/index.js'
const NoMatch = () => (
<p>Path not found</p>
);
const HomePage = () => (
<div>WELCOME!</div>
);
class App extends Component {
render() {
return (
<div className="App ">
<BrowserRouter>
<Switch>
<Route path="/login" component = {AsyncAuthPage} />
<Route path="/home" component = {HomePage} />
<Route path="*" component={NoMatch} />
</Switch>
</BrowserRouter>
</div>
);
}
}
export default App;
The AsyncAuthPage component implements PassportJS based authentication. In the above sample, I would like to protect Homepage route with authentication. How can this be done? After a user has successfully logged in, the following needs to be taken care of :
The parent App.js needs to know that login was successful
All pages should try to avoid making an API call to the backend (on
componentDidMount or page load) as much as possible, to verify if current user is logged in.
Should work on page reload too

Related

Creating admin user in MERN

I want to create an admin user in my MERN application, I created a user model on back-end and put isAdmin there. Now I want to protect some routes on back-end. On front-end I need to protect normal user to access admin panel, which is accessible when click on NavLink.
User schema:
Signup controller:
Front end routes:
you can use switch route of react routes like that.
import React from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Route,
browserHistory,
Switch
} from 'react-router-dom';
import App from './app/App';
import Welcome from './app/Welcome';
import NotFound from './app/NotFound';
const isAdmin = false; // flag of current user login
const Root = () => (
<Router history={browserHistory}>
<Switch>
{{isAdmin ? <Route exact path="/admin" component={Admin}/> : null }}
{{isAdmin ? <Route exact path="/admin/new" component={AdminNew}/> : null }}
<Route component={NotFound}/>
</Switch>
</Router>
);
ReactDOM.render(
<Root/>,
document.getElementById('root')
);

What's wrong with my routing in my react application?

I have a React application that I just deployed to my server and now the routing isn't working as expected. I need some help figuring it out.
When running locally, this works:
<button className="welcome-buttons"
onClick={() => window.location.href = 'http://localhost:3000/services'}>
Read More <i className="fas fa-arrow-alt-circle-right"></i>
</button>
I would click on the button and it would route to http://localhost:3000/services. Then when I was ready to deploy, I changed this to http://www.assertivesolutions.ca/services, created a build and uploaded it to my server. I would go to my site (http://www.assertivesolutions.ca) and it would load fine, but when I clicked on the button, I get taken to a page that says Cannot GET /services.
I'm not sure why this is. Maybe I can't just replace localhost:3000 with www.assertivesolutions.ca in the code. But what is the right way to do it?
This is what I have in app.js
import React, { Component } from 'react';
import './App.scss';
import './Home.scss';
import Header from './Header/Header';
import Banner from './Banner/Banner';
import Welcome from './Welcome/Welcome';
import MainFocus from './MainFocus/MainFocus';
import WhatWeDo from './WhatWeDo/WhatWeDo';
import OurBlog from './OurBlog/OurBlog';
import OurClients from './OurClients/OurClients';
import ContactUs from './ContactUs/ContactUs';
import Footer from './Footer/Footer';
import smoothscroll from 'smoothscroll-polyfill';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Blog from './Blog/blog';
import OurServices from './OurServices/OurServices';
import SideMenu from './SideMenu/SideMenu';
class App extends Component {
render() {
smoothscroll.polyfill();
return (
<Router>
<Switch>
<Route path="/" exact>
<div className="app-master-container">
<SideMenu pageWrapId={'page-wrap'} outerContainerId={'outer-container'} />
<div className="header"><Header /></div>
<Banner />
<Welcome />
<MainFocus />
<WhatWeDo />
<OurBlog />
<OurClients />
<ContactUs />
<Footer />
</div>
</Route>
<Route path="/blog">
<Blog/>
</Route>
<Route path="/services">
<OurServices/>
</Route>
</Switch>
</Router>
);
}
}
export default App;
On the server, I'm running node and bouncy to handle the routing to each website I host, like this:
const bouncy = require('bouncy');
const express = require('express');
const path = require('path');
const { create, engine } = require('express-handlebars');
bouncy(function(req, bounce) {
const host = req.headers.host;
console.log(`host=${host}`);
if (host === 'shahspace.com' || host === 'www.shahspace.com') {
if (req.url.includes('/music%20mixes/')) bounce(8002);
else bounce(8000);
}
if (host === 'assertivesolutions.ca' || host === 'www.assertivesolutions.ca') bounce(8001);
if (host === 'fmshahdesign.com' || host === 'www.fmshahdesign.ca') bounce(8003);
}).listen(80);
const fmsApp = express();
fmsApp
.use(express.static(path.join(__dirname, 'fmshahdesign.com')))
.listen(8003);
const assertSolApp = express();
assertSolApp
.use(express.static(path.join(__dirname, 'assertivesolutions.ca')))
.listen(8001);
const musicMixApp = express();
musicMixApp.engine('.hbs', engine({
extname: 'hbs',
defaultLayout: false
}));
musicMixApp.set('view engine', 'hbs');
musicMixApp.set('views', path.join(__dirname, 'shahspace.com/music mixes/views'));
/****** more code for handling music mix app ********/
In other words, all requests come in on port 80 where the node router is listen, and then it checks the host to see which website is being requested. That's where bouncy comes in. It bounces the request to the appropriate port corresponding to the requested website. assertivesolutions.ca is on port 8001 so it bounces it there. Each site has an app created with express which handles the request on the appropriate port.
This works as long as I go to www.assertivesolutions.ca but as soon as I go to www.assertivesolutions.ca/services, it get the Cannot GET /services message. I would create a services folder in the assertivesolutions.ca folder (where the assertSolApp directs requests to) but the contents were created by the build (which I would think should be able to handle requests for /services) so I don't think I should mess with that.
Can anyone see the problem? Thanks.
If you're not sure about your url . Instead of Redirect to Another Webpage with vanilla javascript you can use react router (which you're already using and it seems version 5 ) .
Just add it to your component like this :
in functional components :
import { useHistory } from "react-router-dom";
..............
let history = useHistory();
<button className="welcome-buttons"
onClick={() => history.push("/services")}>
Read More <i className="fas fa-arrow-alt-circle-right"></i>
</button>
in class components :
You can get access to the history object’s properties via the withRouter which is a higher-order component. you can do something like this :
import { withRouter } from "react-router-dom";
class Example extends React.Component {
render() {
return (
<div>
.........
<button className="welcome-buttons"
onClick={() => this.props.history.push("/services")}>
Read More <i className="fas fa-arrow-alt-
circle-right"></i>
</button>
</div>
);
}
}
export default withRouter(Example);
this is a simple codesandbox showing how o implement withRouter
You need to write the .htaccess file in the server root directory.
Check this link

How to stop firebase re-auth() on every page reload in a react app?

So i have this great react app using firebase auth and firestore.
Everything working fine except
Whenever i reload the page while a user is already logged in... navbar links change for a second.
Looks like app automatically re-login(re-auth) the user on every page reload. Why so? How to get rid of it? Some look-alike code sample
import React, {useState, useEffect} from 'react';
import {Switch, Route} from 'react-router-dom'
import firebase from 'firebase/App'
export const App = () => {
const [isAuth, setIsAuth] = useState()
const auth = firebase.auth()
useEffect(() => {
auth.onAuthStateChanged(user => {
if(user) {
setIsAuth(true)
} else {
setIsAuth(false)
}
})
}, [isAuth])
return(
<div className="App">
<Navbar />
<Switch>
<Route to="/signIn" component={Login} />
<Route to="/signUp" component={SignUp} />
<Route to="/signOut" component={SignOut} />
</Switch>
</div>
)
};
Finally fixed it.
Reason it was happening bcoz firebase servers were verifying the user on each page reload which took some time and cause flickering in navbar for half a second.
Solution has three easy steps
Once logged in, store the user as boolean on local storage.
firebase.auth().onAuthStateChanged(user=>{
if (user) {
// store the user on local storage
localStorage.setItem('user', true);
} else {
// removes the user from local storage on logOut
localStorage.removeItem('user');
}
})
Check The user from local storage in navbar component
const userLocal = JSON.parse(localStorage.getItem('user'));
userLocal ? <SignedInLinks/> : <SignedOutLinks/>;
Remove user from local storage on logout
#illiterate.farmer You are almost right. Actually you should only save a flag, isSignedIn=true or false in the localStorage because saving the full user object makes your app vulnerable to hacking very easily.
Any javascript function can access the localStorage and thus it will expose you tokens that can be used to impersonate as a genuine user to your backend system.
I was having this problem too and I think Firebase's intended way of doing this is to use the FirebaseAuthConsumer... providerId is null when awaiting auth status.
Compare this sandbox where the "not signed in" content is rendered for a split second before the signed in content, with this one where no render happens until Firebase has told us whether or not the user is signed in. Will need to press the "Sign in" button on first load and then refresh to test behaviour.

Fetching API from react sending me wrong URL

Learning React.js and Node.js and making a simple crud app with Express API on the back-end and React.js on the front end.
App.js of my React.js looks like this.
`import React, { Component } from 'react';
import './App.css';
import Rentals from './components/Rentals';
import Idpage from './components/Idpage';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
class App extends Component {
render() {
return (
<div className="mainappdiv">
<Router>
<main>
<Route exact path="/" component={Home} />
<Route exact path="/rentals" component={Rentals} />
<Route path="/rentals/:propertyid" component={Idpage} />
</main>
</div>
</Router>
</div>
);
}}
export default App;
I am making an app that when if you go to /rentals, it will fetch the data and print stuff. This is currently working and all the data from my database is rendering.
Now I am trying to go to /rentals/1 or /rentals/2 then trying to print only listings of that id.
import React, { Component } from 'react';
class Idpage extends Component {
componentDidMount() {
fetch('api/listofrentals/2')
.then((response)=>{
console.log(response)
return response.json()
})
.then((singlerent)=>{
console.log(singlerent)
})
}
render() {
return (
<div>
<p>this is the id page solo</p>
<p>{this.props.match.params.propertyid}</p>
</div>
);
}}
export default Idpage;
When I do this, I get an error saying GET http://localhost:3000/rentals/api/listofrentals/2 404 (Not Found)
I am trying to fetch from the URL http://localhost:3000/api/listofrentals/2 and do not understand why the "rentals" part is in the url.
My React server is running on localhost:3000 and node.js is running on localhost:30001. And my React's package.json has this "proxy": "http://localhost:3001/"
Fetch by default will access a relative path to where you are using it. You can specify you want to bypass the relative path by starting your url with /.
fetch('/api/listofrentals/2')
In case if you want to change the base url for testing. You can turn off web security in Google and use.
In ubuntu command line it is
google-chrome --disable-web-security --user-data-dir

Testing React Router with Jest and Enzyme

My goal is to test my React Router Route exports in my app and test if it is loading the correct component, page, etc.
I have a routes.js file that looks like:
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import { App, Home, Create } from 'pages';
export default (
<Route path="/" component="isAuthenticated(App)">
<IndexRoute component={Home} />
<Route path="create" component={Create} />
{/* ... */}
</Route>
);
Note: isAuthenticated(App) is defined elsewhere, and omitted.
And from what I've read, and understood, I can test it as such:
import React from 'react';
import { shallow } from 'enzyme';
import { Route } from 'react-router';
import { App, Home } from 'pages';
import Routes from './routes';
describe('Routes', () => {
it('resolves the index route correctly', () => {
const wrapper = shallow(Routes);
const pathMap = wrapper.find(Route).reduce((pathMapp, route) => {
const routeProps = route.props();
pathMapp[routeProps.path] = routeProps.component;
return pathMapp;
}, {});
expect(pathMap['/']).toBe(Home);
});
});
However, running this test results in:
Invariant Violation: <Route> elements are for router configuration only and should not be rendered
I think I understand that the issue might be my use of Enzyme's shallow method. I took this solutions from this SO question. I believe I understand that it is attempting to parse through the wrapper in search of a Route call, putting each into a hashtable and using that to determine if the correct component is in the table where it should be, but this is not working.
I've looked through a lot of documentation, Q&A, and blog posts trying to find "the right way" to test my routes, but I don't feel I'm getting anywhere. Am I way off base in my approach?
React: 15.4.2
React Router: 3.0.2
Enzyme: 2.7.1
Node: 6.11.0
You can't directly render Routes, but a component with Router that uses routes inside. The answer you pointed to has the complete solution.
You will probably also need to change the browserHistory under test environment to use a different history that works on node. Old v3 docs:
https://github.com/ReactTraining/react-router/blob/v3/docs/guides/Histories.md
As a side note, what's the point of testing Route which I assume is already tested in the library itself? Perhaps your test should focus on your route components: do they render what they should based on route params? Those params you can easily mock in your tests because they're just props.
I'm telling you this because in my experience understanding what to test was as important as learning how to test. Hope it helps :)

Resources