I'm trying to learn react, and am setting up routes in my application which require you to log in. I'm trying to adapt the example given here
The code I've written should either redirect the user or display the protected route. But when I log in I'm still being redirected.
I believe the issue is in my PrivateRoute class below. I pass it a authenticated property which is set in the parent class, but it doesn't appear to update.
In app.js we declare the authenticator, where we perform async login with our backend.
I pass the checkLoggedIn function to the login component, where we set the parent's authenticated state property to true. I'm console.log()ing the state just to check it's occurring, which it is.
When I then click the Link to /protected route I'm still being redirected.
app.js
// imports ...
let authenticator = new Authenticator();
class ProtectedComponent extends Component {
render() {
return (
<h1>Protected!</h1>
);
}
}
class App extends Component {
constructor(props){
super(props);
this.state = {
authenticator: authenticator,
authenticated: authenticator.isLoggedIn(),
}
}
checkLoggedIn() {
this.setState({authenticated: true});
console.log(this.state);
}
render() {
let routes, links = null;
links = <div className="links">
<Link to="/login">Login</Link>
<Link to="/protected">Protected</Link>
</div>;
routes = <div className="routes">
<Route
path="/login"
render={() =>
<Login
authenticator={this.state.authenticator}
loginCallback={this.checkLoggedIn} />
}
/>
<PrivateRoute
path="/protected"
component={ProtectedComponent}
authenticated={this.state.authenticated}
/>
</div>;
return (
<Router className="App">
{links}
{routes}
</Router>
);
}
}
export default App;
PrivateRoute.js
// imports ....
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route {...rest} render={props =>
authenticated === true
? (<Component {...props} />)
: (<Redirect to={{
pathname: "/login",
state: { from: props.location }
}} />
)
}/>
);
export default PrivateRoute;
Login.js
// imports ...
class Login extends Component {
constructor(props) {
super(props);
this.authenticator = props.authenticator;
this.loginCallback = props.loginCallback;
this.state = {
identifier: "",
password: "",
}
}
updateState = (e, keyName = null) => {
this.setState({[keyName]: e.target.value})
}
attemptLogin = (e) => {
this.authenticator.loginPromise(this.state.identifier, this.state.password)
.then(resp => {
if(resp.data.success === true) {
this.authenticator.setToken(resp.data.api_token);
this.loginCallback();
} else {
this.authenticator.removeToken()
}
})
.catch(err => {
console.error(err);
});
}
render(){
<button onClick={this.attemptLogin}> Log In </button>
}
}
export default Login;
I'm setting the authenticated state to true in the callback method, but when I go to the protected route (and run it's render method) it appears to be evaluating to false.
If I'm misunderstanding the react props system, let me know. If you'd like to see any more of the code let me know and I'll amend the question.
You have to create a PrivateRoute HOC component first:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('bpm-user')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
and wrap your routes that most be protected:
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/new/index" component={NewIndex} />
<PrivateRoute path="/jobs/index" component={JobsIndex} />
<PrivateRoute path="/unions/index" component={UnionsIndex} />
<PrivateRoute exact path="/" component={ListIndex} />
<PrivateRoute exact path="/charges" component={MunicipalCharges} />
</Switch>
and use Link
<Link to="/jobs/index">Jobs</Link>
my login reducer
import axios from 'axios';
import * as actionTypes from './AuthActionTypes';
export const login = (user) => {
return dispatch => {
// for example => dispatch({type:actionTypes.REQUEST_LOGIN_USER});
axios({
method: 'post',
url: '/api/auth/login',
data: { 'username': user.username, 'password': user.password },
headers: { 'Content-Type': 'application/json;charset=utf-8' }
})
.then(res => {
localStorage.setItem('bpm-user', JSON.stringify(res.data));
dispatch({
type: actionTypes.LOGIN_USER,
payload: res.data
})
})
.catch(error => {
// TODO... for example => dispatch({type:actionTypes.FAILD_LOGIN_USER, payload:error});
})
}
}
export const logout = () => {
localStorage.removeItem('bpm-user');
}
like the example codes that i copied from my own project
Related
Here, is my full code details:
route.js
<AdminRoute path="/dashboard" component={Dashboard} />
And, my admin route:
AdminRoute
import React from 'react'
import { Route } from 'react-router-dom'
import AdminLayout from '../components/layouts/AdminLayout'
const AdminRoute = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => (
<AdminLayout>
<Component {...props} />
</AdminLayout>
)} />
)
}
export default AdminRoute
and this is the AdminLayout,
and here it is hitting that console.log correctly, but not redirecting.
AdminLayout/
import React from 'react';
import { Redirect } from "react-router-dom";
import { isAuthenticated } from '../../utils/auth';
const AdminLayout = ({ children, ...rest }) => {
const isLoggedIn = isAuthenticated();
console.log("isLoggedIn >>",isLoggedIn);
if(!isLoggedIn){
//ITS HITTING THIS CONSOLE LOG
//BUT ITS NOT REDIRECTING
console.log('hit');
<Redirect
to={{
pathname: "/login",
state: { from: children.props.location },
}}
/>
}
return (
<div>{children}</div>
);
}
and this is the helper function to determine either the user is logged in or not.
Auth.js
export function isAuthenticated(){
if(localStorage.getItem("_token")){
return true;
}
return;
}
const auth = {
isAuthenticated,
};
export default auth;
The Redirect component needs to be returned in order to be rendered, and, ultimately, trigger a redirect, i.e.
const AdminLayout = ({ children, ...rest }) => {
const isLoggedIn = isAuthenticated();
if (!isLoggedIn){
return <Redirect
to={{
pathname: "/login",
state: { from: children.props.location },
}}
/>
}
return (
<div>{children}</div>
);
}
See the below function i am creating the Auth routes and getting the children undefined and shows blank page. In App.js i am using the private route as you can see below and when i use simple Route instead of PrivateRoute its shows the Login component
<PrivateRoute exact path="/" name="Login" render={props => <Login {...props}/>} />
Here is is My PrivateRoute.js. When i console the children its shows undefined
function PrivateRoute({ children, ...rest }) {
const token = cookie.get('token');
return (
<Route
{...rest}
render={({ location }) =>
!token ? (
children
) : (
<Redirect
to={{
pathname: "/dashboard",
state: { from: location }
}}
/>
)
}
/>
);
}
export default Private Route;
I usually use something like it. Let me know if it works for you.
Here is my privateRoute.js component:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthenticated } from 'auth';
export default function PrivateRoute({ component: Component, ...rest }) {
return (
<Route {...rest} render={
props => (
isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
)} />
);
}
Here I have another file in the root auth.js:
export const isAuthenticated = () => {
return Boolean(localStorage.getItem('jwttoken'));
}
export const login = ({email, password}) => {
// Logic
localStorage.setItem('jwttoken', 'jkdsalkfj');
return true;
}
export const logout = () => {
localStorage.removeItem('jwttoken');
}
You can call it like:
<PrivateRoute
component={ProjectPage}
path="/project/:id"
/>
Children refer to what's inside the tag
<PrivateRoute>
<PrivateComponent/>
</PrivateRouter>
Here your children would be <PrivateComponent/>
You're not passing anything inside your PrivateRoute in your example. So you'll have undefined
I want to make a PrivateRoute in React with an async LoginCheck. I have tried this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: false
}
}
componentDidMount() {
//ASYNC FUNCTION
setTimeout(() => {
this.setState({loggedIn: true});
}, 1000)
}
render() {
console.log("RENDERED", this.state.loggedIn);
const PrivateRoute = ({component: Component, ...rest}) => (
<Route {...rest} render={(props) => (
this.state.loggedIn === true
? <Component {...props} />
: <Redirect to="/login" />
)}/>
)
return(
<Router>
<Switch>
<Route path="/" exact component={Home} />
<PrivateRoute path="/private" component={PrivatePage} />
</Switch>
</Router>
)
}
}
But that is not working.
In the console it shows first "RENDERED false" and after a second "RENDERED true". So it is rendered with the right parameters but in the const PrivateRoute he is redirecting to the login page so loggedIn is false.
Why? What can I do?
You can create a function checkAuth() to seperate the validation as follow
import React from "react";
import {
BrowserRouter as Router,
Switch,
Redirect,
Route,
} from "react-router-dom";
import Home from "./Home";
import PrivatePage from "./Private";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: true
}
}
componentDidMount() {
//ASYNC FUNCTION
setTimeout(() => {
this.setState({ loggedIn: false });
}, 1000)
}
render() {
console.log("RENDERED", this.state.loggedIn);
const checkAuth = () => {
try {
// To be replaced with your condition
if (this.state.loggedIn === true) {
return true;
}
} catch (e) {
return false;
}
return false;
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
checkAuth() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/' }} />
)
)} />
)
return (
<Router>
<Switch>
<Route path="/" exact component={Home} />
<PrivateRoute path="/private" component={PrivatePage} />
</Switch>
</Router>
)
}
}
Visit the url "http://localhost/private" as loggedIn: true this route is authorized but after 0.2s the state of loggedIn will be update it false and the protected route will be redirected to the home page.
I'm trying to write Authentication checking for my DashBoard. But the function itself is not getting called. Can anyone give me some solution for this? I'm developing in ReactJs.
This is the Route part :
<Router>
<div>
<Route exact path={"/"} component={Home} />
<Route path={"/SignUp"} component={SignUp} />
<Route path={"/SignIn"} component={SignIn} />
<Route path={"/Dashboard"} component={Dashboard} onEnter={this.requireAuth} />
</div>
</Router>
This is the function :
requireAuth (nextState, replace) {
console.log("?????????????",this.state.getToken);
if(!this.state.getToken) {
replace({pathname: '/'});
}
}
In react-router v4, you can make use of render prop to Route along with the lifecycle methods to replace the onEnter functionality existing in react-router v3.
See this answer for more details:
onEnter prop in react-router v4
However since all you want to do is authentication in the onEnter prop, you could easily create a HOC that does that
const RequireAuth = (Component) => {
return class App extends Component {
componentWillMount() {
const getToken = localStorage.getItem('token');
if(!getToken) {
this.props.history.replace({pathname: '/'});
}
}
render() {
return <Component {...this.props} />
}
}
}
export { RequireAuth }
and use it like
<Route path={"/Dashboard"} component={RequireAuth(Dashboard)}/>
Edit: In case you need to make a network call to find if the use if authenticated of not, you would write the HOC like
const RequireAuth = (Component) => {
return class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
const { isAuthenticated, isLoading } = this.state;
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
Update:
In addition to the HOC, you can also go for the PrivateRoute component like
const PrivateRoute = ({component: Component, isAuthenticated, isLoading, ...rest }) => {
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { PrivateRoute };
and you can use it like
class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
<Router>
<div>
<Route exact path={"/"} component={Home} />
<Route path={"/SignUp"} component={SignUp} />
<Route path={"/SignIn"} component={SignIn} />
<PrivateRoute path={"/Dashboard"} component={Dashboard} isAuthenticated={this.state.isAuthenticated} isLoading={this.isLoading}/>
</div>
</Router>
}
}
I am writing a React.js application (v15.3) using react-router (v2.8.1) and ES6 syntax. I cannot get the router code to intercept all transitions between pages to check if the user needs to login first.
My top level render method is very simple (the app is trivial as well):
render()
{
return (
<Router history={hashHistory}>
<Route path="/" component={AppMain}>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
<Route path="subject" component={SubjectPanel}/>
<Route path="all" component={NotesPanel}/>
</Route>
</Router>
);
}
All the samples on the web use ES5 code or older versions of react-router (older than version 2), and my various attempts with mixins (deprecated) and willTransitionTo (never gets called) have failed.
How can I set up a global 'interceptor function' to force users to authenticate before landing on the page they request?
Every route has an onEnter hook which is called before the route transition happens. Handle the onEnter hook with a custom requireAuth function.
<Route path="/search" component={Search} onEnter={requireAuth} />
A sample requireAuth is shown below. If the user is authenticated, transition via next(). Else replace the pathname with /login and transition via next(). The login is also passed the current pathname so that after login completes, the user is redirected to the path originally requested for.
function requireAuth(nextState, replace, next) {
if (!authenticated) {
replace({
pathname: "/login",
state: {nextPathname: nextState.location.pathname}
});
}
next();
}
In v4 you just create a route component that checks if uses is authenticated and return the next components and of course next component can be other routes.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Route, Redirect } from 'react-router-dom';
import AuthMiddleware from 'modules/middlewares/AuthMiddleware';
class PrivateRoute extends Component {
static propTypes = {
component: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
isLoggedIn: PropTypes.func.isRequired,
isError: PropTypes.bool.isRequired
};
static defaultProps = {
isAuthenticated: false
};
constructor(props) {
super(props);
if (!props.isAuthenticated) {
setTimeout(() => {
props.isLoggedIn();
}, 5);
}
}
componentWillMount() {
if (this.props.isAuthenticated) {
console.log('authenticated');
} else {
console.log('not authenticated');
}
}
componentWillUnmount() {}
render() {
const { isAuthenticated, component, isError, ...rest } = this.props;
if (isAuthenticated !== null) {
return (
<Route
{...rest}
render={props => (
isAuthenticated ? (
React.createElement(component, props)
) : (
<Redirect
to={{
pathname: isError ? '/login' : '/welcome',
state: { from: props.location }
}}
/>
)
)}
/>
);
} return null;
}
}
const mapStateToProps = (state) => {
return {
isAuthenticated: state.auth.isAuthenticated,
isError: state.auth.isError
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
isLoggedIn: () => AuthMiddleware.isLoggedIn()
}, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(PrivateRoute);
This version of the onEnter callback finally worked for react-router (v2.8):
requireAuth(nextState,
replace)
{
if(!this.authenticated()) // pseudocode - SYNCHRONOUS function (cannot be async without extra callback parameter to this function)
replace('/login')
}
The link which explains react-router redirection differences between V1 vs v2 is here. Relevant section quoted below:
Likewise, redirecting from an onEnter hook now also uses a location descriptor.
// v1.0.x
(nextState, replaceState) => replaceState(null, '/foo')
(nextState, replaceState) => replaceState(null, '/foo', { the: 'query' })
// v2.0.0
(nextState, replace) => replace('/foo')
(nextState, replace) => replace({ pathname: '/foo', query: { the: 'query' } })
Full Code Listing Below (react-router version 2.8.1):
requireAuth(nextState,
replace)
{
if(!this.authenticated()) // pseudocode - SYNCHRONOUS function (cannot be async without extra callback parameter to this function)
replace('/login');
}
render() {
return (
<Router history={hashHistory}>
<Route path="/" component={AppMain}>
<Route path="login" component={Login}/>
<Route path="logout" component={Logout}/>
<Route path="subject" component={SubjectPanel} onEnter={this.requireAuth}/>
<Route path="all" component={NotesPanel} onEnter={this.requireAuth}/>
</Route>
</Router>
);
}
If you're using react-router 4 and above, use Render props and redirect to solve this. Refer: onEnter not called in React-Router
This is not a safe solution
You can try n use useEffect hook on every page requiring login as:
useEffect(() => {
const token = localStorage.getItem('token');
if(!token) {
history.push('/login');
}
}
This uses useHistory hook from 'react-router-dom'
you just need to initialize it before calling it as:
const history = useHistory();
As already stated above it is not a safe sloution, but a simple one
You can do it by simply creating a RestrictedRoute or Private component and pass your redux user authenticated state to this and in RestrictedRoute component redirect if state is false, in case of true redirect to component.
code:
const RestrictedRoute = ({ component: Component, authUser, auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.is_authenticated && auth.is_authorized && authUser ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>
)
}
/>
);
<Switch>
<RestrictedRoute path={`${match.url}app`}
authUser={authUser}
auth={{ is_authenticated, is_authorized }}
component={MainApp}
/>
<Route path='/signin' component={SignIn} />
<Switch>