Nuxt.js 2 And Express.js Authentication with Auth Module and JWT
Tags: nuxt, express, authmodule, jwt
Published: 2021-07-09 14:17:06

Hi, today we want to build an authentication app with Nuxt.js and Express.js, we'll focus on the front-end, and nuxt auth module and we will make a `local` authentication.
I remember myself when i wanted to add authentication in the app and how I was stuck in it because I couldn't find a good resource for it.
so I decided to write this article for other people to use it and don't get in trouble like me
This article is made for full-stack developers and also front-end developers so if you're not a full-stack web developer don't worry I will explain everything that you can add authentication on your app

**This application does not support of Refresh token for back-end, it's supported for front-end but commented because back-end had not set it and if we set it on front-end it will produce an error, in other words for back-end is just all about Access token, if you want to have Refresh token too, please give attention to last lines of this post**

before the start I should say that you need to know about these technologies:

###### Vue & Nuxt.js 
###### Express.js & Mongodb (If you want also implement API)

so let's start it

## Nuxt.js
1- make a new app with `npx create-nuxt-app front`
2- choose the Axios module when making new app (if you didn't don't worry we will install it  later)
3- install nuxt module and the Axios if you didn't 
`yarn add --exact @nuxtjs/auth-next`
`yarn add @nuxtjs/axios`
or with npm 
`npm install --save-exact @nuxtjs/auth-next`
`npm install @nuxtjs/axios`

then add it on nuxt.config.js like below:
```javascript
{
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth-next'
  ],
  auth: {
    // Options
  }
}
```
now it's time to add options on auth module :

```javascript
  auth: {
    strategies: {
      local: {
//      scheme: "refresh",
        token: {
          property: "token", //property name that the Back-end sends for you as a access token for saving on localStorage and cookie of user browser
          global: true,
          required: true,
          type: "Bearer"
        },
        user: {
          property: "user",
          autoFetch: true
        },
//      refreshToken: {  // it sends request automatically when the access token expires, and its expire time has set on the Back-end and does not need to we set it here, because is useless
//        property: "refresh_token", // property name that the Back-end sends for you as a refresh token for saving on localStorage and cookie of user browser
//        data: "refresh_token", // data can be used to set the name of the property you want to send in the request.
//      },
        endpoints: {
          login: { url: "/api/auth/login", method: "post" },
//        refresh: { url: "/api/auth/refresh-token", method: "post" },
          logout: false, //  we don't have an endpoint for our logout in our API and we just remove the token from localstorage
          user: { url: "/api/auth/user", method: "get" }
        }
      }
    }
  },
```
and here is the config for the Axios that I recommend to use it:

```javascript
  axios: {
    baseURL: "http://localhost:8080"  // here set your API url
  },
```
**Tip:** if your back-end project has implemented Refresh token you should uncomment the `refresh` endpoint and change it to the right endpoint which your back-end gave you and also you should uncomment these `refreshToken` object and also `scheme: refresh` 

now we make some components:

`/components/Auth/Login/index.vue`

```javascript
<template>
  <div>
    <form @submit.prevent="login">
      <div class="mb-3">
        <label for="email" class="form-label">Email address</label>
        <input
          type="email"
          class="form-control"
          id="email"
          v-model="loginData.email"
          aria-describedby="emailHelp"
        />
      </div>
      <div class="mb-3">
        <label for="password" class="form-label">Password</label>
        <input
          type="password"
          v-model="loginData.password"
          class="form-control"
          id="password"
        />
      </div>
      <button type="submit" class="btn btn-primary w-100">login</button>
    </form>
  </div>
</template>
<script>
export default {
  data() {
    return {
      loginData: {
        email: "",
        password: ""
      }
    };
  },
  methods: {
    async login() {
      try {
        let response = await this.$auth.loginWith("local", {
          data: this.loginData
        });
        this.$router.push("/");
        console.log(response);
      } catch (err) {
        console.log(err);
      }
    }
  }
};
</script>
<style></style>
```

`/components/Auth/Register/index.vue`
```javascript
<template>
    <div>
      <form @submit.prevent="register">
        <div class="mb-3">
          <label for="fullname" class="form-label">Full Name</label>
          <input
            type="text"
            v-model="registerData.fullname"
            class="form-control"
            id="fullname"
          />
        </div>
        <div class="mb-3">
          <label for="email" class="form-label">Email address</label>
          <input
            type="email"
            class="form-control"
            id="email"
            v-model="registerData.email"
            aria-describedby="emailHelp"
          />
        </div>
        <div class="mb-3">
          <label for="password" class="form-label">Password</label>
          <input
            type="password"
            v-model="registerData.password"
            class="form-control"
            id="password"
          />
        </div>
        <button type="submit" class="btn btn-primary w-100">Register</button>
      </form>
    </div>
  </template>
  <script>
  export default {
    data() {
      return {
        registerData: {
          fullname: "",
          email: "",
          password: ""
        }
      };
    },
    methods: {
      async register() {
        try {
          const user = await this.$axios.$post("/api/auth/signin", {
            fullname: this.registerData.fullname,
            email: this.registerData.email,
            password: this.registerData.password
          });
          console.log(user);
        } catch (err) {
          console.log(err);
        }
      }
    }
  };
  </script>
  <style></style>
```

