Routing components in React JS using login functionality - reactjs

I am trying to build a full stack web application using Spring and React JS.
I have created login/register APIs in Spring and have connected them to React. They work just alright.
This is my UserService
import axios from 'axios';
const user_base_url = "http://localhost:8080/users";
class UserService{
createUser(user) {
return axios.post(user_base_url + '/register', user);
}
authenticateUser(user) {
return axios.post(user_base_url + '/login', user);
}
}
export default new UserService();
This how I validate my user in the LoginComponent.
validateUser = () => {
let user = {
username: this.state.email,
password: this.state.password,
};
UserService.authenticateUser(user).then((res) => {
if(res.data === 'SUCCESS') {
//logged in
console.log("logged in");
} else if(res.data === 'FAILURE') {
console.log("NO");
this.resetLoginForm();
this.setState({"error":"Invalid username or password"});
}
})
};
I now wish to add routes to my application so that certain components can only be accessed when logged in.
function App() {
return (
<div>
<Router>
<HeaderComponent/>
<div className="container">
<Switch>
<Route path="/" exact component={LandingPageComponent}></Route>
<Route path ="/customers" component = {ListCustomerComponent}></Route>
<Route path ="/add-customer/:id" component = {CreateCustomerComponent}></Route>
<Route path = "/view-customer/:id" component = {ViewCustomerComponent}></Route>
<Route path = "/admin-login" component = {AdminLoginComponent}></Route>
<Route path = "/admin-register" component = {AdminResgisterComponent}></Route>
</Switch>
</div>
<FooterComponent/>
</Router>
</div>
);
}
export default App;
How can this be achieved? I found solutions using tokens but I haven't used a token, I only check user is entering correct username and password from the database (MySQL) via my REST API.
Any help would be highly appreciated.

There are two types of Routes that are usually defined: Open Routes and Protected Routes.
Open Routes are the ones that are accessible by a user without any authentication and Protected Routes are the ones that require a certain form of authentication to be accessed.
Now, let's proceed to answer your questions.
How to Implement a Protected Route?
First, you need to know whether the user is authenticated or not and for the same, you will need to use a certain "value" or "token" (Like An ID Card) that says that this user is authenticated.
For a simple practise application, you could just store a Boolean saying whether the user is authenticated or not.
You will need to store this value in a place such as Local Storage, Cookies or Session Storage.
For this example, I have assumed that you are storing the value in a local Storage.
A Protected Route will be wrapped around with a condition that checks the Local Storage to find a value that says the user is authenticated.
isAuthenticated === true ===> Show The Desired Component.
isAuthenticated === false ===> Redirect the User to the login page.
// isAuthenticated is extracted from the local storage.
<Route path="/aClassfiedPath" render={() => (
isAuthenticated === true
? <DesiredComponent />
: <Redirect to='/login' />
)} />
You will also notice another practice, that is, to make a totally separate Layout for the Protected Components and inside the Layout, check whether the user is authenticated or not.
// Assuming the required Modules have been imported
const ProtectedLayout = ({children}) => {
if (!isAuthenticated) {
return (
<Redirect to="/loginPage" />
)
}
// children contains the Desired Component and `div` tag represents a custom
// container meant for Protected Routes. Like: A Speacial Header or Side Navigation.
return (
<div>
{children}
</div>
)
}
Recommended Read
An Article on Protected Routes
<Redirect> Reference
Render Prop of <Route>

Related

Create wrapper to check user role in App.js component routes

