ReactJS - Authenticate with React Router 4 - reactjs

I am new to react and I am trying to create a secure area. My app's file structure is as follows:
build
node_modules
public
src
AboutScreen
AdminDash
ContactScreen
HomeScreen
LoginScreen
SignUpScreen
WorkRequestScreen
App.js
index.js
routes.js
serviceWorker.js
package.json
package-lock.json
My routing is done in App.js. This file looks like this:
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import Home from './HomeScreen/Home';
import WorkReq from './WorkRequestScreen/WorkReq';
import About from './AboutScreen/About';
import Contact from './ContactScreen/Contact';
import Login from './LoginScreen/Login';
import Signup from './SignUpScreen/Signup';
import adminDash from './AdminDash/AdminDash';
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
class App extends Component {
render() {
return (
<div>
<PrivateRoute path='/adminDash' component={adminDash} />
<Route exact path="/workReq" render={(props) => <WorkReq {...props}/>} />
<Route exact path="/about" component={About}/>
<Route exact path="/contact" component={Contact}/>
<Route exact path="/login" component={Login}/>
<Route exact path="/signup" component={Signup}/>
<Route exact path="/" component={Home}/>
</div>
);
}
}
export default App;
My Login component looks like this:
import App from '../App';
class LoginScreen extends Component {
super(props);
this.state = {
redirectToReferrer: false,
};
login = () => {
App.fakeAuth.authenticate(() => {
this.setState(() => ({
redirectToReferrer: true
}))
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer === true) {
return <Redirect to={from} />
}
return (
<div className='buttonCont'>
<button size='lg' type='button' onClick={this.login.bind(this)}>HIT IT!</button>
</div>
);
}
}
Currently this does not redirect the user to another page but will redirect them to AdminDev. The error I am getting is:
TypeError: Cannot read property 'authenticate' of undefined (login.js)
The prolem line is: App.fakeAuth.authenticate(() => {. The thing I would like to know is am I doing this right or am I missing the idea completly?
I have been following these two articles:
https://tylermcginnis.com/react-router-protected-routes-authentication/
https://auth0.com/blog/react-router-4-practical-tutorial/

You are getting this error beacause fakeAuth is not exported from App.js file. Put the fakeAuth object into a separate file i.e. auth.js and export it from there.
// auth.js
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
export { fakeAuth }
Then, import it in App.js and Login.js component like this.
import { fakeAuth } from './path-to-auth-js-file';

Related

React Router TypeError: Cannot read property 'state' of undefined

I am making a basic routing app in react and redux and i am getting an error when i want to redirect to the referrer.
Error happens in the line:
this.referrer = props.location.state.referrer || "/";
This is my Login component.
import React, { Component, createRef } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import axios from "axios";
import { updateAuthTokens, updateLoggedin } from "./redux/actions/actions";
class Login extends Component {
constructor(props) {
super(props);
this.idRef = createRef();
this.pwdRef = createRef();
this.state = {
isLoggedIn: false,
isError: false,
identifier: "",
password: "",
};
this.postLogin = this.postLogin.bind(this);
this.referrer = props.location.state.referrer || "/";
}
postLogin(e) {
e.preventDefault();
const identifier = this.idRef.current.value;
const password = this.pwdRef.current.value;
axios
.post(process.env.REACT_APP_BASE_URL + "/auth/local", {
identifier,
password,
})
.then((result) => {
if (result.status === 200) {
this.props.ul(true);
this.props.uat(result.jwt);
this.setState({ isLoggedIn: true });
} else {
this.setState({ isError: true });
}
})
.catch((e) => {
this.setState({ isError: true });
});
}
render() {
if (this.state.isLoggedIn) {
return <Redirect to={this.referrer} />;
} else {
return (
<div>
<form>
<input type="username" ref={this.idRef} placeholder="email" />
<input type="password" ref={this.pwdRef} placeholder="password" />
<button onClick={this.postLogin}>Sign In</button>
</form>
{this.state.isError && (
<p>The username or password provided were incorrect!</p>
)}
</div>
);
}
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
const mapDispatchToProps = {
ul: updateLoggedin,
uat: updateAuthTokens,
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Redirect happens from this component:
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
class PrivateRoute extends React.Component {
render() {
const { isLoggedIn, children, ...rest } = this.props;
if (isLoggedIn) {
return <Route {...rest}>{children}</Route>;
} else {
return (
<Redirect
to={{
pathname: "/login",
state: { referrer: this.props.location }
}}
/>
);
}
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
export default connect(mapStateToProps, null)(PrivateRoute);
And my routes are here:
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { connect } from "react-redux";
import Home from "./Home";
import Admin from "./Admin";
import Members from "./Members";
import Login from "./Login";
import PrivateRoute from "./PrivateRoute";
import "./App.css";
class App extends Component {
render() {
return (
<Router><div>
<ul>
<li>
<Link to="/">Home Page</Link>
</li>
<li>
<Link to="/members">Members</Link>
</li>
<li>
<Link to="/admin">Admin</Link>
</li>
</ul>
</div>
<Switch>
<Route path="/login">
<Login />
</Route>
<Route exact path="/">
<Home />
</Route>
<PrivateRoute path="/members">
<Members />
</PrivateRoute>
<PrivateRoute path="/admin">
<Admin />
</PrivateRoute>
</Switch>
</Router>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
export default connect(mapStateToProps, null)(App);
I haven't been able to get it to work and have had to comment it out which means i always end up in the home page instead of my intended location.
Any help/insight would be appreciated.
for accessing location as props change:
<Route path="/login">
<Login />
</Route>
<Route exact path="/">
<Home />
</Route>
<PrivateRoute path="/members">
<Members />
</PrivateRoute>
<PrivateRoute path="/admin">
<Admin />
</PrivateRoute>
to
<Route path="/login" component={Login} />
<Route exact path="/" component={Home} />
<PrivateRoute path="/members" component={Members} />
<PrivateRoute path="/admin" component={Admin} />
You are not going to always redirect so change:
this.referrer = props.location.state.referrer || "/";
to
this.referrer = props.location.state ? props.location.state.referrer : "/";

How to hide nav bar component in reactjs using protected route

Hi there i have created a protected route in and passing navbar as parent component. then i passing child components in protected rout i want to hide navbar in some component on some action
Here is my protected.router.js code
import React from "react";
import { Route, Redirect } from "react-router-dom";
import Navbar from './component/Layouts/Navbar';
export const ProtectedRoute = ({
component: Component,
...rest
}) => {
return (
<Route
{...rest}
render={props => {
if (localStorage.getItem("loggedAsli") === "1") {
return <div> <Navbar/>
<Component {...props} /> </div>;
} else {
return (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
);
}
}}
/>
);
};
And this my App.js code
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import { ProtectedRoute } from "./protected.route";
import Learn from "./component/Learn/Learn";
class App extends React.Component {
constructor(props) {
super();
this.updateUserLoginSetting = this.updateUserLoginSetting.bind(this);
this.state = {
userId: "",
instituteId: ""
};
}
updateUserLoginSetting = (userId, instituteId) => {
this.setState({ userId: userId, instituteId: instituteId });
};
render() {
return (
<BrowserRouter>
<div className="App">
<Route render={props => <Login />} exact path="/login" />
<ProtectedRoute exact path="/Learn" component={Learn} />
</div>
</BrowserRouter>
);
}
}
export default App;
How i can hide navbar in Learn Component
Please guide me.
is there any global state handling
You could just pass a prop (e.g. showNav) and use it inside ProtectedRoute
export const ProtectedRoute = ({
component: Component,
showNav = true, // added showNav prop
...rest
}) => {
return (
<Route
{...rest}
render={props => { // check if should showNav
if (localStorage.getItem("loggedAsli") === "1" && showNav) {
return <div> <Navbar/>
<Component {...props} /> </div>;
} else {
return (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
);
}
}}
/>
);
};
And pass false when it's Learn
// pass showNav as false to hide nav bar
<ProtectedRoute showNav={false} exact path="/Learn" component={Learn} />

Updating redux state in react app.js file Authentication

import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import { library } from "#fortawesome/fontawesome-svg-core";
import {
faHome,
faClock,
faTasks,
faStickyNote,
faCalendarWeek
} from "#fortawesome/free-solid-svg-icons";
import { connect } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/authActions";
import Home from "./Home";
import SideNav from "./Components/SideNav";
import Recent from "./Components/Recent";
import TopBar from "./Components/TopBar";
import AddNote from "./AddNote";
import LogIn from "./Components/LogIn/LogIn.js";
import Register from "./Components/Register/Register";
import ToDo from "./Components/ToDo/ToDo";
import { timingSafeEqual } from "crypto";
library.add(faHome, faClock, faTasks, faStickyNote, faCalendarWeek);
class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
}
componentDidUpdate(prevProps) {
if(this.props != this.prevProps) {
console.log("hello")
}
}
LogInContainer = () => {
return <Route path="/login" component={LogIn} />;
};
RegisterContainer = () => {
return <Route path="/register" component={Register} />;
};
DefaultContainer = () => {
return (
<div className="app_container">
<SideNav />
<TopBar />
<Route exact path="/" component={Home} />
<Route path="/recent" component={Recent} />
<Route path="/AddNote" component={AddNote} />
<Route path="/ToDo" component={ToDo} />
</div>
);
};
// Check for authenticaition
AuthRoute = ({ component: Component, props, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (this.props.auth.isAuthenticated) {
return <Component {...props} />;
}
else {
return (
<Redirect
to={{
pathname: "/login",
state: { from: this.props.location }
}}
/>
);
}
}}
/>
);
};
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/login" component={this.LogInContainer} />
<Route exact path="/register" component={this.RegisterContainer} />
<this.AuthRoute component={this.DefaultContainer} />
</Switch>
</BrowserRouter>
);
}
}
const mapStateToProps = (state) => ({
auth: state.auth
})
export default connect(mapStateToProps)(App);
How can app.js receive the new state from redux after logging in? The initial fetch it will get isAuthenticated = false. User then log ins but app.js isn't getting the new state. Am I implementing authenitcation wrong? comonentDidUpdate is throwing an error when trying to update props but feel like this is a bad way of doing it anyways

How to restrict access to routes in react-router-dom based on session?

I have a React front-end and Python back-end with user session management using flask-login's HttpOnly Session Cookies. How can I restrict react-router-dom routes based on this type of session management? I've created a ProtectedRoute component:
import { Route, Redirect } from 'react-router-dom';
class ProtectedRoute extends Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
}
}
render() {
const { component: Component, ...props } = this.props
return (
<Route
{...props}
render={props => (
this.state.authenticated ?
<Component {...props} /> :
<Redirect to='/login' />
)}
/>
)
}
}
export default ProtectedRoute;
Is it possible to set this.setState({authenticated: true}) based on the existing session?
Why not pass authenticated (or isEnabledin my example) as a prop? ProtectedRoute will rerender when its props change. This is what I use in my React applications:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const ProtectedRoute = ({isEnabled, ...props}) => {
return (isEnabled) ? <Route {...props} /> : <Redirect to="/login"/>;
};
export default ProtectedRoute;
And then you can just use this like so:
<ProtectedRoute path="/dashboard" isEnabled={isAuthenticated()} />
I know this question is old but I was looking for the same thing. Here's my routes.js file:
import auth from './services/auth'
const PrivateRoute = ({isAuthenticated, ...props}) => {
return (isAuthenticated) ? <Route {...props} /> : <Redirect to="/login"/>;
};
class Routes extends React.Component {
constructor(){
super();
this.state = {
isAuthenticated: false
}
}
componentDidMount(){
auth.get('')
.then( async (response) => {
const status = await response.status
if (status === 200) {
this.setState({isAuthenticated: true})
} else {
this.setState({isAuthenticated: false})
}
})
.catch( async (error) => console.log(error))
}
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/login" exact component={Login} />
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/" component={() => "barra"}/>
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/home" component={() => "home"}/>
<PrivateRoute isAuthenticated={this.state.isAuthenticated} path="/profile" component={() => "profile"}/>
</Switch>
</BrowserRouter>
)
};
}
And auth import is:
const axios = require('axios');
axios.defaults.withCredentials = true;
const auth = axios.create({
baseURL: "http://localhost:5000/auth"
})
export default auth;
So, basically I have a Flask app with Flask-Login running on another local server (with CORS enabled THIS IS VERY IMPORTANT) and if 200 is returned, user on react is authenticated.

