Vue Authentication

Setting up Vue Authentication using Expressjs, MongoDB, and JWT


In this article, we will be building an authentication system in Vue using expressjs, MongoDB and JSON web token(JWT) for the authentication.

we will start by creating a simple REST API that will enable a user to signup and login with their details.

Before we kick off

Learn Vue.js and modern, cutting-edge front-end technologies from core-team members and industry experts with our premium tutorials and video courses on VueSchool.io.

Click here to Browse all Courses on VueSchool.io

Here is the Demo of our app:

Learning prerequisites

  • Basic familiarity with Javascript.
  • Basic Knowledge of vuejs.
  • Basic Knowledge of REST APIs.

Outline

Below is the outline of the steps we will be taking to accomplish the task. We will start up with building our backend service then after that, we build our frontend.

Setting up the backend

  • Setup Nodejs with the npm init -y command
  • Setup a file structure for our application.
  • Install all the required dependencies for our application
  • Create an express server and configure all dependencies.
  • Connect to the database.
  • Create User model
  • Create the necessary routes
  • Create the auth middleware.

Ok, let’s write some codes…

Setup Nodejs with the npm init -y command

  • let’s start by creating a folder on our home directory for our application. So open your command tool and do the following:
cd desktop
mkdir authapp && cd authapp
mkdir server
mkdir client
cd server

what we just did was create a directory called authapp , after that, we created two other directories named server and the other client (the server directory will house our backend while the client directory will house our vuejs application), after that, we move into the server directory to setup our backend application. To set up a nodejs application we will need a package.json file to handle all of our project dependencies. So to create that we need to run npm init -y on our terminal.

npm init -y

After running this command a package.json file will be generated.

Setup a file structure for our application.

Its time to create a file structure for our application. Go ahead and set up a file structure like the one below:

Vue Authentication

From the structure above, it will be very easy to configure our application and also making it very scalable.

Install all the required dependencies for our application

Our application will need some npm packages for development. I will explain all the packages below:

  • Expressjs : A node.js framework that makes it easy to build web applications.
  • mongodb: A document-oriented NoSQL database used for high volume data storage.
  • mongoose: An object modeling tool used to asynchronous query MongoDB.
  • bcrypt : This will help us hash user passwords before storing them into the database.
  • jsonwebtoken : JSON Web Token(JWT) will be used for authentication and authorization. This package will help to set up protected routes that only logged in users can access.
  • nodemon : Nodemon will re-run the express server every time we make changes to our code.
  • morgan : Morgan is a logger tool used to log all requests made on the server.
  • body-parser: body-parser is what allows express to read the request body and then parse that into a JSON object that we can be understood.
  • cors : This is a middleware that can be used to enable CORS with various options.

The first thing we need to do is to install nodemon globally so that it can be accessed anywhere on our local machine. To install that open ur terminal and type this command:

npm install -g nodemon

After doing this we can now install our packages:

npm i express mongoose bcrypt jsonwebtoken nodemon morgan body-parser cors --save

This command will install all these packages and add them among our dependencies in the package.js file. Notice that it also creates a node-modules directory, this is where all our packages are being stored. Remember not to add this package while you are pushing your project to git. To avoid this we need to create a .gitignore file. To do this open your terminal and run this command:

touch .gitignore

 This command will create a .gitignore file. Open the file and type node-modules in it.

Create an express server and configure all dependencies

Now head over to the index.js file and type the following code:

const express = require("express");
const PORT = process.env.PORT || 4000;
const morgan = require("morgan");
const cors = require("cors");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const config = require("./config/db");
const app = express();
//configure database and mongoose
mongoose.set("useCreateIndex", true);
mongoose
  .connect(config.database, { useNewUrlParser: true })
  .then(() => {
    console.log("Database is connected");
  })
  .catch(err => {
    console.log({ database_error: err });
  });
// db configuaration ends here
//registering cors
app.use(cors());
//configure body parser
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//configure body-parser ends here
app.use(morgan("dev")); // configire morgan
// define first route
app.get("/", (req, res) => {
  console.log("Hello MEVN Soldier");
});
const userRoutes = require("./api/user/route/user"); //bring in our user routes
app.use("/user", userRoutes);
app.listen(PORT, () => {
  console.log(`App is running on ${PORT}`);
});

