Authenticate private routes with API - reactjs

I am trying to authenticate private routes. I have it working by checking for a cookie before allowing access. However, cookies can be spoofed so I have an API end point which accepts the cookie and returns whether its valid or not.
Working version without API:
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
cookies.get('sessionid') ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/',
state: { from: props.location }
}}/>
)
)}/>
)
ReactDOM.render((
<Router>
<Switch>
<Route exact path="/" component={Login}/>
<PrivateRoute exact path="/home" component={Home} />
<PrivateRoute exact path="/upload" component={Upload}/>
<PrivateRoute exact path="/logout" component={Logout}/>
<PrivateRoute exact path="/review" component={Review}/>
<Route component={ NotFound } />
</Switch>
</Router>
), document.getElementById('root'))
Additional code for API call:
axios.post(process.env.REACT_APP_API_URL+'/account/validate-session', {
t1_session_id: cookies.get('sessionid')
})
.then(function (response) {
if(response.data.status === "OK"){
console.log('authenticated go to private route');
} else {
console.log('not valid, redirect to index');
}
}.bind(this))
.catch(function (error) {
console.log('not valid, redirect to index');
}.bind(this));
The issue is I am not sure how to incorporate the API section of code into the main route section.
Thanks

Well, I guess you must write a wrapper component for it. Let's try:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import YourComponent from './path/to/YourComponent';
class WrapperComponent extends Component {
constructor(props) {
super(props);
this.state = {
isAuth: false
};
}
componentDidMount() {
axios.post(process.env.REACT_APP_API_URL+'/account/validate-session', {
t1_session_id: cookies.get('sessionid')
})
.then(function (response) {
if(response.data.status === "OK"){
this.setState({isAuth: true})
} else {
this.setState({isAuth: false})
}
}.bind(this))
.catch(function (error) {
console.log('not valid, redirect to index');
}.bind(this));
}
render() {
return (this.state.isAuth ? <YourComponent /> : null);
}
}
export default WrapperComponent;
And now your route must redirect to WrapperComponent:
const PrivateRoute = ({ component: WrapperComponent, ...rest }) => (
<Route {...rest} render={props => (
<WrapperComponent {...props}/>
)}/>
)