and 

`/components/Home/index.vue`
```javascript
<template>
    <div>
      <h2>You're in home page</h2>
    </div>
  </template>
  <script>
  export default {};
  </script>
  <style></style>
  
```

and

`/components/User/index.vue`
```javascript
<template>
    <div>
      Hello dear <b style="color:red">{{ getUserInfo.fullname }}</b> you're in
      profile page
      <hr />
      This is your information:
      <br /><br />
      <table class="table">
        <thead>
          <tr>
            <th scope="col">ID</th>
            <th scope="col">FullName</th>
            <th scope="col">Email</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>{{ getUserInfo.id }}</td>
            <td>{{ getUserInfo.fullname }}</td>
            <td>{{ getUserInfo.email }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </template>
  
  <script>
  export default {
    computed: {
      getUserInfo() {
        return this.$store.getters.getUserInfo;
      }
    }
  };
  </script>
  
  <style></style> 
```

and 

`/components/Layouts/Header/index.vue`

```javascript
<template>
    <div>
      <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
          <nuxt-link class="navbar-brand" to="/">Navbar</nuxt-link>
          <button
            class="navbar-toggler"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent"
            aria-expanded="false"
            aria-label="Toggle navigation"
          >
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
              <template v-if="!isAuthenticated">
                <li class="nav-item">
                  <nuxt-link
                    class="nav-link active"
                    aria-current="page"
                    to="/auth/login"
                    >Login</nuxt-link
                  >
                </li>
                <li class="nav-item">
                  <nuxt-link
                    class="nav-link active"
                    aria-current="page"
                    to="/auth/register"
                    >Register</nuxt-link
                  >
                </li>
              </template>
              <template v-else>
                <li class="nav-item" @click="logout">
                  <nuxt-link class="nav-link active" aria-current="page" to="#"
                    >Logout</nuxt-link
                  >
                </li>
                <li>
                  <nuxt-link
                    class="nav-link active"
                    aria-current="page"
                    to="/profile"
                  >
                    Profile
                  </nuxt-link>
                </li>
              </template>
            </ul>
          </div>
        </div>
      </nav>
    </div>
  </template>
  
  <script>
  export default {
    methods: {
      async logout() {
        await this.$auth.logout();  // this method will logout the user and make token to false on the local storage of the user browser
      }
    },
    computed: {
      isAuthenticated() {
        return this.$store.getters.isAuthenticated;  // it check if user isAuthenticated 
      }
    }
  };
  </script>
  
  <style></style>
  
```

and 
`/layouts/default.vue`

```javascript
<template>
    <div>
      <layouts-header />
      <div class="container">
        <br />
        <Nuxt />
      </div>
    </div>
  </template>
  
  <script>
  export default {};
  </script>
  
  <style>
  @import url("https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap.min.css");
  </style>
```
and 

`/pages/index.vue`
```javascript
<template>
    <div>
      <home />
    </div>
  </template>
  
  <script>
  export default {};
  </script>
```

and 

`/pages/auth/login/index.vue`
```javascript
<template>
    <div>
      <auth-login />
    </div>
  </template>
  
  <script>
  export default {};
  </script>
  
  <style></style>
  
```

and 

`/pages/auth/register/index.vue`
```javascript
<template>
    <div>
      <auth-register />
    </div>
  </template>
  
  <script>
  export default {};
  </script>
  
  <style></style>
  
```

and 

`/pages/profile/index.vue`
```javascript
<template>
    <div>
      <user />
    </div>
  </template>
  
  <script>
  export default {
    middleware: "isAuthenticated"  // it will use `isAuthenticated` middleware
  };
  </script>
  
  <style></style>
  
```

and a middleware for checking if user authenticated:

```javascript
export default function({ store, redirect }) {
    if (!store.state.auth.loggedIn) {
      return redirect("/auth/login");
    }
}
```

and at last vuex store : 

```javascript
export const getters = {
    isAuthenticated(state) {
      return state.auth.loggedIn; // auth object as default will be added in vuex state, when you initialize nuxt auth
    },
    getUserInfo(state) {
      return state.auth.user;
    }
  };
```

## Express.js

That's was our nuxt codes and now its time to make our API:

first of all make a directory and enter this command on Terminal/Cmd and `npm init -y`
then `npm install express body-parser bcryptjs jsonwebtoken mongoose`
and then
`npm install --save-dev nodemon` it will add nodemon as a dev dependency

##### Cauation if `"main"` was like this `"main":"index.js"` on `package.json` file, change it to `"main": "app.js"` 

now its time we create some files:

`nodemon.js root direcotry that you just made`

```javascript
{
  "env": {
    "MONGO_USER": "mohammadali", // your cluster name
    "MONGO_PASS": "20212021", // your cluster password
    "MONGO_DB": "auth" // your db name
  }
}
```

`on package.json will be on root directory add these lines of codes on scripts like as I did in below`