In the above code snippet, we load our express into our file, we then require all our packages and then we configure the packages. We then require the user route that we shall create later and we set our port number. We also require our db.js file that has our database connection which we shall create shortly.

Go to your terminal and type the command nodemon . You should be seeing App is running on 4000 on your terminal. That means we’ve successfully set up our express server.

Connect to database

Go to config/db.js file and type the code below:

module.exports = {
  database: "mongodb://localhost:27017/authapp",
  secret: "password"
};

Now you should see Database is connected outputted on the terminal. That means that we’ve successfully set up our MongoDB database.

Create User model

Now it’s time to define our user model. Head over to /api/user/model/User.js file and type the following code:

const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const userSchema = mongoose.Schema({
  name: {
    type: String,
    required: [true, "Please Include your name"]
  },
  email: {
    type: String,
    required: [true, "Please Include your email"]
  },
  password: {
    type: String,
    required: [true, "Please Include your password"]
  },
  tokens: [
    {
      token: {
        type: String,
        required: true
      }
    }
  ]
});

//this method will hash the password before saving the user model
userSchema.pre("save", async function(next) {
  const user = this;
  if (user.isModified("password")) {
    user.password = await bcrypt.hash(user.password, 8);
  }
  next();
});

//this method generates an auth token for the user
userSchema.methods.generateAuthToken = async function() {
  const user = this;
  const token = jwt.sign({ _id: user._id, name: user.name, email: user.email },
  "secret");
  user.tokens = user.tokens.concat({ token });
  await user.save();
  return token;
};

//this method search for a user by email and password.
userSchema.statics.findByCredentials = async (email, password) => {
  const user = await User.findOne({ email });
  if (!user) {
    throw new Error({ error: "Invalid login details" });
  }
  const isPasswordMatch = await bcrypt.compare(password, user.password);
  if (!isPasswordMatch) {
    throw new Error({ error: "Invalid login details" });
  }
  return user;
};

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

Let me explain the code snippet. We start by requiring mongoose, bcrypt and jsonwebtoken in our user model, after that, we create our mongoose schema which takes in an object which defines the property of the user schema.

Mongoose converts the schema into a document in the database and those properties will be converted into fields in our document.

The token array will hold all the tokens of a particular user and stores it in the database. Every time a user registers or logs in, we shall create a token and append it to the existing array of tokens.

After this we define three methods: The first method will hash the user password before saving it in the database, the second method generates an auth token for the user, this token is what we will be using for the validation. The last method will search for a user by email and password, if it finds the user it returns the user but if it doesn’t it returns an error.

Create the necessary routes

Now let’s create our routes. Below is the list of endpoints we will be creating:

  • HTTP POST /register  —— Register User.
  • HTTP POST /login —— Allow users to login.
  • HTTP GET /me  ——- Get user profile.

Let’s start by creating a user’s route. Head over to /routers/user.js and write the following code:

const express = require("express");
const router = express.Router();
const userController = require("../controller/userController");

router.post("/register", userController.registerNewUser);
router.post("/login", userController.loginUser);
router.get("/me", userController.getUserDetails);

module.exports = router;

userController.js:

const User = require("../model/User");

exports.registerNewUser = async (req, res) => {};
exports.loginUser = async (req, res) => {};
exports.getUserDetails = async (req, res) => {};

Here we create the routes in a different file and then we create the methods attached to them in a different file to make the code scalable. So if we call the /user/register route it will go into the userController file and looks for a method called registerNewUser and then executes the function but if it doesn’t it returns an error.

let continue by creating the registerNewUser method. Below is the code snippet:

exports.registerNewUser = async (req, res) => {
  try {
   console.log(isUser);
    if (isUser.length >= 1) {
      return res.status(409).json({
        message: "email already in use"
      });
    }
    const user = new User({
      name: req.body.name,
      email: req.body.email,
      password: req.body.password
    });
    let data = await user.save();
    const token = await user.generateAuthToken(); // here it is calling the method that we created in the model
    res.status(201).json({ data, token });
  } catch (err) {
    res.status(400).json({ err: err });
  }
};