Every user can only spoof his own cookie. Likewise, a user can spoof the entire JS code that runs in his browser. Therefore, calling the server for verifying the cookie will not mitigate your vulnerability so much.
Instead, you should trust the cookie in your JS code, and check the cookie in every operation in the server that requires login.
Notice that a cookie is not the safest way of identifying a user since it's a subject to CSRF attack. You should either use JWT or check the referer.
Anyway if you decide to adhere your idea of asking the server whether the cookie is valid, I would adopt arikanmstf's answer, except you should handle three cases: auth=true, auth=false and auth=null, since if we're yet to get an answer from the server we want just to hide the element, but if we got a negative answer, we wanna redirect the user.
constructor(props) {
super(props);
this.state = {
isAuth: null
};
}
render() {
if(this.state.isAuth === null) return null;
return (this.state.isAuth ? <YourComponent /> : <Redirect ... />
}

Related

React: What is the best way to block an invalid session from accessing page

I am using react-router-dom to handle routes. I am making a simple login page where you login, session gets generated and redirects to home page.
I want to have a check on my home page to say if the user doesn't have a valid session, throw them back to the login page ("/"). I have been at this for hours and my problem is, axios is async so I haven't found a way to not load the page till this check is done, and the solution I have made shows the page for a split second while this call to my API is being made.
What is the standard way of doing this? My method is below:
App.js
import React, { Component } from 'react'
import Login from './Login';
import Home from './Home';
import { Route, Redirect } from 'react-router-dom';
import axios from 'axios'
export class App extends Component {
constructor(props) {
super(props)
this.state = {
homeRoute: <Route path="/home" component={Home}></Route>
}
}
getAuthorisedAPI() {
axios.get("http://localhost:8080/validateSession")
.then(response => {
console.log(response.data.Success)
if(!response.data.Success) {
this.setState({
homeRoute: <Route path="/home" component={Home}><Redirect to="/" /></Route>
})
}
}).catch(error => {
console.log(error)
})
}
componentDidMount() {
this.getAuthorisedAPI()
}
render() {
axios.defaults.withCredentials = true
return (
<>
<Route exact path="/" component={Login} />
{
this.state.homeRoute
}
</>
)
}
}
export default App
If there was a way axios could do a non async call so the page load waits for this response then the below would be ideal..
const isValid = true
return (
<>
<Route exact path="/" component={Login} />
<Route path="/home" component={Home}>
{
!isValid && <Redirect to="/" />
}
</Route>
</>
)
Thanks
Since you are on client side, I'd recommend to split your app in two. The <AuthenticatedApp /> and the <UnauthenticatedApp />. You can read more about this on https://kentcdodds.com/blog/authentication-in-react-applications.
The other option is to use async/await.
async getAuthorisedAPI() {
try {
const response = await axios.get("http://localhost:8080/validateSession");
console.log(response.data.Success)
if(!response.data.Success) {
this.setState({
homeRoute: <Route path="/home" component={Home}><Redirect to="/" /></Route>
})
}
} catch(error) {
console.log(error)
}
}
async componentDidMount() {
await this.getAuthorisedAPI()
}

Why is my react router switch not detecting the auth state change?

Currently I have the following react router
<BrowserRouter>
<Switch>
{/* It should display the register page if the isAuthenticated state is true */}
<Route exact path="/register" render={(props) => ((this.state.isAuthenticated) ? <RegisterPage /> : <NewLandingPage {...props} /> )} />
<Route exact path="/login" render={(props) => <LoginPage />} />
</Switch>
</BrowserRouter>
In my App.js
And then in the constructor of my App.js I have the following states, and authservice call
this.state = {
isAuthenticated: false
}
isAuthenticated().then((result) => {
if (result == true) {
this.state.isAuthenticated = true;
console.log("its true authenticated");
console.log(this.state.isAuthenticated);
console.log("----")
} else {
this.state.isAuthenticated = false;
console.log("its false not autheticated");
console.log(this.state.isAuthenticated);
console.log("----")
}
});
By default, my isAuthenticated state is false. So i wouldnt be able to display "/register" if I hadnt an auth service.
But I have implemented an isAuthenticated() to make the app.js check if theres a valid user. It actually displays the desired data. For example, if im autheticated, the result would be true, and I would receive the following in my console.
console.log("its true authenticated");
console.log(this.state.isAuthenticated); //true
But seems that the browserrouter is not taking into effect the isAuthenticated() method, and is relying solely in the default value, which is false, so It will always hide my register page will never be displayed besides me having an authservice there
this.state = {
isAuthenticated: false
}
Any idea of why is my browser not detecting the states change?
EDIT:
isAuthenticated() function by request
import axios from 'axios'
import Cookies from 'js-cookie';
export default function isAuthenticated(){
var accesstoken = Cookies.get('accesstoken');
return axios({ method: 'post', url: 'http://localhost:3003/verify', headers: { Authorization: `Bearer ${accesstoken}` } })
.then(function (response) {
if(response.data.status === "valid"){
console.log("service valid")
return true;
}else{
console.log("service invalid")
return false;
}
});
}
When you change the state with redux(for example with an action you bring the component from actions in redux, and it sets new state in the reducer), it will effect after the component mount, there is a way to render the component(If the props have changed) and get the new state status, with ComponentDidUpdate method:
So the actions effect the global state(in reducer), and you bring this state to your component. ComponentDidUpdate will take a look and when the state you bring with the props has changed, it will update the component and allow you to execute setState
ComponentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.isAuthenticated !== prevProps.isAuthenticated) {
this.setState({
isAuthenticated:true
});
// I suppose you call the state from the reducer by this.props.isAuthenticated
}
}
Also for checking the authentication state, I am using an auth.js util like this, it gets the authenticated state from reducer, makes a redirect to login page instead of the component, if authenticated is false:
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
const AuthRoute = ({ component: Component, authenticated, ...rest }) => (
<Route
{...rest}
render={props =>
authenticated === true ? (
<Redirect to="/user" />
) : (
<Component {...props} />
)
}
/>
);
const mapStateToProps = state => ({
authenticated: state.user.authenticated
});
AuthRoute.propTypes = {
user: PropTypes.object
};
export default connect(mapStateToProps)(AuthRoute);
so in my app.js, I can check the auth state and redirect if necessary:
import AuthRoute from "./util/AuthRoute.js";
<Switch>
<Route exact path="/user" component={home} />
<AuthRoute exact path="/login" component={userLogin} />
<AuthRoute exact path="/signup" component={userSignup} />
<Route exact path="/ticketList" component={ticketList} />
</Switch>
Possibly the issue is that the authentication code is in your constructor which gets called once. You might want to try it a slightly different way. Create a const (outside your class) to hold the switchable component content:
export const RegisterOrLandingPage = () => {
if (isAuthenticated()) {
return <RegisterPage />;
}
return <NewLandingPage />;
}
Your isAuthenticated() code should ideally be in a function too, rather than sat in the constructor or used as a state, and abstracted out to its own component (i.e., callable from anywhere, usable by anyone).
Then your route can be slightly different:
<Route exact path="/register" component={RegisterOrLandingPage} />

