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

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.

Related

React Native, Node js authentication

What are the best libraries should I be using for node js + react-native authentication?
I was using passport local strategy with ejs templates for the login/register page because I was building a web app.
Now I am working on mobile application, I don't know how this is going to work on a mobile app.
Any ideas on how can I start this?
Thanks in advance.
The following is how I implement authentication in my react-native applications with node js backend. I use jwt tokens, where I store the user's token inside the device using expo's secure storage library. Then following the recommended auth flow by react-native-navigation documentation, whenever the app starts I check whether the token is present and return corresponding navigation stack. Refer to the code below for implementation.
LoginScreen.js/RegisterScreen.js
//...
// Get token from your backend and save it in secure storage
const userToken = request.data.token
try {
await SecureStore.setItemAsync("token", userToken);
} catch (error) {
console.warn(error);
}
// ...
App.js
export default function App() {
const [isReady, setIsReady] = useState(false)
const [userToken, setUserToken] = useState();
// Fetch token and set state
const restoreUser = async () => {
const token = await SecureStore.getItemAsync("token")
setUserToken(token)
}
if (!isReady)
// Show splash screen while checking for userToken
return (
<AppLoading
startAsync={restoreUser}
onFinish={() => setIsReady(true)}
onError={console.warn}
/>
);
return (
<Stack.Navigator>
{userToken == null ? (
// No token found, user isn't signed in
<Stack.Screen
name="SignIn"
component={SignInScreen}
options={{
title: 'Sign in',
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
) : (
// User is signed in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
);
}
On the backend take a look at jsonwebtoken npm package to see how to create jwt tokens. Goodluck!

Redirect React page to SAML login using Express backend with Passport

I currently have a functional application in Express, but I am moving to React, thus express should act as an API now. Currently I am moving the login page but I am having a problem I do not know how to solve. For the login I have the following considerations:
Using Passport to authenticate users
Using SAML (SSO) to authenticate them
To be able to authenticate through SSO, the user is redirected to the SSO page and then redirected back to the express app.
This login works with express because I can redirect through different pages. But I am unable to do this with react because I can't find a way to redirect to the SSO login page (redirecting back is done automatically by the SSO site).
This is my current saml login
router.post('/login',
passport.authenticate('saml', {
successRedirect: '/', // SUCCESS: Go to home page
failureRedirect: 'login', // FAIL: Go to /user/login
})
);
This is the form where a user should login
export class Login extends React.Component{
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(){
fetch("http://localhost:3000/auth/login",
{method : 'POST'})
.then(response => alert(response));
}
render(){
return (
<div className="flex h-screen">
<div className="container m-auto max-w-md w-full space-y-8">
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
<form className="space-y-6 p-8" onSubmit={this.handleSubmit} id="loginForm">
<div>
<button type="submit">
<span className="absolute left-0 inset-y-0 flex items-center pl-3">
Sign in
</button>
</div>
</form>
</div>
</div>
);
}
As you can see, when a user presses the button, the submit handler is called, where I can do a post request, but I don't know how to continue with the authentication.
I have tried to add a action="http://localhost:3000/auth/login" method="post" to the form. This works, it successfully redirects to the SSO login page but there there are 2 problems here.
The redirect back does not work because it is a post request (it contains user information)
The redirect should be done to the express server, since it is the one that saves the cookies, passport, authentication data and it must complete the redirect.
I am not sure this would work with a real domain, outside local host.
Any ideas?
Thanks!
In the end, to authenticate I created a public subdomain auth.domain.com and launched a window popup to the authentication page, once the page is closed authentication should have been completed.
const authLoginURL = 'http://auth.domain.com/login';
const loginWindow = window.open(
authLoginURL,
"_blank"
);
const timer = setInterval(() => {
if(loginWindow.closed) {
clearInterval(timer);
this.props.callback();
}
}, 1000);

Authenticated routes with React and PassportJS

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

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