Redirect to the same page that user tried to access before login - Reactjs

When a user tries to view a private page(/products/1/edit), he will be redirected to the login component.
After login i want to redirect the user to the same product edit page. **The problem is that I am unable to get the location props in the component. It is returning as undefined.
** The code is as follows.
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<AppRouter />
</div>
);
}
}
export default App;
AppRouter.js
import { ConnectedRouter } from 'react-router-redux';
...
class AppRouter extends Component {
componentDidMount() {
this.props.checkAlreadyLoggedIn();
}
render() {
const { token, location } = this.props;
return ((
<ConnectedRouter history={history}>
<Layout style={{ minHeight: '100vh' }}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" render={ () => token && <Redirect to=
{
location.state && location.state.from ? location.state.from.pathname :
"/dashboard"
} /> || <Login />} />
<PrivateRoute path="/dashboard" component={Dashboard} />
...
</Switch>
</Layout>
</Layout>
</ConnectedRouter>
));
}
}
history.js
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
export default history;
i am already defined the private route as follows
class PrivateRoute extends PureComponent {
static propTypes = {
token: PropTypes.string,
component: PropTypes.any
}
render() {
const { component: Component, ...rest } = this.props;
let token = utils.getUserToken();
return (
<Route
{...rest}
render={ () =>
(token && <Component {...this.props} />) ||
<Redirect to={{
pathname: '/login',
state: { from: this.props.location }
}}/>
}
/>
)
}
}
Only if i get the location.state.from.pathname, i will be able to redirect the user to the page that he tried to access prior to login.
Any idea on how to fix this?
This is the PrivateRoute implementation I use and it performs redirect:
import React from 'react';
import { Route, Redirect } from "react-router-dom";
import { isLoggedIn } from '../../utils/authHelper';
class PrivateRoute extends React.Component {
renderComponent = () => {
const { path, component: Component } = this.props;
return (
isLoggedIn()
? <Component {...this.props} />
: <Redirect to={{
pathname: '/login',
state: { from: path }
}} />
)
}
render (){
const { component: Component, ...rest } = this.props;
return (
<Route {...rest} render={this.renderComponent} />
)
}
}
export default PrivateRoute;
And usage is like:
<Switch>
<Route path='/login' component={LoginScreen} />
<PrivateRoute path='/profile' component={ProfileComponent} />
</Switch>
And on /login route you will get the original url via this.props.location.state.from:
...
doLogin(username, password)
.then(() => {
window.location.href = `/${this.props.location.state.from}`;
});
Using react hooks useHistory() method history.goBack()
...
login(userData);
history.goBack();

Resources