React, why are my private routes rendered for a brief second to unauthorized users before the redirect kicks in?

I've been using private routes in order to secure them, but the problem im facing is that whenever an unauthorized user enters that route, he will be able to see the protected route content for a brief second, right before he is redirected again to a default route for unauthorized users.
Why is that happening? I suspect that it happens because my auth verification service relies on an external server, and that causes a delay on the verification, while the user is being verified it happens to display the protected route, but once it has finished verifying it and the result is "unauthorized" it redirects the user to another unprotected route.
This is how my PrivateRoute looks
import { Route } from 'react-router-dom';
import React from 'react';
import { Redirect } from 'react-router';
export default ({ component: Component, render: renderFn, authed, ...rest }) =>
Component ? (
<Route
{...rest}
render={props =>
authed === true ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/', state: { from: props.location } }} />
)
}
/>
) : (
<Route {...rest} render={props => authed === true ? renderFn(props) : <Redirect to={{ pathname: '/', state: { from: props.location } }} /> } />
);
This is my router, localted in app.js
<BrowserRouter>
<Switch>
<PrivateRoute authed={this.state.isAuthenticated} path='/register' component={RegisterPage} />
<Route exact path="/login" render={(props) => <LoginPage />} />
<Route exact path="/" render={(props) => <NewLandingPage {...props} />} />
<PrivateRoute authed={this.state.isAuthenticated} path="/route1" render={props => <iframe frameBorder={0} src={constans.route1} className="iframe" />} />
<PrivateRoute authed={this.state.isAuthenticated} path="/route2" render={props => <iframe frameBorder={0} src={constans.route2} className="iframe" />} />
</Switch>
</BrowserRouter>
This is the constructor of my app.js
this.state = {
isAuthenticated: true
}
And this is what I have in componentDidMount()
componentDidMount() {
isAuthenticated().then((result) => {
if (result == true) {
this.setState({ isAuthenticated: true})
} else {
this.setState({ isAuthenticated: false})
}
});
}
As you can see its making a call to an authentication server, and I suspect that its where the delay happens and that might be the cause of the render happening to unauthorized users
import axios from 'axios'
import Cookies from 'js-cookie';
export default function isAuthenticated() {
var accesstoken = Cookies.get('accesstoken');
return axios({ method: 'post', url: 'http://localhost:3003/verify', headers: { Authorization: `Bearer ${accesstoken}` } })
.then(function (response) {
if (response.data.status === "valid") {
console.log("service valid")
return true;
} else {
console.log("service invalid")
return false;
}
});
}
How can I solve this? I was thinking about adding a middle page in the PrivateRoute with "loading" content, or something like that, but im not sure how or where to implement it.
First of please use strict null checks (i.e.: myBool === true) because they protect you from referencing nulls or undefined values in code.
For your problem it looks like that isAuthenticated is always true in the beginning. Axios async response then automatically changes that value afterwards.
In your app.js you already initialise isAuthenticated value to true, try setting it to false in the beginning since i guess that all your users should not be authenticated in the beginning right?
Everything else looks fine for me.

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.

React 16.7 protected routes

I want to protect routes and make them available only for authenticated users.
the problem is that I know if the user is authenticated or not after doing a fetch() and updating the state in the context (and that takes some time)
I want to redirect users to /login when the fetch is complete and the context state isAuthenticated = false
import { Route, Switch } from 'react-router-dom'
import { MyContext } from './Context'
...
<MyContext.Consumer>
{(context) => (
<Switch>
<Route path="/" exact strict component={Homepage}/>
<Route path="/this-is-a-protected-route" exact strict component={Admin}/>
<Route path="/login" exact strict component={Login}/>
</Switch>
)}
</MyContext.Consumer>
this is the context
export const MyContext = React.createContext()
export class MyProvider extends Component {
state = {
'isAuthenticated': false,
}
componentDidMount() {
fetch('/authenticated')
.then(
function(response) {
response.json().then(function(data) {
this.setState({'isAuthenticated': true})
});
}
)
.catch(function(error) {
console.log('fetch error', error);
});
}
render() {
return (
<MyContext.Provider value={{
isAuthenticated: this.state.isAuthenticated
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
Ideally I will be redirected to /login only after hitting /this-is-a-protected-route and the state.isAuthenticated = false (but this is the default value!)
I removed some code for brevity so we can focus on the problem. Hope you understand thanks!
You can render protected routes only if user authenticated
{context.isAuthenticated ?
<Route path="/this-is-a-protected-route" exact strict component={Admin}/> :
<Redirect to='/login' />
}
Private routes can be moved to separate component.
More details and examples here.

Resources