Protected route not working correctly with React and Firebase - javascript

I'm building a small app with firebase and react and currently working on implementing the authentication. I've set the onAuthStateChanged in my app component as a side effect and whenever user is logged in it should be redirected to a desired component from ProtectedRoute.
This works correctly but unfortunately when refreshing the page the ProtectedRoute is not rendering correct component and is just firing redirection.
I get what is happening: on refresh user is empty and only after then it change so I would expect to see a screen flicker and a proper redirection.
Could you please look at below code and maybe tell me how to fix this behavior?
App component:
const App = () => {
const [authUser, setAuthUser] = useState<firebase.User | null>(null);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
authUser ? setAuthUser(authUser) : setAuthUser(null);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute exact path={ROUTES.HOME} component={Home} />
<ProtectedRoute exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</div>
</Router>
</AuthUserContext.Provider>
);
};
Protected Route:
interface Props extends RouteProps {
component?: any;
children?: any;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
...rest
}) => {
const authUser = useContext(AuthUserContext);
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};

Found the fix. Had to add the flag checking for user authentication status (default value of that flag is set to true). Flag needs to be passed to ProtectedRoute as prop and if is True then render some loading component:
App component:
const App = () => {
const [authUser, setAuthUser] = useState(false);
const [authPending, setAuthPending] = useState(true);
const Firebase = useContext(FirebaseContext);
useEffect(() => {
const authListener = Firebase!.auth.onAuthStateChanged((authUser) => {
setAuthUser(!!authUser);
setAuthPending(false);
});
return () => authListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<AuthUserContext.Provider value={authUser}>
<Router>
<div>
<Navigation />
<hr />
<Switch>
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUpPage} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route
exact
path={ROUTES.PASSWORD_FORGET}
component={PasswordForget}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.HOME}
component={Home}
/>
<ProtectedRoute
pendingAuth={authPending}
exact
path={ROUTES.ACCOUNT}
component={Account}
/>
<Route exact path={ROUTES.ACCOUNT} component={Account} />
<Route exact path={ROUTES.ADMIN} component={Admin} />
</Switch>
</div>
</Router>
</AuthUserContext.Provider>
);
};
ProtectedRoute:
interface Props extends RouteProps {
component?: any;
children?: any;
pendingAuth: boolean;
}
const ProtectedRoute: React.FC<Props> = ({
component: Component,
children,
pendingAuth,
...rest
}) => {
const authUser = useContext(AuthUserContext);
if (pendingAuth) {
return <div>Authenticating</div>;
}
return (
<Route
{...rest}
render={(routeProps) =>
!!authUser ? (
Component ? (
<Component {...routeProps} />
) : (
children
)
) : (
<Redirect
to={{
pathname: ROUTES.SIGN_IN,
state: { from: routeProps.location },
}}
/>
)
}
/>
);
};

Related

How to direct user to another page if the person not authorized?

I want to direct user to the login page if he/she is not authorized. I wrote the bottom code for this purpose. Root route is login page, and AdminPanel page is for admin. If data.success is true this means person is admin. But this code render 404 not found on http://localhost:3000/user url. How can I fix this issue?
const App = () => {
const [auth, setAuth] = useState(false);
const authControl = async () => {
try {
const res = await axios({
url: "https://localhost:44357/api/Auth/user",
withCredentials: true,
});
console.log(res.data.success);
if (res.data.success) setAuth(true);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
authControl();
}, []);
return (
<div>
<BrowserRouter>
<Route path="/" exact component={Login} />
<Route
path="/user"
render={() => {
auth ? <AdminPanel /> : <Redirect to="/" />;
}}
/>
<Route render={() => <h1>404 Not Found</h1>} />
</BrowserRouter>
</div>
);
};
you need to wrap the <Route />s in a <Switch /> from react router.
something like this:
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
</Switch>

How can I send data from one component to another without calling that component in React?