The user registration route creates a new user with the given information that we access from the req.body .After saving the user, we generate an authentication token and return it as a response alongside the user data.let’s test our endpoint to see if its working using POSTMAN.POSTMAN is currently one of the most popular tools used in API testing endpoints.

Vue Authentication

Ensure that the type of request being sent is a POST request and ensure that the URL is localhost:4000/user/register. And also ensure that you provide the required fields which are name, email, and password like we defined them in the user model.

Now let’s write the login method and test it. Below is the code snippet:

exports.loginUser = async (req, res) => {
  try {
    const email = req.body.email;
    const password = req.body.password;
    const user = await User.findByCredentials(email, password);
    if (!user) {
      return res.status(401).json({ error: "Login failed! Check authentication credentials" });
    }
    const token = await user.generateAuthToken();
    res.status(201).json({ user, token });
  } catch (err) {
    res.status(400).json({ err: err });
  }
};

Ensure that the type of request being sent is a POST request and ensure that the URL is localhost:4000/user/login .if your code is similar to mine you should get a similar response like the one below:

Create the auth middleware

Express middleware are functions that execute during the lifecycle of a request to the Express server. Each middleware has access to the HTTP request and response for each route (or path) it’s attached to. We want to check if a person who is trying to access a specific resource is authorized to access it. That is where we take advantage of middleware.

Head over to /config/auth.js and write the following code:

const jwt = require("jsonwebtoken");
module.exports = (req, res, next) => {
  try {
    const token = req.headers.authorization.replace("Bearer ", "");
    console.log(token);
    const decoded = jwt.verify(token, "secret");
    req.userData = decoded;
    // console.log(req.userData);
    next();
  } catch (err) {
    return res.status(401).json({
      message: "Authentification Failed"
    });
  }
};

Now to use this auth middleware, we are going to go back to the user route file, import our auth middleware by requiring it at the top of the file just after requiring the user controller. Modify the following code in our user route like this:

router.get("/me", auth,userController.getUserDetails);

And then we modify the following code in the usercontroller:

exports.getUserDetails = async (req, res) => {
  await res.json(req.userData);
};

If we try to access this route from postman we should get an error message saying that we are unauthorized. So what we have to do is go to the Authorization section in postman and then click on the type of authentication, We will use the Bearer Token type. What we have to do is to copy our token when we log in and then paste it in the input token field and then try to send a request to the user/me route again.

After we have added the authorization token and sent the request to /user/route we should get this:

Vue Authentication

Notice the Authorization tab is selected and in there, Bearer Token is chosen from the drop-down and on the right, a token is provided.
Now we have completed our Backend API…So let’s dive into our frontend.

Setting up the Frontend

  • Create a new Vuejs project using the vuejs Cli.
  • Installing and configuring packages.
  • Create the user interface(UI) for our application.
  • Configure the authentication service using our backend API.

Create a new Vuejs project using the vuejs Cli.

Now we need to move into the client directory that we created earlier and create a vuejs project. Open your terminal on your desktop and type this commands:

cd authapp
cd client
vue create authapp

You will be prompted to select some packages for your application, We will be using babel and vue-router for the application. After setting up your project you will have a vuejs project with this structure:

After installation, we need to serve the application. Open your terminal and type the following:

cd authapp
npm run serve

Automatically your app should be running on port 8080.

Installing and configuring packages.

Its time to install some packages for our frontend.we will install vue-jwt-decode,bootstrap,sweetalert and axios. So let’s open our terminal and type the following command:

npm i vue-jwt-decode axios bootstrap sweetalert
  • vue-jwt-decode : This is a JWT decoder for Vuejs.
  • Bootstrap : This is a CSS framework for developing responsive and mobile-first websites.
  • sweetalert: This is a beautiful, responsive, customizable and accessible replacement for JavaScript’s popup boxes.

After installing these packages we need to configure them in our /src/main.js file. You /src/main.js the file should look like this:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import axios from "axios";
import "bootstrap/dist/css/bootstrap.css";

const base = axios.create({
  baseURL: "http://localhost:4000"
});

Vue.prototype.$http = base;
Vue.config.productionTip = false;
new Vue({
  router,
  render: h => h(App)
}).$mount("#app");