```javascript
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start-server": "node app.js",
    "dev": "nodemon app.js"
  },
```

`app.js on root direcotry that you just made`

```javascript
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");

// routes
const authRouter = require("./routes/authRouter");

const app = express();

app.use(bodyParser.json());

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    "Access-Control-Allow-Methods",
    "OPTIONS, GET, POST, PUT, PATCH, DELETE"
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
});

app.use("/api/auth/", authRouter);

app.use((error, req, res, next) => {
  console.log(error);
  const status = error.statusCode || 500;
  const message = error.message;
  const data = error.data;
  res.status(status).json({ message: message, data: data });
});

// connect to db
const MONGOOSE_URI = `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@cluster0.4r3gv.mongodb.net/${process.env.MONGO_DB}`;
mongoose
  .connect(MONGOOSE_URI)
  .then((result) => {
    app.listen(process.env.PORT || 8080);
  })
  .catch((err) => console.log(err));

```

and 

`/controllers/authController.js`

```javascript
const bcrypt = require("bcryptjs");
const userModel = require("../models/userModel");
const jwt = require("jsonwebtoken");

exports.postSignin = async (req, res, next) => {
  const { fullname, email, password } = req.body;
  try {
    const exsitUser = await userModel.findOne({ email: email });
    if (exsitUser) {
      const error = new Error(
        "Eamil already exist, please pick another email!"
      );
      res.status(409).json({
        error: "Eamil already exist, please pick another email! ",
      });
      error.statusCode = 409;
      throw error;
    }

    const hashedPassword = await bcrypt.hash(password, 12);
    const user = new userModel({
      fullname: fullname,
      email: email,
      password: hashedPassword,
    });
    const result = await user.save();
    res.status(200).json({
      message: "User created",
      user: { id: result._id, email: result.email },
    });
  } catch (err) {
    if (!err.statusCode) {
      err.statusCode = 500;
    }
    next(err);
  }
};

let loadedUser;
exports.postLogin = async (req, res, next) => {
  const { email, password } = req.body;

  try {
    const user = await userModel.findOne({ email: email });

    if (!user) {
      const error = new Error("user with this email not found!");
      error.statusCode = 401;
      throw error;
    }
    loadedUser = user;

    const comparePassword = bcrypt.compare(password, user.password);

    if (!comparePassword) {
      const error = new Error("password is not match!");
      error.statusCode = 401;
      throw error;
    }
    const token = jwt.sign({ email: loadedUser.email }, "expressnuxtsecret", {
      expiresIn: "20m", // it will expire token after 20 minutes and if the user then refresh the page will log out
    });
    res.status(200).json({ token: token });
  } catch (err) {
    if (!err.statusCode) {
      err.statusCode = 500;
    }
    next(err);
  }
};

exports.getUser = (req, res, next) => { // this function will send user data to the front-end as I said above authFetch on the user object in nuxt.config.js will send a request and it will execute
  res.status(200).json({
    user: {
      id: loadedUser._id,
      fullname: loadedUser.fullname,
      email: loadedUser.email,
    },
  });
};
```

and 


`/middleware/isAuth.js`

```javascript
const jwt = require("jsonwebtoken");

module.exports = (req, res, next) => {
  const authHeader = req.get("Authorization");
  if (!authHeader) {
    const error = new Error("Not authenticated.");
    error.statusCode = 401;
    throw error;
  }
  const token = authHeader.split(" ")[1];
  let decodedToken;
  try {
    decodedToken = jwt.verify(token, "expressnuxtsecret");
  } catch (err) {
    err.statusCode = 500;
    throw err;
  }
  if (!decodedToken) {
    const error = new Error("Not authenticated.");
    error.statusCode = 401;
    throw error;
  }
  req.userId = decodedToken.userId;
  next();
};

```

and 


`/models/userModel.js`

```javascript
const express = require("express");
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const UserSchema = new Schema(
  {
    fullname: {
      type: String,
      required: true,
    },
    email: {
      type: String,
      required: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true }
);

module.exports = mongoose.model("User", UserSchema);

```

and 


`/routes/routes.js`

```javascript
const express = require("express");
const router = express.Router();
const authController = require("../controllers/authController");

router.post("/signin", authController.postSignin);
router.post("/login", authController.postLogin);
router.get("/user", authController.getUser);

module.exports = router;

```
#### Note that this was a test project and typically you have to add some validation for client-side app inputs, and also server-side app

finally it's done, hope you've enjoyed it, and there is my [Github](https://github.com/mohammadali0120/nuxt-express-authentication/) link that you can find the source code


**Some suggestions that might be useful for you:**

For those people, that want to have both (refresh token and access token) for their application and also want to use Postgresql instead of MongoDB, I suggest **[this](https://github.com/mohammadali0120/nuxt-express-postgres-authentication)** GitHub repository.


And for those people, that want to have both (refresh token and access token) for their application and also want to use Postgresql instead of MongoDB and also use Typescript for their Back-end application, I suggest **[this](https://github.com/mohammadali0120/nuxt-express-and-typescript-postgres-authentication)** GitHub repository