I know the question is very general but I cannot find the correct way and terms to search this online to see how it can be done.
My problem is that I have an app that users log in and then a user role is added to a context element (like if the user is admin or simple user etc).
I can restrict access to the components by checking the user role and redirect. somerhing like
return user==="admin"?<div>...</div>: "redirect to no access component"
Is a way to create a wrapper that will wrap every Route in the App.js and perform this check instead.
normally you should restrict the access in the router of your application.
but to anwser the question you can create a hooks that checks if user is admin or else redirect to the no access component using react-router:
const useCheckUserPermission = (permission) => {
// get the user from the context
useEffect(() => {
if(user === permission) // redirect to no access component
}, [])
return [];
}
then you can use it like this:
const Component = (props) => {
useCheckUserPermission("admin");
return // return your component
}
If its a page then you can follow the following Approach:
While attaching Routes to Router you can create custom Route component which will check the condition.
Like:
import { Route } from "react-router-dom";
const PrivateRouteAdmin=({path,component:Component...rest})=>{
const user = useContext(AuthContext).userType;// any global state which you have used
return user==='admin'?
<Route path={path} {...rest}
render={(props)=>{
return <Component {...props} />
}}/>: <Redirect to={"/"}/>
}
You can use it in you code as follows:
<BrowserRouter>
<PrivateRouteAdmin exact path={'/protected'} component={ProtectedPage} />
</BrowserRouter>
If you want to apply some condition on specific page element, then you can use the approach which you are following otherwise make another component for that purpose too like:
const AdminContent = ({children)=> {
const user = useContext(AuthContext).userType;// any global state which you have used
return user==='admin'?children:<Redirect to={"/"}/>
}
and you can call the component anywhere you want like:
<AdminContent> <div>protected</div></AdminContent>

My Login Form is showing even when I am already authenticated using React Routing?

I am using redux saga in my app, I have a login form and when I get authenticated I use the Redirect Component to move to the app, I do this of course after changing my connectedUser state, that is like that :
const initialState = {
loading: false,
user: {},
status: ""
};
When authenticating my state change to status:"AUTH" and user: { // user data }, And this is how I redirect to the Application component and this is how my Authentication component rendering method looks like :
render() {
if (this.props.connectedUser.status === "AUTH") {
return <Redirect to="/" />;
}
return MyLoginForm
}
My routes are defined in the whole app container :
function App(props) {
return (
<ThemeProvider theme={theme}>
<I18nProvider locale={props.language.language}>
<BrowserRouter>
<Switch>
<Route exact path="/" component={Application}></Route>
<Route path="/authenticate" component={Authentification}></Route>
</Switch>
</BrowserRouter>
</I18nProvider>
</ThemeProvider>
);
}
const msp = state => ({ language: state.language });
export default connect(msp, null)(App);
This is working fine, the disconnect button is doing fine too, but the problem occurs when I am redirected to my app ( meaning this route "/" ).
I want that when I refresh the page, I don't get redirected to the authentication page, Why I am going there ? it is obvious that my state is the initial state again, the status is "" again.
I am not asking for code but an idea, how to implement this.
I was trying to put that status in my localStorage but I don't feel like it is a good practise, also I found a problem whith this because If I change the localStorage then I will have to re render the component so the condition can be verified.
I want that when I refresh the page, I don't get redirected to the authentification page, Why I am going there ? it is obvious that my state is the initial state again, the satus is "" again.
Sure! Refresh page will initial state again.
First of all, you need store current authentification after signed in. I suggest you use the redux-sessionstorage
Next, in Authentication component you should dispatch an action to get current authentification and update status in your state.

Protected Routes React Router 4 not working with auth state stored in Redux

I am trying to make an authenticated route in React Router v4 as per this example. Showing the code for posterity:
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => (!!authed)
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
My authentication state (authed), which is initialized as an empty object at the reducer, is derived from a Redux store. This is how my App.js looks like:
class App extends Component {
componentDidMount() {
const token = localStorage.getItem("token");
if (token) {
this.props.fetchUser();
}
}
render() {
return (
<Router>
<div>
<PrivateRoute authed={this.props.authed} path='/dashboard' component={Dashboard} />
/>
</div>
</Router>
);
}
}
The problem is that the authed state starts as undefined and then, once the Router component is mounted, it updates the state to true. This is however a bit late, because the user would be already redirected back to the login page. I also tried to replace the componentDidMount() lifecycle method, with the componentWillMount() but that did not fix the problem either.
What strategies would you suggest?
UPDATE 1: The only way I get around this is by testing for the authed state before returning the <Route /> component such as this:
render() {
if (!!this.props.authed) {
return (
<Router>
<div>
...
UPDATE 2: I am using Redux Thunk middleware to dispatch the action. The state is being passed as props correctly - I am using console.log() methods inside the PrivateRoute component to verify that the state mutates correctly. The problem is of course that it is mutating late, and the Route is already redirecting the user.
Pasting code of reducer and action...
Action:
export const fetchUser = () => async dispatch => {
dispatch({ type: FETCHING_USER });
try {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: FETCH_USER, payload: res.data });
} catch (err) {
// dispatch error action types
}
};
Reducer:
const initialState = {
authed: {},
isFetching: false
};
...
case FETCH_USER: // user authenticated
return { ...state, isFetching: false, authed: action.payload };
I had the same problem and from my understanding your update #1 should be the answer. However upon further investigation I believe this is an architectural problem. The current implementation of your Private route is dependent on the information being synchronous.
If we think about it pragmatically the ProtectedRoute essentially returns either a redirect or the component based on the state of our application. Instead of wrapping each Route with a component we can instead wrap all the routes in a component and extract our information from the store.
Yes it is more code to write per protected route, and you'll need to test if this is a viable solution.
Edit: Forgot to mention another big reason this is an architectural problem is if the user refreshes a page which is protected they will be redirected.
UPDATE
Better solution:
On refresh if they are authenticated it will redirect to their target uri
https://tylermcginnis.com/react-router-protected-routes-authentication/
Solution 1
//You can make this a method instead, that way we don't need to pass authed as an argument
function Protected(authed, Component, props) {
return !!authed
? <Component {...props} />
: <Redirect to='/login' />
}
class AppRouter extends React.PureComponent {
componentDidMount() {
const token = localStorage.getItem("token");
if (token) {
this.props.fetchUser();
}
}
render() {
let authed = this.props.authed
return (
<Router>
<Route path='/protected' render={(props) => Protected(authed, Component, props)} />
</Router>
)
}
}
class App extends Component {
render() {
return (
<Provider store={store}>
<AppRouter />
</Provider>
)
}
}
Solution 2
Or we can just check for each component (yes it's a pain)
class Component extends React.Component {
render() {
return (
!!this.props.authed
? <div>...</div>
: <Redirect to='/' />
)
}
}
The same problem was happening with me, I am using a temporary hack to solve it by storing an encrypted value inside localStorage and then decrypting it in my PrivateRoute component and checking if the value matches.
action.js
localStorage.setItem('isAuthenticated', encryptedText);
PrivateRoute.js
if (localStorage.getItem('isAuthenticated')) {
const isAuth = decryptedText === my_value;
return (
<Route
{...rest}
render={(props) =>
isAuth ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
} else {
return <Redirect to="/login" />;
}
Since localStorage is faster, so it is not unnecessarily redirecting. If someone deletes localStorage they will simply be redirected to /login
Note: It is a temporary solution.
Theoretically, you need to get a promise from the NODE API call which you are not getting right now. You need to make architectural changes. I suggest you use redux-promise-middleware this is a redux middleware. I have a sample project in my github account. Where you will get notified if your call to this.props.fetchUser() is completed or not, based on that using Promise you can handle this async problem you have. Go to that repo if need help ask me.
if your App component is connected to the redux store, you probably mean this.props.authed instead of this.state.authed
My authentication state (authed), which is initialized as an empty
object at the reducer, is derived from a Redux store
So you are comparing empty object with true here: (props) => authed === true? Why don't you initialize it with a false?
And are you sure the action this.props.fetchUser is switching the state to true?
Maybe you better also post your action and reducer file

Route handling a uniquely generated URL specific to a user in ReactJS and react-router-dom v4

In the app I am working on if a user forgot their password, they click on a link that brings them to a page where they enter the username. If the username is matched, it sends a uniquely generated URL to the email associated with the username. For example:
http://localhost:8000/auth/security_questions/0e51706361e7a74a550e995b415409fdab4c66f0d201c25cb4fa578959d11ffa
All of this works fine, but I am trying to figure out how to handle this on the front-end routing using React and react-router-dom v4. I made this route.
<Route exact path='/auth/security_questions/:key' component={SecurityQuestions} />
The correct component loads related to security questions, but that is not the behavior I am after. After all, it takes anything you put after security_questions/.
What it should be doing is matching :key against the database before it loads the component.
I'm not sure about a few things:
1) How to parse out the :key portion so that I can pass it as a value to verify against the database.
2) While I have a general idea of how to handle the verification, I am not sure how to tell React: "Ok, the key has been verified in the database. Finish loading the component."
I think it would in general look like:
// ./containers/security_questions.js
componentWillMount() {
this.props.verifyKey(:key);
}
And then actions:
// ./actions/authentication.index.js
export function verifyKey({ :key }) {
return function(dispatch) {
axios
.post(
`${ROOT_URL}/api/auth/security_questions/`,
{ :key }
)
.then(response => {
dispatch('Finish loading the rest of the component')
})
.catch(error => {
dispatch(authError(error.response.data.non_field_errors[0]));
});
}
}
Or maybe instead of it finishing loading the component, it should just route to a different URL that is a protected route.
You can grab the params from the path like so (https://reacttraining.com/react-router/web/api/Route):
<Route path="/user/:username" component={User}/>
const User = ({ match }) => <h1>Hello {match.params.username}!</h1>
You will want to conditionally render based upon some state set by verifyKey.
componentWillMount() {
this.props.verifyKey(this.props.match.params.key);
}
render() {
if (this.state.permitRender) {
return <Component>
} else {
return <LoadingComponent />
}
You can use the render method on the route to put in your verification logic and render the appropriate control. You would need to move the action to verify the key to the component which renders the route, rather than the SecurityQuestions component.
<Route exact
path='/auth/security_questions/:key'
render={(props)=>{
let finalComponent= <ComponentToRenderWhenKeyDoesNotMatch/>;
if(this.props.verifyKey(props.match.params.key)){
resultantComponent=<SecurityQuestions/>
}
return finalComponent;
}}
/>
Route params will be inside the match.params prop:
componentWillMount() {
this.props.verifyKey(this.props.match.params.key);
}
See https://reacttraining.com/react-router/web/example/url-params
--
Then on the success of your security questions I would set some state in your reducer and render based on that.

Why am I losing my ReactJS state?

I'm new to ReactJS, but I have a simple use case: I have a login form that sets the user's state (full name, etc.) and then I use the React Router to browserHistory.push('/') back to the home page. I can confirm that if I stay on the login page that my states actually get saved, however when I go back to the homepage, and into my "parent" component (App.js) and run this before the render method:
console.log(this.state) // this returns null
It always returns true. My constructor is not even doing anything with the state. If I put a log in the constructor on my App.js parent component I can verify that the page is not actually being reloaded, that the component is only mounted once (at least the constructor on App.js is only called once during the whole homepage > login > homepage lifecycle). Yet again, the state seems to be removed after changing pages.
What simple thing am I missing?
Edit: some code, trying to simplify it:
// LoginForm.js
//Relevant method
handleSubmit() {
this.login(this.state.username, this.state.password, (success) => {
if (!success)
{
this.setState({ isLoggedIn: false })
this.setState({ loginError: true })
return
}
this.setState({ isLoggedIn: true })
browserHistory.push('/') // I can verify it gets here and if at this point I console.log(this.isLoggedIn) I return true
})
}
// App.js
class App extends React.Component {
constructor(props) {
super(props);
console.log('hello')
}
render() {
const { props } = this
console.log(this.state) // returns null
return (
<div>
<AppBar style={{backgroundColor: '#455a64'}}
title="ADA Aware"
showMenuIconButton={false}>
<LoginBar/>
</AppBar>
<div>
{props.children}
</div>
</div>
)}
//Part of my routes.js
export default (
<Route path="/" component={App}>
<IndexRoute component={HomePage}/>
<Route path="/login" component={LoginForm}/>
<Route path="*" component={NotFoundPage}/>
</Route>
);
Where you call handleSubmit(), what component calls it?
If it is <LoginForm> or <LoginBar> or something like this, your this. means that component, non <App>.
To set parent's state (<App> in your case) you should pass to child a property with a handler function (for example onLogin={this.handleAppLogin.bind(this)}) so your child must call this prop (this.props.onLogin(true)) and handler in the <App> will set App's state: handleAppLogin(isLoggedIn) { this.setState({isLoggedIn}); }
But for the "global" state values such as login state, access tokens, usernames etc, you better shoud use Redux or some other Flux library.
This was a closed issue router project and discussed in many SO articles:
https://github.com/ReactTraining/react-router/issues/1094
https://github.com/reactjs/react-router-redux/issues/358
Redux router - how to replay state after refresh?
However, it is persistent real world need. I guess the pattern of how to handle this is based on Redux and/or browser storage.

Resources