Adding JWT Login Authentication in a React App
Tags: react, tutorial, devjournal, webdev
Published: 2022-08-28 14:02:00

In recent times, one of the robust ways to authenticate a login credential is JWT authentication. Today, we will discuss how we can implement JWT authentication for a login app in React. For styling, I am using [Material UI](https://mui.com/) for React. Originally, I implemented JWT authentication over a private login API; replace this API with your private API.

First thing first, what is a JWT? JWT is short for JSON Web Token, and the JWT authentication is a compact way that ensures the safe transmission of information between parties as a JSON object. The data is verifiable because it is digitally signed using an **HMAC** algorithm (Hash-based Message Authentication Code). 

This is how JWT authentication works : 

![JWT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/91lnf19e07ow8coj53ka.png)

What is happening here is, that when you sign up to a system for the first time, your password is encrypted with an HMAC algorithm, and therefore, it is hashed. After that, if you sign in, two types of token are generated against your password, both of which will expire after some time. This time is set by the back-end and for this reason, you need to save these tokens. After that, if you try to make any API request, you will send the tokens with the header. If your header format is invalid, so is your request. If you have a valid header, both the access and refresh tokens are checked. 
If both the tokens are invalid, you force a logout action on whoever is trying to send that API request. 

The steps that you need to follow to implement a JWT authentication in your front-end are as follows:

1. Create a React App, then create a Login component with user input and a submit button and call it from App.js.
2. Write a handleSubmit where you will save the user input as states and also the access and refresh tokens in the localStorage. 
3. Send an API request. Check if there is a header, if not this is an invalid request. If there is a header, check for access and refresh token and match with the values of the localStorage. 
4. Conditionally check status codes and hit Refresh API and Logout API if Needed
5. If both the tokens are valid after checking all these conditions, you will receive a response. 

Now that you understand how JWT works, let's code this workflow.

## Step-1: Creating a React App and a Login Component

For this tutorial, I am using node version 17 and the latest version of yarn package manager. Create a react app named **jwt** and got to the folder jwt:

```
npx create-react-app jwt
cd jwt/

```

You will write a component named **Login.js**. For styling, install Material-UI with yarn:

```
yarn add @material-ui/core
yarn add @mui/icons-material
```

Inside Login component, you will be needing two text boxes for username and password, and a button to submit. 

```React

import React, { useState } from "react";
import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Stack from "@material-ui/core/Stack";
import { useStyles } from "../style.js";

import { MuiThemeProvider, createTheme } from "@material-ui/core/styles";

 const theme = createTheme({
    palette: {
      primary: {
        main: "#121212",
      },
    },
  });
export default function Login(props) {
  const classes = useStyles(props);

  return(
      <Grid container className={classes.root} 
       item xs={12} md={5}>
            <CssBaseline />
            <MuiThemeProvider theme={theme}>
            <Grid>
                <Typography component="h1" variant="h5">
                   Sign in
                </Typography>
            <form className={classes.form} noValidate>
                  <TextField
                   label="Username"
                   variant="outlined"
                   color="primary"
                   margin="normal"
                   required
                   fullWidth
                   id="username"
                   name="username"
                  />
                 <TextField
                   label="Password"
                   type={"password"}
                   variant="outlined"
                   margin="normal"
                   required
                   fullWidth
                   id="password"
                   name="password"
                  />
                 <Button
                   type="submit"
                   fullWidth
                   color="primary"
                   variant="contained"
                   className={classes.submit}
                  >
                    Sign In
                </Button>
            </form>
          </Grid>
        </MuiThemeProvider>
    </Grid>
  )
}
```
I have separated the styling codes from this component. The style.js file looks like the following:

```React
import { makeStyles } from "@material-ui/core/styles";


const useStyles = makeStyles((theme) => ({
  root: {
    height: "100vh",
    display: "flex",
    flexDirection: "column",
    margin: theme.spacing(12, 20),
    alignItems: "center",
  },
  form: {
    width: "100%",
    marginTop: theme.spacing(1),
  },
  submit: {
    margin: theme.spacing(3, 0, 2),
    backgroundColor: "#121212",
    color: "#fff"
  },
 
}));
export { useStyles };

```

Now, call it from **App.js** like the following:

```React
...
function App() {
  return (
    <div className="App">
     <Login/>
    </div>
  );
}
```
You can use any styling libraries to achieve this. If you follow through this, the output should look like this:


![login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hi3313cg20fhr8vd1qx.png)

## Step-2: Write a Handle function and Store Access and Refresh Tokens

In this step, we will get the data from the user and do something with it. We need to save this data with React states at first, and then we have to send in the POST API.

Declare a state which will hold the username and the password:

```React
const [data, setData] = useState({
    username: "",
    password: "",
  });

```

Inside the text fields, put the states in the value property:

```React

         <TextField
              ...
              id="username"
              name="username"
              value={data.username}
              onChange={handleChange}
            />

           <TextField
             
              id="password"
              name="password"
              value={data.password}
              onChange={handleChange}
            />

```

We need to track the change of the values with a handler function named **handleChange** . Write the function like the following:

```React
const handleChange = (e) => {
    const value = e.target.value;
    setData({
      ...data,
      [e.target.name]: value,
    });
  };
```

Instead of individually doing a setState operation, we at first are checking the value of the target, that is a text input. After that we are spreading the initial state object. 

We will do all the API operation with **Axios**. Install it:

```
yarn add axios
```

Put your private login API in a global variable such as login:

```React
const login = "your login api"

```
Remember how all the text boxes and Sign In button were wrapped in a form tag? We need to pass a handler function to handle the submit operation. Besides, in the API body of type POST, you are required to send the username and password. In our case we can send the entire state object in the API body. The whole handleSubmit will be like the following:

```React
const handleSubmit = (e) => {
    e.preventDefault();
    const userData = {
      username: data.username,
      password: data.password,
    };
    axios.post(login, userData)
      .then((response) => {
        if (response.status === 200) {
 
          console.log(response.status);
          console.log(response.data);
        }
      })
      .catch((error) => {
        if (error.response) {
          console.log(error.response);
          console.log("server responded");
          
        } else if (error.request) {
          console.log("network error");
        } else {
          console.log(error);
        }
      });
  };
```
If you check your login API in Postman, you will get to see the access tokens and refresh tokens in the output. 


![tokens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zp1idw816n1nk9sk8fox.png)

We have to save this in the localStorage. Do it inside the following block:

```React
if (response.status === 200) {
           localStorage.setItem("accessToken", response.data["access"]);
          localStorage.setItem("refreshToken", response.data["refresh"]);
        
         window.location.href = "/home";
        }
```

Write a bare minimum Home page component to see the change:

```React
import React, { useState } from "react";

export default function Home(){
    return(
        <div>this is home page</div>
    )
}
```
Oh no! It's in the login page even after our login. That's because, we didn't make any management for routing. Install React Router first:

```
yarn add react-router-dom
```
And now add the following code in your App.js:

```React
import { BrowserRouter, Route, Routes } from "react-router-dom";

import Home from "./components/Home";
function App() {
  return (
    <div className="App">
    <BrowserRouter>
            <Routes>
              <Route path="/" element={<Login />} />
              <Route path="/home" element={<Home />} />
            </Routes>
          </BrowserRouter>
    </div>
  );
}
```


## Step-3: Check the Header of the API Request

Inside your home component, console the tokens:

```React
const access = localStorage.getItem("accessToken");
console.log("access", access)

const refresh = localStorage.getItem("refreshToken");
console.log("refresh", refresh)

```


![token home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drwgluc8zprvokhiscf3.png)

Let's make an API request in the home page. Place your private API in a variable:

```React
const details = "any of your private API"

```

If you do not send a header to this API, you will receive an error code 403 (or whatever the back-end sends). You won't be able to make any API requests without a header. In order to send a header, create a object header in the following format:

```React
const header = {
  "Content-Type": "application/json",
  Authorization: `Bearer ${access}`,
};

```
You can check in the postman like the following way:


![Bearer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezt7zjpsm78kob43yf4g.png)

Say, your API is a get request. You can write it like the following, with the header you created:

```React
const [data, setData] = useState([]);
  useEffect(() => {
    axios
      .get(details, {
        headers: headers,
      })
      .then((response) => {
        setData(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, [data]);

return <div>this is home page {data}</div>;
```

## Step-4: Conditionally check status codes and hit Refresh API and Logout API if Needed

In this section, we will explore the "Access token is valid?-No" logic branch. In a nutshell, if you find both the tokens invalid, send a request to your logout API. If just the refresh token is invalid, send a request to the Refresh Token API and update the refresh token.

Here's the code:

```React
const logout_url = "your logout url"
const refresh_url = "your refresh url"

// inside the catch block of axios
.catch((error) => {
        if (error.response.status === 401) {
          console.log(error.response);
  
          axios.post(refresh_url, refresh).then((request) => {
            if (request.status === 200) {
              console.log("refresh valid");
              localStorage.setItem("accessToken", request.data["access"]);
            } else if (request.status === 401) {
              console.log("invalid so logout");
              axios.post(logout_url, headers).then((response) => {
                localStorage.setItem("accessToken", "");
                localStorage.setItem("refreshToken", "");
                console.log("access", response.data["access"]);
                console.log("refresh", response.data["refresh"]);
                
                window.location.href = "/login";
              });
            }
```
## Step-5: Send Response 

If you have followed all these steps, you have implemented an App which is robust with a JWT authentication. Find the final code in my github - https://github.com/Afroza2/React-Apps/tree/jwt . 

 I think I have given a layout for implementing a JWT authentication in your React app. In some cases, only the access token is provided by the back-end, so you can omit the whole check of the refresh token. Nevertheless, once you implement it, you don't have to think about security again.