Create the user interface(UI) for our application

 By default our application should look like this:

Vue Authentication

We need to clean up the UI. Let’s go to our src/components folder and delete the HelloWorld.vue file. After deleting it, we should get an error on our console. To fix that error go to src/views/home.vue and replace the code there with the codes below:

<template>
  <div>
    <h1>This is the home page</h1>
  </div>
</template>
<script>
export default {};
</script>
<style scoped></style>

Then go to src/App.vue and replace the code there with the codes below:

<template>
  <div id="app">
    <router-view />
  </div>
</template>

Now its time to create our login and register components. In the src/components directory we will create a new directory called auth .In that auth directory will create two vue files: login.vue and register.vue.

Type the following code in login.vue file:

<template>
  <div class="container">
    <div class="row">
      <div class="col-lg-6 offset-lg-3 col-sm-10 offset-sm-1">
        <form
          class="text-center border border-primary p-5"
          style="margin-top:70px;height:auto;padding-top:100px !important;"
          @submit.prevent="loginUser"
        >
          <input
            type="text"
            id="email"
            class="form-control mb-5"
            placeholder="Email"
            v-model="login.email"
          />
          <!-- Password -->
          <input
            type="password"
            id="password"
            class="form-control mb-5"
            placeholder="Password"
            v-model="login.password"
          />
          <p>
            Dont have an account??<router-link to="/register"
              >click here</router-link
            >
          </p>
          <!-- Sign in button -->
          <center>
            <button class="btn btn-primary btn-block w-75 my-4" type="submit">
              Sign in
            </button>
          </center>
        </form>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      login: {
        email: "",
        password: ""
      }
    };
  },
  methods: {
    async loginUser() {}
  }
};
</script>

Type this in your register.vue file:

<template>
  <div class="container">
    <div class="row">
      <div class="col-lg-6 offset-lg-3 col-sm-10 offset-sm-1">
        <form
          class="text-center border border-primary p-5"
          style="margin-top:70px;height:auto;padding-top:100px !important;"
          @submit.prevent="registerUser"
        >
          <input
            type="text"
            id="name"
            class="form-control mb-5"
            placeholder="Name"
            v-model="register.name"
            required
          />
          <input
            type="email"
            id="email"
            class="form-control mb-5"
            placeholder="Email"
            v-model="register.email"
            required
          />
          <!-- Password -->
          <input
            type="password"
            id="password"
            class="form-control mb-5"
            placeholder="Password"
            v-model="register.password"
          />
          <p>
            Already have an account??<router-link to="/"
              >click here</router-link
            >
            <!-- Sign in button -->
            <center>
              <button class="btn btn-primary btn-block w-75 my-4" type="submit">
                Sign in
              </button>
            </center>
          </p>
        </form>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      register: {
        name: "",
        email: "",
        password: ""
      }
    };
  },
  methods: {
    async registerUser() {}
  }
};
</script>

Now we need to create views for our login and register components.So go to src/view and create two files: login.vue and register.vue and type the following codes.

For login.vue:

<template>
  <div class="login">
    <login />
  </div>
</template>
<script>
import login from "@/components/auth/login";
export default {
  components: {
    login
  }
};
</script>

For register.vue:

<template>
  <div class="register">
    <register />
  </div>
</template>
<script>
import register from "@/components/auth/register";
export default {
  components: {
    register
  }
};
</script>

Now let’s register our routes. Go to src/router/index.js and replace the code with this:

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
Vue.use(VueRouter);
const routes = [
  {
    path: "/home",
    name: "home",
    component: Home
  },
  {
    path: "/",
    name: "login",
    component: () => import("../views/login.vue")
  },
  {
    path: "/register",
    name: "register",
    component: () => import("../views/register.vue")
  }
];
const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});
export default router;

You can test your routes and ensure that all views are working fine.

Configure the authentication service using our backend API.

Let’s start by registering a new user. Let’s modify the script that handles users’ registration. Go to src/components/auth/register and modify the registerUser method with this:

async registerUser() {
      try {
        let response = await this.$http.post("/user/register", this.register);
        console.log(response);
        let token = response.data.token;
        if (token) {
          localStorage.setItem("jwt", token);
          this.$router.push("/");
          swal("Success", "Registration Was successful", "success");
        } else {
          swal("Error", "Something Went Wrong", "error");
        }
      } catch (err) {
        let error = err.response;
        if (error.status == 409) {
          swal("Error", error.data.message, "error");
        } else {
          swal("Error", error.data.err.message, "error");
        }
      }
    }

Here we send a request to /user/register  with the data inputted by the user to our backend service, we created earlier. If no error was caught and the registration was successful it will return a promise which contains the user’s information alongside the token generated. The token is then stored in our local storage.

Now let’s Continue by Handling the login route. Go to src/components/auth/login and modify the loginUser method with this:

async loginUser() {
      try {
        let response = await this.$http.post("/user/login", this.login);
        let token = response.data.token;
        localStorage.setItem("jwt", token);
        if (token) {
          swal("Success", "Login Successful", "success");
          this.$router.push("/home");
        }
      } catch (err) {
        swal("Error", "Something Went Wrong", "error");
        console.log(err.response);
      }
    }

Here it is checking if the user’s details are correct, if it’s correct it takes the user to the home page where the user’s details are displayed else it throws an error.

Let’s work on the home page. The home page will display the details of the logged-in user.

Here is the code for the home page:

<template>
  <div>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        <a class="navbar-brand" href="#">Navbar</a>
        <button
          class="navbar-toggler"
          type="button"
          data-toggle="collapse"
          data-target="#navbarNav"
          aria-controls="navbarNav"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div
          class="collapse navbar-collapse justify-content-end"
          id="navbarNav"
        >
          <ul class="navbar-nav">
            <li class="nav-item active">
              <a class="nav-link" @click="logUserOut"> Logout</a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <section>
      <div class="container mt-5">
        <div class="row">
          <div class="col-md-12">
            <ul class="list-group">
              <li class="list-group-item">Name : {{ user.name }}</li>
              <li class="list-group-item">Email : {{ user.email }}</li>
            </ul>
          </div>
        </div>
      </div>
    </section>
  </div>
</template>
<script>
import VueJwtDecode from "vue-jwt-decode";
export default {
  data() {
    return {
      user: {}
    };
  },
  methods: {
    getUserDetails() {
      let token = localStorage.getItem("jwt");
      let decoded = VueJwtDecode.decode(token);
      this.user = decoded;
    },
    logUserOut() {
      localStorage.removeItem("jwt");
      this.$router.push("/");
    }
  },
  created() {
    this.getUserDetails();
  }
};
</script>
<style scoped></style>

We are making use of the vue-jwt-decode package. The package decodes the encrypted token which we get from our local storage and the returns the user’s details and after that, we display the user’s details. We also add a feature to logout a user. This removes the users token from local storage and takes the user back to the login page.

But this application isn’t still very secured. The user can still get access to the home page even when the is no token in the user’s local storage. So let fix that. We will write a little method on the vuejs router file that will check for authenticated users. So go to src/router/index and replace the code with the code below:

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
Vue.use(VueRouter);
const routes = [
  {
    path: "/home",
    name: "home",
    component: Home,
    meta: {
      requiresAuth: true
    }
  },
  {
    path: "/",
    name: "login",
    component: () => import("../views/login.vue")
  },
  {
    path: "/register",
    name: "register",
    component: () => import("../views/register.vue")
  }
];
const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (localStorage.getItem("jwt") == null) {
      next({
        path: "/"
      });
    } else {
      next();
    }
  } else {
    next();
  }
});
export default router;

So let me explain better. If you can see that we have added a new parameter in the home router object:

meta: {
      requiresAuth: true
    }
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (localStorage.getItem("jwt") == null) {
      next({
        path: "/"
      });
    } else {
      next();
    }
  } else {
    next();
  }
});

When the home route is called, it calls a method that checks if the is a token in the user’s local storage, if there is any it takes the user to the home page if the token is null it redirects the user back to the login page. With this method, a user cannot access the home route if not logged in.

Conclusion

In this guide, we have seen how to use jwt and vue-router to define checks on our routes and prevent users from accessing certain routes. We also saw how to redirect users to different parts of our application based on the authentication state. We also built a mini server with Node.js to handle user authentication.

You can access Code Source from here.


Share on social media

//