Simple tutorial on React authentication with redux + Example

Simple tutorial on React authentication with redux + Example

In a multiple-page application, there are a number of public pages where any user can visit them and there are a few private pages with some sensitive data, where only a certain kind of user who has rights and credentials can visit those pages. This type of distinction in users is achieved by adding authentication in the application. In this blog, we are going to talk about react authentication with redux.

In this tutorial, we will focus on adding authentication so that developers can secure their private data and differentiate which users can visit them and which can not. In order to keep the tutorial simple and focus only on authentication, we will not be using any API endpoints, but we will sure be using localStorage to keep the user logged in the application even if he closes the tab or the application reloads.

Getting started

Let’s get started first with installing the required files which we going to need for this tutorial, for UI purposes I am going to install bootstrap, libraries required are:-

  1. React Bootstrap
  2. Redux
  3. React Router DOM
npm install react-router-dom redux react-redux react-bootstrap [email protected]

The file structure of the project going to be,

auth-project/
├── src/
│   ├── components/
│   │   └── header.js
│   ├── container/
│   │   ├── dashboard.js
│   │   └── login.js
│   ├── redux/
│   │   ├── constants.js
│   │   ├── actions.js
│   │   ├── reducer.js
│   │   └── store.js
│   ├── app.js 
│   ├── index.js
│   ├── protectedRoute.js 
│   └── style.css 
├── package.json
└── README.md

And then add bootstrap CSS file to your index.js file,

import "bootstrap/dist/css/bootstrap.min.css";

Also read, A simple React Hook Form Validation with Formik and Yup

Step 1 — Setuping the routes

The first step in this tutorial would be creating a few dumb pages and setting up the simple routes. It will allow us to get a clear picture of the different pages and components so that later it will be easy in navigating, constructing, and adding different components. So let us, first create 2 pages,

Login

export const Login = () => {
  return(
    <h2>Login Page</h2>
  )
}

Dashboard

export const Dashboard = () => {
  return(
    <h2>Dashboard Page</h2>
  )
}

After creating pages, let us now finally set up the routes for the different pages in app.js,

import "./styles.css";
import { Login } from "./container/login";
import { Dashboard } from "./container/dashboard";
import {
  BrowserRouter as Router,
  Switch,
  Route,
} from "react-router-dom";

export default function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Dashboard} />
        <Route exact path="/login" component={Login} />
      </Switch>
    </Router>
  );
}

Also read, How to Run A Cronjob That Stops an EC2 Instance When SSH or Session Manager Connections are Closed

Step 2 — Building a Login Page

Now next let us work on creating a login page and add a form to accept simple login credentials, let us first work on creating its UI,

import React, { useState } from "react";
import { Form, Button } from "react-bootstrap";

export const Login = () => {
  return(
    <section className='section bg-light'>
      <div className='container ht-100 d-flex justify-content-center align-items-center'>
        <div className='card p-2 shadow-lg d-flex card-width-300 justify-content-center align-items-center'>
          <h4>Login</h4>
          <Form>
            <Form.Group className="mb-3" controlId="email">
              <Form.Label>Email address</Form.Label>
              <Form.Control type="email" placeholder="Enter email" />
              <Form.Text className="text-muted">
                We'll never share your email with anyone else.
              </Form.Text>
            </Form.Group>

            <Form.Group className="mb-3" controlId="password">
              <Form.Label>Password</Form.Label>
              <Form.Control type="password" placeholder="Password" />
            </Form.Group>
            <Button variant="primary" type="submit">
              Submit
            </Button>
          </Form>
        </div>
      </div>
    </section>
  )
}

Now login page would look like this,

login page

After creating Ui, now let us add the form functionality and console the submitted data,

import React, { useState } from "react";
import { Form, Button } from "react-bootstrap";

export const Login = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if(email && password){
      console.log(email, password)
    }else{
      alert("Wrong Credentials")
    }
  }

  return(
    <section className='section bg-light'>
      <div className='container ht-100 d-flex justify-content-center align-items-center'>
        <div className='card p-2 shadow-lg d-flex card-width-300 justify-content-center align-items-center'>
          <h3 className="mb-3">Login</h3>
          <Form onSubmit={handleSubmit}>
            <Form.Group className="mb-3" controlId="email">
              <Form.Label>Email address</Form.Label>
              <Form.Control type="email" placeholder="Enter email"
              value={email} onChange={(e) => setEmail(e.target.value)}/>
              <Form.Text className="text-muted">
                We'll never share your email with anyone else.
              </Form.Text>
            </Form.Group>

            <Form.Group className="mb-3" controlId="password">
              <Form.Label>Password</Form.Label>
              <Form.Control type="password" placeholder="Password" 
              value={password} onChange={(e) => setPassword(e.target.value)}/>
            </Form.Group>
            <Button variant="primary" type="submit">
              Submit
            </Button>
          </Form>
        </div>
      </div>
    </section>
  )
}

