Login App – Create login form in ReactJS using secure REST API – Part 3
Today we’ll show you how to create login form in ReactJS using secure REST API with example. It’s the last part of the login application where we will create the login form in ReactJS and integrate the Node.js secure REST API.
We planned to divide this article into three parts.
- Part 1 – Implementation flow
- Part 2 – Create REST API for authentication in Node.js using JWT
- Part 3 – Create login form in ReactJS using secure REST API (You are here…)
Before you continue, we want you to check out the first & second parts of this article. You can also check the following video for React Login App.
Way to create login form in ReactJS using secure REST API
- Create secure REST API
- Setup react application
- Create react components like Home, Login and Dashboard
- Implement authenticated routes
- Output
1. Create secure REST API
To create login application, we need secure REST API to integrate into the application. So we have already created the REST API in Node.js for authentication. Kindly refer the below article to create it or clone it from the GitHub repository.
After that just run the project so we can consume the REST API.
2. Setup react application
First, Let’s setup the simple react application to implement the login functionality. Following link will help you to create basic react application.
3. Create react components like Home, Login and Dashboard
Now, It’s time to create components for Home, Login and Dashboard pages and link it using the routing.
- Home component (Access it with/without login) – It’s a simple component which we can access it with/without user login. By default we’ll open this component.
- Login component (Access it without login only) – Here we will create a login page. So with the help of it we can call the REST API and on successful call we will redirect it on the Dashboard component.
- Dashboard component (Access it after login only) – It’s a private page that users can access only after successfully logged in. We will also provide the option to logout from user account in the same component.
If you don’t know how to implement routing in react app then refer to the following link for routing.
For demo purpose we are creating these components and updating the css in “src” directory.
Home.js
1 2 3 4 5 6 7 8 9 10 11 | import React from 'react'; function Home() { return ( <div> Welcome to the Home Page! </div> ); } export default Home; |
Login.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | import React, { useState } from 'react'; function Login(props) { const username = useFormInput(''); const password = useFormInput(''); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); // handle button click of login form const handleLogin = () => { props.history.push('/dashboard'); } return ( <div> Login<br /><br /> <div> Username<br /> <input type="text" {...username} autoComplete="new-password" /> </div> <div style={{ marginTop: 10 }}> Password<br /> <input type="password" {...password} autoComplete="new-password" /> </div> {error && <><small style={{ color: 'red' }}>{error}</small><br /></>}<br /> <input type="button" value={loading ? 'Loading...' : 'Login'} onClick={handleLogin} disabled={loading} /><br /> </div> ); } const useFormInput = initialValue => { const [value, setValue] = useState(initialValue); const handleChange = e => { setValue(e.target.value); } return { value, onChange: handleChange } } export default Login; |
Dashboard.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import React from 'react'; function Dashboard(props) { // handle click event of logout button const handleLogout = () => { props.history.push('/login'); } return ( <div> Welcome User!<br /><br /> <input type="button" onClick={handleLogout} value="Logout" /> </div> ); } export default Dashboard; |
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import React from 'react'; import { BrowserRouter, Switch, Route, NavLink } from 'react-router-dom'; import Login from './Login'; import Dashboard from './Dashboard'; import Home from './Home'; function App() { return ( <div className="App"> <BrowserRouter> <div> <div className="header"> <NavLink exact activeClassName="active" to="/">Home</NavLink> <NavLink activeClassName="active" to="/login">Login</NavLink><small>(Access without token only)</small> <NavLink activeClassName="active" to="/dashboard">Dashboard</NavLink><small>(Access with token only)</small> </div> <div className="content"> <Switch> <Route exact path="/" component={Home} /> <Route path="/login" component={Login} /> <Route path="/dashboard" component={Dashboard} /> </Switch> </div> </div> </BrowserRouter> </div> ); } export default App; |
index.js
1 2 3 4 5 6 | import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root')); |
index.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } .content { padding: 20px; } .header { padding: 10px; background: #edf2f4; border-bottom: 1px solid #999; } .header a { color: #0072ff; text-decoration: none; margin-left: 20px; margin-right: 5px; } .header a:hover { color: #8a0f53; } .header small { color: #666; } .header .active { color: #2c7613; } |
4. Implement authenticated routes
Let’s integrate the secure API to the react application. Follow the below steps to do it.
Create common utils
To manage the login and logout functionality, we need to create a few functions in “Common.js” file which will help us to manage token and user data using
sessionStorage
. Here we will create this file in “src/Utils” directory.src/Utils/Common.js
1234567891011121314151617181920212223// return the user data from the session storageexport const getUser = () => {const userStr = sessionStorage.getItem('user');if (userStr) return JSON.parse(userStr);else return null;}// return the token from the session storageexport const getToken = () => {return sessionStorage.getItem('token') || null;}// remove the token and user from the session storageexport const removeUserSession = () => {sessionStorage.removeItem('token');sessionStorage.removeItem('user');}// set the token and user from the session storageexport const setUserSession = (token, user) => {sessionStorage.setItem('token', token);sessionStorage.setItem('user', JSON.stringify(user));}Integrate sign in API
Let’s integrate the “/users/signin” API on the click event of the login button. Refer to the below link if you don’t know how to call API in ReactJS.
API call in React JSHere, we used the axios for the API call. After successful login, we are storing the response data in sessionStorage and redirect it to the dashboard page. Also managing the loading and error flag via
useState
.src/Login.js
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455import React, { useState } from 'react';import axios from 'axios';import { setUserSession } from './Utils/Common';function Login(props) {const [loading, setLoading] = useState(false);const username = useFormInput('');const password = useFormInput('');const [error, setError] = useState(null);// handle button click of login formconst handleLogin = () => {setError(null);setLoading(true);axios.post('http://localhost:4000/users/signin', { username: username.value, password: password.value }).then(response => {setLoading(false);setUserSession(response.data.token, response.data.user);props.history.push('/dashboard');}).catch(error => {setLoading(false);if (error.response.status === 401) setError(error.response.data.message);else setError("Something went wrong. Please try again later.");});}return (<div>Login<br /><br /><div>Username<br /><input type="text" {...username} autoComplete="new-password" /></div><div style={{ marginTop: 10 }}>Password<br /><input type="password" {...password} autoComplete="new-password" /></div>{error && <><small style={{ color: 'red' }}>{error}</small><br /></>}<br /><input type="button" value={loading ? 'Loading...' : 'Login'} onClick={handleLogin} disabled={loading} /><br /></div>);}const useFormInput = initialValue => {const [value, setValue] = useState(initialValue);const handleChange = e => {setValue(e.target.value);}return {value,onChange: handleChange}}export default Login;Manage the logout in dashboard
On click of the Logout button, we are removing the data from the sessionStorage and redirect it to the login page. Also we are showing the user name on page of the Dashboard.
src/Dashboard.js
123456789101112131415161718192021import React from 'react';import { getUser, removeUserSession } from './Utils/Common';function Dashboard(props) {const user = getUser();// handle click event of logout buttonconst handleLogout = () => {removeUserSession();props.history.push('/login');}return (<div>Welcome {user.name}!<br /><br /><input type="button" onClick={handleLogout} value="Logout" /></div>);}export default Dashboard;Create public and private routes
We divided routes in three parts.
- Normal routes – Which we can use to access routes with or without user login.
- Public routes – Which we can use to access the routes without login token only. So if user is already logged-in then we will redirect it to the dashboard page.
- Private routes – Which we can use to access the routes with login token only. So if user is not logged-in then we will redirect back it to the login page.
Utils/PrivateRoute.js
123456789101112131415import React from 'react';import { Route, Redirect } from 'react-router-dom';import { getToken } from './Common';// handle the private routesfunction PrivateRoute({ component: Component, ...rest }) {return (<Route{...rest}render={(props) => getToken() ? <Component {...props} /> : <Redirect to={{ pathname: '/login', state: { from: props.location } }} />}/>)}export default PrivateRoute;Utils/PublicRoute.js
123456789101112131415import React from 'react';import { Route, Redirect } from 'react-router-dom';import { getToken } from './Common';// handle the public routesfunction PublicRoute({ component: Component, ...rest }) {return (<Route{...rest}render={(props) => !getToken() ? <Component {...props} /> : <Redirect to={{ pathname: '/dashboard' }} />}/>)}export default PublicRoute;Now we have to slightly update the routes in “App.js” to implement authentication.
1234567891011121314......import PrivateRoute from './Utils/PrivateRoute';import PublicRoute from './Utils/PublicRoute';......<Switch><Route exact path="/" component={Home} /><PublicRoute path="/login" component={Login} /><PrivateRoute path="/dashboard" component={Dashboard} /></Switch>......export default App;Handle the browser refresh
At last, we have to handle the browser refresh to authenticate the application. For that we will integrate one more API called “/verifyToken” and manage the authentication flag. Based on the authentication flag we will manage the loading screen.
App.js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657import React, { useState, useEffect } from 'react';import { BrowserRouter, Switch, Route, NavLink } from 'react-router-dom';import axios from 'axios';import Login from './Login';import Dashboard from './Dashboard';import Home from './Home';import PrivateRoute from './Utils/PrivateRoute';import PublicRoute from './Utils/PublicRoute';import { getToken, removeUserSession, setUserSession } from './Utils/Common';function App() {const [authLoading, setAuthLoading] = useState(true);useEffect(() => {const token = getToken();if (!token) {return;}axios.get(`http://localhost:4000/verifyToken?token=${token}`).then(response => {setUserSession(response.data.token, response.data.user);setAuthLoading(false);}).catch(error => {removeUserSession();setAuthLoading(false);});}, []);if (authLoading && getToken()) {return <div className="content">Checking Authentication...</div>}return (<div className="App"><BrowserRouter><div><div className="header"><NavLink exact activeClassName="active" to="/">Home</NavLink><NavLink activeClassName="active" to="/login">Login</NavLink><small>(Access without token only)</small><NavLink activeClassName="active" to="/dashboard">Dashboard</NavLink><small>(Access with token only)</small></div><div className="content"><Switch><Route exact path="/" component={Home} /><PublicRoute path="/login" component={Login} /><PrivateRoute path="/dashboard" component={Dashboard} /></Switch></div></div></BrowserRouter></div>);}export default App;
5. Output
Run the login application and check it in the browser.
In this article, we have covered the general straight forward flow of the authentication. Check out the below article where you will find the more secure way to implement authentication including refresh token and CSRF protection.
Login App with CSRF protection
hi clue its a very very helpful article thank you , but i have a huge problem with that and that is i can create a fake jwt token in my browser before login and can access to the privateroute how can i deal with that ?
Hi, Parsa,
Thank you for referring the article.
You can follow the Part-2 article. This is all about the backend API including JWT token in Node.js.
If you don’t know about the Node.js then you can directly clone the repository and start the project. Postman collection is also there to test the API.
If you are still facing the issue then let us know we will help you to setup Part 2 and in future we are planning to host backend API on server so any one can access it for demo.
Let us know if you still have any queries.
Follow us on Facebook & Twitter to get latest update.
– Clue Mediator
thanks for replying ,
yeah i’ve done the node js and my server is responsing for veryfing token but in react i got confused in the privateroute part i use the getToken() method and this method just returning the sessionStorage token so i can simply change the sessionStorage token without login and type my component path in the url and get into it so is there any way to send the verify request to the server when routing ?
Hi Parsa,
You are right. Anyone can update the value of the sessionStorage and try to access the private routes. But in real project your private route is also verified with the next API request. Because after login your all API contains the token so it will be verify and if that is invalid or unauthorized then you can make force logout via middleware in ReactJS.
In alternative way, you can use secure and httponly cookie to manage the token. We will write separate article on it.
You can subscribe us for weekly email update of our news & articles.
– Clue Mediator
Hello,
thank you very much for the effort provided and quite well detailed.
just a question, I try to show / hide the “login” link if the user is already logged in, can you tell me how to do it please?
Hello Iweb,
We are glad you liked it.
You can use the access token via localStorage to show/hide login link.
Let us know if you still have any queries.
Like us on Facebook & Share it.
Happy Coding..!!!
Is it good for security, save token to session storage?
we can see the token via inspect
Hello Ade,
You are right anyone can see the token via inspect. But make sure you are not going to store any sensitive details inside the token. In the alternative way, you can use the secure and httponly cookie to store the token.
In the upcoming days, we will write the separate article on it.
Subscribe us for weekly update or like and follow us for regular update.
Happy Coding..!!!
Ok, I’ll wait that tutorial about secure and httponly cookie.
I was subscribe.
thank’s
Thank you for subscribing to our blog.
We are working on it.
Happy Coding..!!
Hello,
Thank you very much for your effort. It really helped a lot.
I have a question. So after generating the JWT token in the backend side and sending it in the response to the client side, how can I save this token and use it to send it in future API calls ?
Is there an article you can suggest about this topic?
Hello Diaa,
Thank you for reading the article.
If you check this article then you can see we have used the localStorage to manage the token and send it in the subsequent request.
Moreover, If you want to manage the token via cookies and also want CSRF protection then check out the below article where you will find the more secure way to implement authentication including refresh token and CSRF protection.
Login App with CSRF protection
Hope you like it. Let us know if you have any further queries.
Subscribe us for weekly updates or like and follow us for regular updates.
Happy Coding..!!
HÄ°! Thank you for this. I have a problem, when I clone and run the project and enter a made up email and password, I get
Unhandled Rejection (TypeError): undefined is not an object (evaluating ‘error.response.status’)
(anonymous function)
src/Login.js:21
can you help with that?
Thanks a lot
please ignore my previous comment I have solved it
We glad you fixed the issue.
Subscribe us for weekly updates or like and follow us for regular updates.
Hey, I have the same problem. How did you solve it? Thanks
Are you talking about this (“secretOrPrivateKey must have a value”) problem?
Hello, the problem is after I write PublicRoute and PrivateRoute in App.js. login page displays an error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Could you tell me why this would happen?
Hi Luka,
You have to add one more condition in the componentDidUpdate method to prevent the repeated loop.
Did you check Create public and private routes?
Let us know if you are still facing any issues.
This is really interesting, You are a very skilled blogger.
I have joined your feed and look forward to seeking more of your excellent post.
Hey ! Thanks for the great tutorial but i have a problem at login.js:21
Unhandled Rejection (TypeError): Cannot read property ‘status’ of undefined. I cant fix it.
Hi Rodman,
Please check your backend API. There should be some error. I guess your error is coming because of the following line.
if (error.response.status === 401) setError(error.response.data.message);
Please check the error object. If required then add one more condition to avoid an exception. Your updated code should be…
if (error.response && error.response.status === 401) setError(error.response.data.message);
Hope this will help you!
Let us know if you have any further queries.
geToken is not secure in my opinion. Can we compare the getToken to backend db every useeffect mounting of protected route?
Yes, you can do it. But I will suggest you check the next level article to manage the authentication.
Login App with CSRF protection
Hope you will get your answers in the given article.
after all this setup, when I tried to login it is giving me an error: “secretOrPrivateKey must have a value” how to solve this?
Hi Parkhij, Have you passed the “JWT_SECRET” from the backend to generate or verify the token?
Hello, how would I connect to a REST API to prevent CORS that is using a proxy and how to configure it. Thank you.
Hi Faardeen,
Will write an article on the proxy configuration. Check out the following article for CORS error.
https://www.cluemediator.com/how-to-enable-cors-in-node-js
i am getting 400 bad request
help mee plzzzzz
Please take a look at the Network tab and check the response. Hope you see the error message.
return (
15 |
16 | {context => {
> 17 | invariant(context, “You should not use outside a “);
| ^ 18 |
19 | const { history, staticContext } = context;
20 | //How to solve this type of error
I think this issue is related to routing. Can you please verify your routing code?
where do i find the api?
/users/signin
Hi Fabio,
Please look at Part 2 – Login App – Create REST API for authentication in Node.js using JWT.
Well explained! Keep posting.
This post offers clear idea for the new people of blogging, that truly how to do running a blog.
For latest news you have to visit web and on internet I found this site as a most excellent website for most recent updates.