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.
Checkout more articles on ReactJS/Node.js
- Form Validation in ReactJS
- Arrow Functions in JavaScript
- API calls with React Hooks
- Handle Events in React
- Create React Application using Multiple Components
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.
https://www.youtube.com/watch?v=iD1oXpYONmg
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.
Login App β Create REST API for authentication in Node.js using JWT β Part 2
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
import React from 'react';
function Home() {
return (
<div>
Welcome to the Home Page!
</div>
);
}
export default Home;
Login.js
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
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
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
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
index.css
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
- Integrate sign in API
- Manage the logout in dashboard
- Create public and private routes
- Handle the browser refresh
-
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
// return the user data from the session storage
export const getUser = () => {
const userStr = sessionStorage.getItem('user');
if (userStr) return JSON.parse(userStr);
else return null;
}
// return the token from the session storage
export const getToken = () => {
return sessionStorage.getItem('token') || null;
}
// remove the token and user from the session storage
export const removeUserSession = () => {
sessionStorage.removeItem('token');
sessionStorage.removeItem('user');
}
// set the token and user from the session storage
export 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.
Here, 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
import 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 form
const 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
import React from 'react';
import { getUser, removeUserSession } from './Utils/Common';
function Dashboard(props) {
const user = getUser();
// handle click event of logout button
const 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
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { getToken } from './Common';
// handle the private routes
function 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
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { getToken } from './Common';
// handle the public routes
function 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.
...
...
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
import 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.
Output - Create login form in ReactJS using secure REST API - Clue Mediator
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