Also read, How to add/remove input fields dynamically with Reactjs

Step 3 — Building a Dashboard Page

After completing the login page, now let us also tweak the UI of the dashboard and logout button, so that users can log out after completing its work in the dashboard, first create a header component and add a logout button,

export const Header = () => {
  return (
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <div className="container">
        <span class="navbar-brand">Dashboard</span>
        <button type="button" class="btn btn-danger">
          Log Out
        </button>
      </div>
    </nav>
  );
};

And then call the header component in the dashboard page,

import { Header } from "../components/header";
export const Dashboard = () => {
  return (
    <section>
      <Header />
      <div className='container ht-100 
      d-flex justify-content-center 
      align-items-center'>
      <h2>Dashboard Page</h2>
      </div>  
    </section>
  );
};

The dashboard page will look like this,

dashboard page

Also read, A simple React Hook Form Validation with Formik and Yup

Step 4 — Setting up the Redux

Now comes the most crucial part of the tutorial, setting up the redux, before moving forward let is first set up the actions constants to store all the variables in constants.js file,

Constant

// constants variables

export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";

Now lets us move to reducer.js, to create initials state and reducers, we will have an initial state with properties isAuthenticated and user, I have provided initial user credentials to verify the login credentials.

Reducer

import {LOGIN_SUCCESS, LOGOUT_SUCCESS} from "./constants";

const initialState = {
  isAuthenticated: false,
  user:{email:'[email protected]', pass:'asdf'}
};

// Reducers
const AuthReducer = (state = initialState, action) => {
  switch (action.type) {
    case LOGIN_SUCCESS:
      return {
        ...state,
        isAuthenticated: true,
      };
    
      case LOGOUT_SUCCESS:
      return {
        ...state,
        isAuthenticated: false,
      };

    default:
      return state;
  }
};

export default AuthReducer;

Actions

Then we will create two action functions for login and logout,

import {LOGIN_SUCCESS, LOGOUT_SUCCESS} from "./constants";


export const getLogin = () => {
  return({
    type: LOGIN_SUCCESS
  })
}

export const getLogout = () => {
  return({
    type: LOGOUT_SUCCESS
  })
}

Store

After creating the reducer function, then we will create our global redux store which consists of state data of the whole application and pass the reducer function,

import { createStore } from "redux";
import AuthReducer from "./reducer";

const store = createStore(AuthReducer);

export default store;

then finally we will import the store and pass it to the Provider component, Provider component makes redux store readily available to all nested components that need access to the global state data.

import { StrictMode } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import Store from "./redux/store";

import App from "./App";

const rootElement = document.getElementById("root");

ReactDOM.render(
  <StrictMode>
    <Provider store={Store}>
      <App />
    </Provider>
  </StrictMode>,
  rootElement
);

Also read, React Lifecycle Methods | Detail Explanation with Diagram

Step 5 — Adding up the Actions

After setting up the redux store, it is now time to add action functions to the components, we will dispatch the function which tells redux to update the store, first I will be importing getLogin action function in the login page, when a user submits the credentials data in the login form, it first checks if the user email and password saved in initial state matches with the entered the credentials, if true then, it dispatch getLogin the function which in reducer turns isAuthenticated to true, and then it redirects it to the dashboard location.

import React, { useState } from "react";
import { Form, Button } from "react-bootstrap";
import { useSelector, useDispatch } from "react-redux";
import { getLogin } from "../redux/actions";
import { useHistory } from "react-router-dom";

