Creating a full-stack MERN app using JWT authentication: Part 4
Tags: node, react, javascript, fullstack
Published: 2019-10-28 14:02:55

---
title: Creating a full-stack MERN app using JWT authentication: Part 4
published: true
date: 2019-10-25 13:00:02 UTC
tags: node,react,javascript,fullstack
canonical_url: https://blog.logrocket.com/mern-app-jwt-authentication-part-4/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/x2ucg0masyca4z2qczbq.jpeg
---
**Written by [Praveen Kumar](https://blog.logrocket.com/author/praveenkumar/)**✏️

This is the final post in our series on building a full-stack MERN app using JWT authentication. Before forging ahead, read through [part one](https://dev.to/bnevilleoneill/creating-a-full-stack-mern-app-using-jwt-authentication-part-1-3ne9), [part two](https://dev.to/bnevilleoneill/creating-a-full-stack-mern-app-using-jwt-authentication-part-2-53a4), and especially [part three](https://dev.to/bnevilleoneill/creating-a-full-stack-mern-application-using-jwt-authentication-part-3-oa2) — the extra context will help you to better understand this continuation.

Up to now, we have successfully created a basic system that talks to the REST endpoint and provides the response, changes the states as required, and shows the right content. It also has a persistent login, too.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Adding a new endpoint

Here, we will be dealing with creating users, validating them on the server side, and generating different types of responses, like user not found, incorrect credentials, etc.

We will start with a sample store for the server and validate the users. Before that, we need an endpoint for the users to sign in. Let us start by editing our `server.js` and adding a new route, like this:

```jsx
app.post("/api/Users/SignIn", (req, res) => {
  res.json(req.body);
});
```

## Creating a store for users

A store is similar to a data store, a static database. All we are going to do is create key-value pairs for the users and make them co-exist. We also need to export the module to import them in the main `server.js`.

So, in `users.js`, we will add a few users. The key is the username, and the value for the object is the password.

```jsx
const Users = {
  Praveen: "Pr@v33n",
  Cloudroit: "C!0uDr0!7"
};

module.exports = Users;
```

Finally, we use the `module.exports` to export the `Users` object as the default export.

### Importing the user

Now we should be using the `require` method to import the user store inside our `server.js` to consume the contents of the `User` object.

```jsx
const Users = require("./users");
```

### User validation logic

This is where we are validating the input from the user (real human using the front end here). The first validation is checking whether the user is present in the system. This can be checked in two ways: by finding the key in the `Object.keys(User)` or by checking to ensure the type is not `undefined` using `typeof`.

If the user isn’t found, we send an error saying that user isn’t found. If the key is present, we validate the password against the value, and if it doesn’t equate, we send an error saying that the credentials aren’t right.

In both cases, we send a status code of `HTTP 403 Forbidden`. If the user is found and validated, we send a simple message saying `"Successfully Signed In!"`. This holds a status code of `HTTP 200 OK`.

```jsx
app.post("/api/Users/SignIn", (req, res) => {
  // Check if the Username is present in the database.
  if (typeof Users[req.body.Username] !== "undefined") {
    // Check if the password is right.
    if (Users[req.body.Username] === req.body.Password) {
      // Send a success message.
      // By default, the status code will be 200.
      res.json({
        Message: "Successfully Signed In!"
      });
    } else {
      // Send a forbidden error if incorrect credentials.
      res.status(403).json({
        Message: "Invalid Username or Password!"
      });
    }
  } else {
    // Send a forbidden error if invalid username.
    res.status(403).json({
      Message: "User Not Found!"
    });
  }
});
```

## Creating a service to consume the users logic

With the above change, we need to update the consuming logic in the front end. We currently don’t have a service for talking to the `Users/SignIn` API endpoint, so we will be creating an auth service to consume the API.

### Creating the auth service

Let’s create a file inside the `services` directory as `services/AuthService.js`. The function `AuthUser` will take up `Username`, `Password`, and a callback function, `cb`, as parameters. The `Username` and `Password` are sent to the `/api/Users/SignIn` endpoint as `POST` data parameters, and in the promise’s `then()`, the callback function is called with the response `res` as its parameter.

The same thing happens with an error condition, where the status code is anything but `2xx`. In that case, we send a second parameter as `true` to the callback function, passing the error object as the first one. We will be handling the error functions appropriately in the client side using the second parameter.

```jsx
import axios from "axios";

export const AuthUser = (Username, Password, cb) => {
  axios
    .post("/api/Users/SignIn", {
      Username,
      Password
    })
    .then(function(res) {
      cb(res);
    })
    .catch(function(err) {
      cb(err, true);
    });
};
```

### Getting rid of JWT on the client side

Since we are not generating any JWT in the client side, we can safely remove the import of the `GenerateJWT()` function. If not, React and ESLint might throw the error `no-unused-vars` during the compile stage.

```jsx
- import { GenerateJWT, DecodeJWT } from "../services/JWTService";
+ import { DecodeJWT } from "../services/JWTService";
+ import { AuthUser } from "../services/AuthService";
```

### Calling auth service on form submission

Now we just need to get our `GenerateJWT` function — and the other dependencies for that function like `claims` and `header` — replaced with `AuthUser` and a callback function supporting the `err` parameter.

Handling errors here is very simple. If the `err` parameter is `true`, immediately set an `Error` state with the received message, accessed by `res.response.data.Message`, and stop proceeding by returning `false` and abruptly halting the function.

If not, we need to check the status to be `200`. Here’s where we need to handle the success function. We need a JWT to be returned from the server, but as it stands, it doesn’t currently return the JWT since it’s a dummy. Let’s work on the server-side part next to make it return the JWT.

```jsx
handleSubmit = e => {
  // Here, e is the event.
  // Let's prevent the default submission event here.
  e.preventDefault();
  // We can do something when the button is clicked.
  // Here, we can also call the function that sends a request to the server.
  // Get the username and password from the state.
  const { Username, Password } = this.state;
  // Right now it even allows empty submissions.
  // At least we shouldn't allow empty submission.
  if (Username.trim().length < 3 || Password.trim().length < 3) {
    // If either of Username or Password is empty, set an error state.
    this.setState({ Error: "You have to enter both username and password." });
    // Stop proceeding.
    return false;
  }
  // Call the authentication service from the front end.
  AuthUser(Username, Password, (res, err) => {
    // If the request was an error, add an error state.
    if (err) {
      this.setState({ Error: res.response.data.Message });
    } else {
      // If there's no error, further check if it's 200.
      if (res.status === 200) {
        // We need a JWT to be returned from the server.
        // As it stands, it doesn't currently return the JWT, as it's dummy.
        // Let's work on the server side part now to make it return the JWT.
      }
    }
  });
};
```

### Showing the error on the screen

Let’s also update our little data viewer to reflect the error message, if it is available. The `<pre>` tag contents can be appended, with the below showing the contents of `this.state.Error`.

```jsx
{this.state.Error && (
  <>
    <br />
    <br />
    Error
    <br />
    <br />
    {JSON.stringify(this.state.Error, null, 2)}
  </>
)}
```

## Generate and send JWT from the server

Currently, our sign-in API `"/api/Users/SignIn"` response just sends out `HTTP 200`. We need to change that so it sends a success message along with a JWT generated on the server.

### Updating response for signing in

After checking if the `Username` is present in the database, we need to check whether the password is right. If both conditions succeed, we have to create a JWT in the server side and send it to the client.

Let’s create a JWT based on our default headers. We need to make the claims based on the `Username` provided by the user. I haven’t used `Password` here because it would be highly insecure to add the password in the response as plaintext.

```jsx
app.post("/api/Users/SignIn", (req, res) => {
  const { Username, Password } = req.body;
  // Check if the Username is present in the database.
  if (typeof Users[Username] !== "undefined") {
    // Check if the password is right.
    if (Users[Username] === Password) {
      // Let's create a JWT based on our default headers.
      const header = {
        alg: "HS512",
        typ: "JWT"
      };
      // Now we need to make the claims based on Username provided by the user.
      const claims = {
        Username
      };
      // Finally, we need to have the key saved on the server side.
      const key = "$PraveenIsAwesome!";
      // Send a success message.
      // By default, the status code will be 200.
      res.json({
        Message: "Successfully Signed In!",
        JWT: GenerateJWT(header, claims, key)
      });
    } else {
      // Send a forbidden error if incorrect credentials.
      res.status(403).json({
        Message: "Invalid Username or Password!"
      });
    }
  } else {
    // Send a forbidden error if invalid username.
    res.status(403).json({
      Message: "User Not Found!"
    });
  }
});
```

### Updating client-side logic for signing in

After updating the above code, the `res.data` holds both `Message` and `JWT`. We need the `JWT`, then we we need to decode it by calling the `DecodeJWT` service and store it in the state. Once that is done, we also need to persist the login after refresh, so we will be storing the `JWT` in `localStorage`, as discussed in the previous post.

As usual, we check if `localStorage` is supported in the browser and, if it is, save the `JWT` in the `localStore` by using the `localStorage.setItem()` function.

```jsx
handleSubmit = e => {
  // Here, e is the event.
  // Let's prevent the default submission event here.
  e.preventDefault();
  // We can do something when the button is clicked.
  // Here, we can also call the function that sends a request to the server.
  // Get the username and password from the state.
  const { Username, Password } = this.state;
  // Right now it even allows empty submissions.
  // At least we shouldn't allow empty submission.
  if (Username.trim().length < 3 || Password.trim().length < 3) {
    // If either of the Username or Password is empty, set an error state.
    this.setState({ Error: "You have to enter both username and password." });
    // Stop proceeding.
    return false;
  }
  // Call the authentication service from the front end.
  AuthUser(Username, Password, (res, err) => {
    // If the request was an error, add an error state.
    if (err) {
      this.setState({ Error: res.response.data.Message });
    } else {
      // If there's no errors, further check if it's 200.
      if (res.status === 200) {
        // We need a JWT to be returned from the server.
        // The res.data holds both Message and JWT. We need the JWT.
        // Decode the JWT and store it in the state.
        DecodeJWT(res.data.JWT, data =>
          // Here, data.data will have the decoded data.
          this.setState({ Data: data.data })
          );
        // Now to persist the login after refresh, store in localStorage.
        // Check if localStorage support is there.
        if (typeof Storage !== "undefined") {
          // Set the JWT to the localStorage.
          localStorage.setItem("JWT", res.data.JWT);
        }
      }
    }
  });
};
```

## Bug fixes and comments

There are a few mistakes that we have missed when developing the whole application, which we would have noticed if we used it like an end user. Let’s find how they crept in and fix them all.

### Clearing all error messages during successful events

The error message is not cleared after a successful sign-in and then signing out. We need to clear the error messages when we get signed in successfully.

```jsx
 AuthUser(Username, Password, (res, err) => {
   // If the request was an error, add an error state.
   if (err) {
     this.setState({ Error: res.response.data.Message });
   } else {
     // If there's no errors, further check if it's 200.
     if (res.status === 200) {
+      // Since there aren't any errors, we should remove the error text.
+      this.setState({ Error: null });
       // We need a JWT to be returned from the server.
       // The res.data holds both Message and JWT. We need the JWT.
       // Decode the JWT and store it in the state.
       DecodeJWT(res.data.JWT, data =>
         // Here, data.data will have the decoded data.
         this.setState({ Data: data.data })
          );
       // Now to persist the login after refresh, store in localStorage.
       // Check if localStorage support is there.
       if (typeof Storage !== "undefined") {
         // Set the JWT to the localStorage.
         localStorage.setItem("JWT", res.data.JWT);
       }
     }
   }
 });
```

### Clearing error messages after sign-out

Same thing here. After signing out, it is better to perform a cleanup of all the content, namely the `Error`, `Response`, and `Data`. We are already setting the `Response` and `Data` to `null`, but not the `Error`.

```jsx
SignOutUser = e => {
   // Prevent the default event of reloading the page.
   e.preventDefault();
   // Clear the errors and other data.
   this.setState({
+    Error: null,
     Response: null,
     Data: null
   });
   // Check if localStorage support is there.
   if (typeof Storage !== "undefined") {
     // Check if JWT is already saved in the local storage.
     if (localStorage.getItem("JWT") !== null) {
       // If there's something, remove it.
       localStorage.removeItem("JWT");
     }
   }
 };
```

## Final commented files

#### `server/server.js`

```jsx
const express = require("express");
const morgan = require("morgan");
const { GenerateJWT, DecodeJWT, ValidateJWT } = require("./dec-enc.js");
const Users = require("./users");

const app = express();
app.use(express.json());
app.use(morgan("dev"));
const port = process.env.PORT || 3100;

const welcomeMessage =
  "Welcome to the API Home Page. Please look at the documentation to learn how to use this web service.";

app.get("/", (req, res) => res.send(welcomeMessage));

app.post("/api/GenerateJWT", (req, res) => {
  let { header, claims, key } = req.body;
  // In case, due to security reasons, the client doesn't send a key,
  // use our default key.
  key = key || "$PraveenIsAwesome!";
  res.json(GenerateJWT(header, claims, key));
});
app.post("/api/DecodeJWT", (req, res) => {
  res.json(DecodeJWT(req.body.sJWS));
});
app.post("/api/ValidateJWT", (req, res) => {
  let { header, token, key } = req.body;
  // In case, due to security reasons, the client doesn't send a key,
  // use our default key.
  key = key || "$PraveenIsAwesome!";
  res.json(ValidateJWT(header, token, key));
});

app.post("/api/Users/SignIn", (req, res) => {
  const { Username, Password } = req.body;
  // Check if the Username is present in the database.
  if (typeof Users[Username] !== "undefined") {
    // Check if the password is right.
    if (Users[Username] === Password) {
      // Let's create a JWT based on our default headers.
      const header = {
        alg: "HS512",
        typ: "JWT"
      };
      // Now we need to make the claims based on Username provided by the user.
      const claims = {
        Username
      };
      // Finally, we need to have the key saved on the server side.
      const key = "$PraveenIsAwesome!";
      // Send a success message.
      // By default, the status code will be 200.
      res.json({
        Message: "Successfully Signed In!",
        JWT: GenerateJWT(header, claims, key)
      });
    } else {
      // Send a forbidden error if incorrect credentials.
      res.status(403).json({
        Message: "Invalid Username or Password!"
      });
    }
  } else {
    // Send a forbidden error if invalid username.
    res.status(403).json({
      Message: "User Not Found!"
    });
  }
});

app.listen(port, () => console.log(`Server listening on port ${port}!`));
```

### Client side

#### `client/src/components/Login.js`

```jsx
import React, { Component } from "react";
import { DecodeJWT } from "../services/JWTService";
import { AuthUser } from "../services/AuthService";

class Login extends Component {
  state = {
    Username: "",
    Password: ""
  };
  handleChange = e => {
    // Here, e is the event.
    // e.target is our element.
    // All we need to do is update the current state with the values here.
    this.setState({
      [e.target.name]: e.target.value
    });
  };
  handleSubmit = e => {
    // Here, e is the event.
    // Let's prevent the default submission event here.
    e.preventDefault();
    // We can do something when the button is clicked.
    // Here, we can also call the function that sends a request to the server.
    // Get the username and password from the state.
    const { Username, Password } = this.state;
    // Right now it even allows empty submissions.
    // At least we shouldn't allow empty submission.
    if (Username.trim().length < 3 || Password.trim().length < 3) {
      // If either of the Username or Password is empty, set an error state.
      this.setState({ Error: "You have to enter both username and password." });
      // Stop proceeding.
      return false;
    }
    // Call the authentication service from the front end.
    AuthUser(Username, Password, (res, err) => {
      // If the request was an error, add an error state.
      if (err) {
        this.setState({ Error: res.response.data.Message });
      } else {
        // If there's no errors, further check if it's 200.
        if (res.status === 200) {
          // Since there aren't any errors, we should remove the error text.
          this.setState({ Error: null });
          // We need a JWT to be returned from the server.
          // The res.data holds both Message and JWT. We need the JWT.
          // Decode the JWT and store it in the state.
          DecodeJWT(res.data.JWT, data =>
            // Here, data.data will have the decoded data.
            this.setState({ Data: data.data })
          );
          // Now to persist the login after refresh, store in localStorage.
          // Check if localStorage support is there.
          if (typeof Storage !== "undefined") {
            // Set the JWT to the localStorage.
            localStorage.setItem("JWT", res.data.JWT);
          }
        }
      }
    });
  };
  SignOutUser = e => {
    // Prevent the default event of reloading the page.
    e.preventDefault();
    // Clear the errors and other data.
    this.setState({
      Error: null,
      Response: null,
      Data: null
    });
    // Check if localStorage support is there.
    if (typeof Storage !== "undefined") {
      // Check if JWT is already saved in the local storage.
      if (localStorage.getItem("JWT") !== null) {
        // If there's something, remove it.
        localStorage.removeItem("JWT");
      }
    }
  };
  componentDidMount() {
    // When this component loads, check if JWT is already saved in the local storage.
    // So, first check if localStorage support is there.
    if (typeof Storage !== "undefined") {
      // Check if JWT is already saved in the local storage.
      if (localStorage.getItem("JWT") !== null) {
        // If there's something, try to parse and sign the current user in.
        this.setState({
          Response: localStorage.getItem("JWT")
        });
        DecodeJWT(localStorage.getItem("JWT"), data =>
          // Here, data.data will have the decoded data.
          this.setState({ Data: data.data })
        );
      }
    }
  }
  render() {
    return (
      <div className="login">
        <div className="container">
          <div className="row">
            <div className="col-6">
              <div className="card">
                {this.state.Data ? (
                  <div className="card-body">
                    <h5 className="card-title">Successfully Signed In</h5>
                    <p className="text-muted">
                      Hello {this.state.Data.Username}! How are you?
                    </p>
                    <p className="mb-0">
                      You might want to{" "}
                      <button
                        className="btn btn-link"
                        onClick={this.SignOutUser}
                      >
                        sign out
                      </button>
                      .
                    </p>
                  </div>
                ) : (
                  <div className="card-body">
                    <h5 className="card-title">Sign In</h5>
                    <h6 className="card-subtitle mb-2 text-muted">
                      Please sign in to continue.
                    </h6>
                    <form onSubmit={this.handleSubmit}>
                      {this.state.Error && (
                        <div className="alert alert-danger text-center">
                          <p className="m-0">{this.state.Error}</p>
                        </div>
                      )}
                      {["Username", "Password"].map((i, k) => (
                        <div className="form-group" key={k}>
                          <label htmlFor={i}>{i}</label>
                          <input
                            type={i === "Password" ? "password" : "text"}
                            name={i}
                            className="form-control"
                            id={i}
                            placeholder={i}
                            value={this.state[i]}
                            onChange={this.handleChange}
                          />
                        </div>
                      ))}
                      <button type="submit" className="btn btn-success">
                        Submit
                      </button>
                    </form>
                  </div>
                )}
              </div>
            </div>
            <div className="col-6">
              <pre>
                State Data
                <br />
                <br />
                {JSON.stringify(
                  {
                    Username: this.state.Username,
                    Password: this.state.Password
                  },
                  null,
                  2
                )}
                {this.state.Response && (
                  <>
                    <br />
                    <br />
                    Response Data (JWT)
                    <br />
                    <br />
                    {this.state.Response}
                  </>
                )}
                {this.state.Data && (
                  <>
                    <br />
                    <br />
                    Decoded Data
                    <br />
                    <br />
                    {JSON.stringify(this.state.Data, null, 2)}
                  </>
                )}
                {this.state.Error && (
                  <>
                    <br />
                    <br />
                    Error
                    <br />
                    <br />
                    {JSON.stringify(this.state.Error, null, 2)}
                  </>
                )}
              </pre>
            </div>
          </div>
        </div>
      </div>
    );
  }
}
export default Login;
```

#### `client/src/services/JWTService.js`

```jsx
import axios from "axios";

export const GenerateJWT = (header, claims, key, cb) => {
  // Send POST request to /api/GenerateJWT
  axios
    .post("/api/GenerateJWT", {
      header,
      claims,
      key
    })
    .then(function(res) {
      cb(res);
    })
    .catch(function(err) {
      console.log(err);
    });
};
export const DecodeJWT = (sJWS, cb) => {
  // Send POST request to /api/DecodeJWT
  axios
    .post("/api/DecodeJWT", {
      sJWS
    })
    .then(function(res) {
      cb(res);
    })
    .catch(function(err) {
      console.log(err);
    });
};
export const ValidateJWT = (header, token, key, cb) => {
  // Send POST request to /api/ValidateJWT
  axios
    .post("/api/ValidateJWT", {
      header,
      token,
      key
    })
    .then(function(res) {
      cb(res);
    })
    .catch(function(err) {
      console.log(err);
    });
};
```

#### `client/src/services/AuthService.js`

```jsx
import axios from "axios";

export const AuthUser = (Username, Password, cb) => {
  axios
    .post("/api/Users/SignIn", {
      Username,
      Password
    })
    .then(function(res) {
      cb(res);
    })
    .catch(function(err) {
      cb(err, true);
    });
};
```

## Deploying the complete code

### Using React’s production build

Once your app is created, we need to build the app by [creating a production build](https://create-react-app.dev/docs/production-build). The command `npm run build` creates a `build` directory with a production build of your app. Your JavaScript and CSS files will be inside the `build/static` directory.

Each filename inside `build/static` will contain a unique hash of the file contents. This hash in the filename enables [long-term caching techniques](https://create-react-app.dev/docs/production-build#static-file-caching). All you need to do is to use a static HTTP web server and put the contents of the `build/` directory into it.

Along with that, you must be deploying your API, too, in the `api/` directory on the root of your server.

### Using Heroku

Since we are already using a [G](https://github.com/praveenscience/JWT-MERN-FullStack)[it repository](https://github.com/praveenscience/JWT-MERN-FullStack) for this, it is a basic requirement for Heroku apps to be in a Git repository. Move to the root of the project to start with, and we need to create an app instance in Heroku. To do so, let’s use the following command in the terminal from the root of the project.

```jsx
➜  JWT-MERN-App git:(master) $ heroku create [app-name]
```

In the above line, `[app-name]` will be replaced with `jwt-mern`. Once the unique app name is chosen, the availability of the name will be checked by Heroku, and it will either proceed or ask for a different name. Once that step is done and a unique app name is chosen, we can deploy to Heroku using the below command:

```jsx
➜  JWT-MERN-App git:(master) $ git push heroku master
```

You can read more about deploying to Heroku in its [documentation](https://devcenter.heroku.com/articles/git).

## GitHub repository and final thoughts

The complete code is available along with the commits in this GitHub Repository: [praveenscience/JWT-MERN-FullStack: Creating a full-stack MERN app using JWT authentication](https://github.com/praveenscience/JWT-MERN-FullStack).

Hope this complete set of articles was informative and interesting. Let me know your thoughts.
* * * 
**Editor's note:** Seeing something wrong with this post? You can find the correct version [here](https://blog.logrocket.com/mern-app-jwt-authentication-part-4/).

## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *
The post [Creating a full-stack MERN app using JWT authentication: Part 4](https://blog.logrocket.com/mern-app-jwt-authentication-part-4/) appeared first on [LogRocket Blog](https://blog.logrocket.com).