Clue Mediator

Login App - Create login form in ReactJS using secure REST API - Part 3

πŸ“…December 13, 2019
πŸ—ReactJS

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

We planned to divide this article into three parts.

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

  1. Create secure REST API
  2. Setup react application
  3. Create react components like Home, Login and Dashboard
  4. Implement authenticated routes
  5. 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.

Create 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.

Routing in React JS

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

    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.

    API call in React JS

    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

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

Demo & Source Code

Github Repository StackBlitz Project