export const Login = () => {
  const dispatch = useDispatch();
  const history = useHistory();
  const state = useSelector((state) => state.user);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    if (email && password) {
      if (state.email == email && state.pass == password) {
        dispatch(getLogin());
        history.push("/")
      } else {
        alert("Credentials did not match");
      }
    } else {
      alert("Wrong Credentials");
    }
  };

  return (
    <section className="section bg-light">
      <div className="container ht-100 
         d-flex justify-content-center 
         align-items-center">
        <div className="card p-2 shadow-lg d-flex 
            card-width-300 justify-content-center 
            align-items-center">
          <h3 className="mb-3">Login</h3>
          <Form onSubmit={handleSubmit}>
            <Form.Group className="mb-3" controlId="email">
              <Form.Label>Email address</Form.Label>
              <Form.Control
                type="email"
                placeholder="Enter email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
              />
              <Form.Text className="text-muted">
                We'll never share your email with anyone else.
              </Form.Text>
            </Form.Group>

            <Form.Group className="mb-3" controlId="password">
              <Form.Label>Password</Form.Label>
              <Form.Control
                type="password"
                placeholder="Password"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
              />
            </Form.Group>
            <Button variant="primary" type="submit">
              Submit
            </Button>
          </Form>
        </div>
      </div>
    </section>
  );
};

And in the header component, we will import getLogout action function, when the user click on the logout button, it dispatches getLogout function, which in reducer turns isAuthenticated to false, which automatically throw the user to the login page due to protected route setting,

import { useDispatch } from "react-redux";
import { getLogout } from "../redux/actions";

export const Header = () => {
  const dispatch = useDispatch();
  return (
    <nav className="navbar navbar-expand-lg navbar-light bg-light">
      <div className="container">
        <span className="navbar-brand">Dashboard</span>
        <button type="button" className="btn btn-danger"
          onClick={() => dispatch(getLogout())}>
          Log Out
        </button>
      </div>
    </nav>
  );
};

Also read, How Does React Js works | Detail Explanation on Virtual DOM

Step 6 — Creating Protected Routes

The final step of this tutorial would be creating protected routes for the dashboard, Protected Routes are the routes that can only be accessed if the isAuthenticated is true. It renders the component passed to it the condition is true, or send the user to the login page.

import React from "react";
import { Route, Redirect } from "react-router-dom";

export default function ProtectedRoutes({
  auth, component: Component,...rest}) 
  {
    return (
      <Route
        {...rest}
        render={(routeProps) =>
          auth ? (
            <Component {...routeProps} />
          ) : (
            <Redirect
              to={{
                pathname: "/login",
                state: { from: routeProps.location },
              }}
            />
          )
        }
      />
    );
}

Now import the Protected route component to the App.js file and pass the dashboard component into it, so that only if the user is logged in can access the dashboard, if not then redirected to the login page. We will pass the isAutheticated value using useSelector hook from react-redux, so that the protected route component verifies if the auth is true or not.

import "./styles.css";
import { Login } from "./container/login";
import { Dashboard } from "./container/dashboard";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import ProtectedRoute from "./protectedRoutes";
import {useSelector} from "react-redux";

export default function App() {
  const auth = useSelector(state => state.isAuthenticated)
  return (
    <Router>
      <Switch>
        <ProtectedRoute auth={auth} exact path="/" component={Dashboard} />
        <Route exact path="/login" component={Login} />
      </Switch>
    </Router>
  );
}

Also read, How to select only one element of a map() in React

Step 7 — (Bonus) Local Storage

You can also add this extra functionality to your app to have a smooth user experience, saving state in local storage allows users to keep logged in even if they close the tab or browser. Data saved in local storage is kept permanently unless it gets changed explicitly, here hoe you can save data in local storage, go to your reducer.js file and this small code,

import { LOGIN_SUCCESS, LOGOUT_SUCCESS } from "./constants";

const initialState = {
  isAuthenticated: localStorage.getItem('authApp') || false,
  user: { email: "[email protected]", pass: "asdf" }
};

// Reducers
const AuthReducer = (state = initialState, action) => {
  switch (action.type) {
    case LOGIN_SUCCESS:
      localStorage.setItem('authApp', true)
      return {
        ...state,
        isAuthenticated: true
      };

    case LOGOUT_SUCCESS:
      localStorage.setItem('authApp', false)
      return {
        ...state,
        isAuthenticated: false
      };

    default:
      return state;
  }
};

export default AuthReducer;

When the initial state is called, we are calling the data from local storage bypassing authApp key and saving to isAuthenticated, if it is undefined then it takes false as a default value, if a user is logged in then we save the key-value pair as authApp and true, and if user logged out then it save key-value pair as authApp and false.

For your reference, I would be adding a demo link so that you can yourself check it and understand its working,

Also read, Easy tutorial to build React Autosuggest from scratch

Final Words

Thank you so much for reading my article, I hope you like my blog. If you find this article helpful then please share it more with your friends and colleagues. And bookmark this website to get more interesting and simple tutorials on various topics. Do check out more articles on technology, software, and business. I hope you have a nice day 😊 !!

Table of Contents