How can I send the room.id data I obtained in the Homepage.js component to the Player.js component? Before doing this, I used PrivateRouter component in App.js. Therefore, my job is getting very difficult since I cannot call the component directly while routing the route with Link.
homepage.js
<Heading as="h1" mb={6}>
Rooms
</Heading>
<Divider orientation="horizontal" />
{rooms.map((room)=> (
<ListItem>
<ListItemText primary={room.roomName} secondary={room.roomInfo}/>
{/* <Link to={`/room/${room.id}`}></Link> */}
<Link to={`/room/${room.id}`}>
<Button>
Join Room
</Button>
</Link>
</ListItem>))}
</GridItem>
app.js
function App() {
return (
<Router>
<Layout>
<Switch>
<PrivateRoute exact path="/">
<Homepage />
</PrivateRoute>
<PrivateRoute exact path="/create-room">
<CreateRoom />
</PrivateRoute>
<PrivateRoute exact path="/contribute">
<Contribute />
</PrivateRoute>
<PrivateRoute exact path="/room/:id">
<Player />
</PrivateRoute>
<Route path="/login">
<LoginForm />
</Route>
<Route path="/confirm">
<ConfirmForm />
</Route>
<Route>
<NotFound />
</Route>
</Switch>
</Layout>
</Router>
);
}
and final, player.js
class Player extends Component {
constructor(){
super();
const params = this.getHashParams();
this.state = {
logeedIn : params.access_token ? true : false,
currentStatus: false,
rooms: {
roomAdminMail: "",
roomName: "",
roomInfo: ""
},
nowPlaying: {
artist_name : 'Not Checked',
song_name: 'Not Checked',
image: ''
}
}
this.getNowPlaying = this.getNowPlaying.bind(this);
this.getRoomInfo = this.getRoomInfo.bind(this);
if(params.access_token){
spotifyWebApi.setAccessToken(params.access_token);
}
}
getRoomInfo = () => {
const db = firebase.firestore();
db.collection('rooms').doc("H5H2XjdwCyrsAboQeRxT").get()
.then((doc) => {
if (doc.exists) {
this.setState( {
rooms: {
roomAdminMail: doc.data().roomAdminMail,
roomName: doc.data().roomName,
roomInfo: doc.data().roomInfo
}
})
} else {
console.log("No such document!");
}
}
)
}
All I want is to send the room.id I use when routing links in homepage.js to the getRoomInfo function in Player.js, that is to make it available as db.collection('rooms').doc(roomId).get()
In player.js you can use this.props.match.params.id.
match.params are key/value pairs parsed from the URL corresponding to the dynamic segments of the path
More info here: https://reactrouter.com/web/api/match
Use Events. A CustomEvent is one native solution you could start with, or you can use an event bus.

In below given 2 examples they are almost similar but still one's exact keyword works but others wont why?

Example 1.
routes.js(1st file)
<ProtectedRoutes path={prop.path} component={prop.component} key={key} />
protectedRoute.js(2nd file)
<Route
{...rest}
exact
render={(props) => {
if (verified) return <Component {...props} />;
return (
<Redirect
to={{
pathname: routes.LICENCEKEY,
}}
/>
);
}}
/>
Example 2.
routes.js(1st file)
<ProtectedRoutes exact path={prop.path} component={prop.component} key={key} />
protectedRoute.js(2nd file)
<Route
{...rest}
render={(props) => {
if (verified) return <Component {...props} />;
return (
<Redirect
to={{
pathname: routes.LICENCEKEY,
}}
/>
);
}}
/>
My question is that the first example is not working but the second example is working.
The only difference is that in 1st example I'm writing exact directly inside and in 2nd example I'm passing exact to protectedRoute component and receiving and applying as rest.
Let me know if u understood my question.
EDIT
full protectedRoute.js file
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
import routes from 'constants/routes.json';
const ProtectedRoute = ({ verified, component: Component, ...rest }) => {
return (
<Route
exact
{...rest}
render={(props) => {
if (verified) return <Component {...props} />;
return (
<Redirect
to={{
pathname: routes.LICENCEKEY,
}}
/>
);
}}
/>
);
};
const mapStateToProps = (state) => {
return {
verified: state.licencekey.verified,
};
};
export default connect(mapStateToProps)(ProtectedRoute);

#reach/router nested protected route

I use reach/router with custom protected route like this :
const ProtectedRoute = ({ component: Component, ...rest }) => (
localStorage.getItem('user_id') ? <Component {...rest} /> : <Redirect from="" to="/login" noThrow />
);
const LoginRoute = ({ component: Component, ...rest }) => (
localStorage.getItem('user_id') ? <Redirect from="" to="/home" noThrow /> : <Component {...rest} />
);
const PublicRoute = ({ component: Component, ...rest }) => (
<Component {...rest} />
);
<Router>
<LoginRoute component={Login} path="/login" />
<PublicRoute default component={Notfound} />
<ProtectedRoute component={landingPage} path="/home" />
<ProtectedRoute path="/" component={App} />
<ProtectedRoute component={UserList} path="user" />
<ProtectedRoute component={UserDetail} path="user/create" />
</Router>
i want this to be nested route with user/:id
<ProtectedRoute component={UserList} path="user" />
<ProtectedRoute component={UserDetail} path="user/create" />
what should i do?
I Feel like you're complicating things
const Routes = () => {
const [loggedIn, setLoggedIn] = useState(false)
useEffect(() => {
localStorage.getItem('user_id') && setLoggedIn(true)
}, [])
return (
<Router>
<LoginRoute component={Login} path="/login" />
<Notfound default />
{
loggedIn
? (
<>
<LandingPage path="/home" />
<App path="/" component={App} />
<UserList component={UserList} path="user">
<UserDetail component={UserDetail} path="create" />
</UserList>
</>
) : (
<Redirect from="" to="/login" noThrow />
)
}
</Router>
)
}
In case this didn't work as intended or you feel you want to use it in your way, do this
<Router>
...
<ProtectedRoute component={UserList} path="user">
<UserDetail path="create" />
</ProtectedRoute>
</Router>
No need of using ProtectedRoute HOC for UserDetail since it's already nested under ProtectedRoute
and in UserList use props.children to render UserDetail

CoreUI React Re-Routing after login is not working properly

So, I just fixed my app so that after there is success logging in, the home page should display. However, after this, the rest of my routing has seemed to magically disappear. Here is the relevant code:
DefaultLayout.js
class DefaultLayout extends Component {
constructor(props){
super(props);
this.state = {
name:'',
redirect: false,
};
}
componentDidMount() {
let data = JSON.parse(sessionStorage.getItem('userData'));
console.log(data);
}
render() {
if(!sessionStorage.getItem('userData') || this.state.redirect){
return (<Redirect to={'/404'}/>)
}
return (
<div className="app">
<AppHeader fixed>
<DefaultHeader />
</AppHeader>
<div className="app-body">
<AppSidebar fixed display="lg">
<AppSidebarHeader />
<AppSidebarForm />
<AppSidebarNav navConfig={navigation} {...this.props} />
<AppSidebarFooter />
<AppSidebarMinimizer />
</AppSidebar>
<main className="main">
<AppBreadcrumb appRoutes={routes}/>
<Container fluid>
<Switch>
{routes.map((route, idx) => {
return route.component ? (<Route key={idx} path={route.path} exact={route.exact} name={route.name} render={props => (
<route.component {...props} />
)} />)
: (null);
},
)}
<Redirect from="/home" to="/dashboard" />
</Switch>
</Container>
</main>
Login.js
class Login extends Component {
constructor(props) {
super(props);
this.state = {
loginError: false,
redirect: false
};
this.signup = this.signup.bind(this);
}
signup(res, type) {
let postData;
if (type === 'google' && res.w3.U3) {
postData = {
email: res.profileObj.email,
name: res.profileObj.name,
googleId: res.profileObj.googleId,
googleAccessToken: res.Zi.access_token,
googleImageURL: res.profileObj.imageURL
};
}
if (postData) {
PostData('v1/zuulusers', postData).then((result) => {
let responseJson = result;
sessionStorage.setItem("userData", JSON.stringify(responseJson));
this.setState({redirect: true});
});
} else {}
}
render() {
if (this.state.redirect || sessionStorage.getItem('userData')) {
return (<Redirect to={'/home'}/>)
}
const responseGoogle = (response) => {
console.log("google console");
console.log(response);
this.signup(response, 'google');
}
return (
<div className="app flex-row align-items-center">
<Container>
<Row className="justify-content-center">
<Col md="5">
<CardGroup>
<Card className="text-white py-5 d-md-down-none" style={{ width: '44%' }}>
<CardBody className="text-center">
<div>
<h2>Login if you dare</h2>
<img src="https://s3.amazonaws.com/whatif-assets-cdn/images/asset_1+copy2.png" alt="zuul logo" id="zuul_logo" className="mx-auto d-block rounded img-avatar"/>
<GoogleLogin
clientId="24656564867-3issjp4bq0gsr05banuomnniilngiicc.apps.googleusercontent.com"
buttonText="Login with Google"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
</div>
App.js
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/home" name="Home Page" component={DefaultLayout} />
<Route exact path="/register" name="Register Page" component={Register} />
<Route exact path="/404" name="Page 404" component={Page404} />
<Route exact path="/500" name="Page 500" component={Page500} />
<Route exact path="/" name="login" component={Login} />
</Switch>
</BrowserRouter>
);
}
}
And then this is a small section of code to show how the routing is set up in CoreUI:
routes.js
const LoadingButtons = Loadable({
loader: () => import('./views/Buttons/LoadingButtons'),
loading: Loading,
});
const Charts = Loadable({
loader: () => import('./views/Charts'),
loading: Loading,
});
const Dashboard = Loadable({
loader: () => import('./views/Dashboard'),
loading: Loading,
});
const routes = [
{ path: '/', name: 'Login', component: Login, exact: true },
{ path: '/home', name: 'Home', component: DefaultLayout, exact: true},
{ path: '/dashboard', name: 'Dashboard', component: Dashboard },
For some reason, after the homepage loads, it doesn't load any of the other pages. They just come up blank but the path is defined in the URL. Not sure what I m doing wrong in routing.
Rather than checking in DefaultLayout which is a container for all navs, try doing so inside , since you are redirecting the routes there.
<Switch>
{routes.map((route, idx) => {
return route.component ? (
<Route
key={idx}
path={route.path}
exact={route.exact}
name={route.name}
render={props =>
sessionStorage.getItem('userData') !== null (
<route.component {...props} /> ) : (
<Redirect to={{ pathname: "/login" }} /> ) //your route here
}
/>
) : null;
})}
<Redirect from="/" to="/dashboard" />
</Switch>
Here, all routes are checked for authentication. If logged in, then user can access it else redirect to login or 404 as your preference.
#Asteriscus answer, worked to me after adding "?" in sessionStorage.getItem('userData') !== null ? and changing <Redirect from="/" to="/dashboard" /> to <Redirect from="/" to="/login" /> to ensure only authenticated user accessing dashboard

Categories

Resources