DEV Community

Cover image for How to setup email verification in Feathers.js - Frontend using Vue.js
Ivan Zaldivar
Ivan Zaldivar

Posted on • Edited on

How to setup email verification in Feathers.js - Frontend using Vue.js

This is the second part of How to setup email verification in Feathers.js If you have arrived here, without reading the previous one, click on the link, and then you come back..

Create a project.

We generate an app with vue cli
vue create feathers-email-verification-vue

Enter your project
cd feathers-email-verification-vue

Open your VS Code
code .

Execute server dev
npm run server

You will see this on your screen.
Screen for default of the project with Vue

Create pages.

Login.vue

<template>
  <h1>Login page</h1>
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({})
export default class LoginPage extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

Register.vue

<template>
  <h1>Register page</h1>
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({})
export default class RegisterPage extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

Add the pages to the Vue router

src > router > index.ts

const routes: Array<RouteRecordRaw> = [
  {
    path: "/register",
    name: "Register",
    component: () => import("@/views/Register.vue"),
  },
  {
    path: "/login",
    name: "Login",
    component: () => import("@/views/Login.vue"),
  },
  // Mores pages.
];
Enter fullscreen mode Exit fullscreen mode

Styles to components.

Now we will add styles to the pages, so that they do not look so simple.

Register.vue
Copy the following content in your component.

<template>
  <div class="container-fluid">
    <div
      class="row justify-content-center align-items-center"
      style="min-height: 85vh"
    >
      <div class="col-12 col-sm-8 col-md-6 col-lg-5 col-xl-4">
        <div class="card bg-white border-0 shadow p-2">
          <div class="card-body">
            <form>
              <div class="form-group py-2">
                <label for="fullname">Fullname</label>
                <input
                  placeholder="Exp: Ivan Zaldivar"
                  class="form-control my-1"
                  autocomplete="off"
                  name="fullname"
                  id="fullname"
                  autofocus
                  required
                />
                <small class="form-text text-muted">Enter your name</small>
              </div>
              <div class="form-group py-2">
                <label for="email">Email</label>
                <input
                  placeholder="Exp: abc@gmail.com"
                  class="form-control my-1"
                  autocomplete="off"
                  name="email"
                  id="email"
                  required
                />
                <small class="form-text text-muted">Enter your email</small>
              </div>
              <div class="form-group py-2">
                <label for="password">Password</label>
                <input
                  class="form-control my-1"
                  type="password"
                  name="password"
                  id="password"
                  required
                />
                <small class="form-text text-muted">Enter your password</small>
              </div>
            </form>
            <div class="pt-2">
              <button class="btn btn-primary border-0 py-2 px-3 w-100">
                Create account
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({})
export default class RegisterPage extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

Login.vue
Copy the following content.

<template>
  <section class="Login">
    <div class="container-fluid">
      <div
        class="row justify-content-center align-items-center"
        style="min-height: 85vh"
      >
        <div class="col-12 col-sm-8 col-md-6 col-lg-5 col-xl-4">
          <div class="card bg-white border-0 shadow p-2">
            <div class="card-header border-0 bg-white pb-0">
              <h2><strong>Login</strong></h2>
              <p class="text-muted">Log in and access our services.</p>
            </div>
            <div class="card-body pt-0">
              <form>
                <div class="form-group py-2">
                  <label for="email">Email</label>
                  <input
                    placeholder="Exp: abc@gmail.com"
                    class="form-control my-1"
                    autocomplete="off"
                    name="email"
                    id="email"
                    autofocus
                    required
                  />
                  <small class="form-text text-muted">Enter your email</small>
                </div>
                <div class="form-group py-2">
                  <label for="password">Password</label>
                  <input
                    class="form-control my-1"
                    type="password"
                    name="password"
                    id="password"
                    required
                  />
                  <small class="form-text text-muted"
                    >Enter your password</small
                  >
                </div>
              </form>
              <div class="pt-2">
                <button class="btn btn-primary border-0 py-2 px-3 w-100">
                  Login
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({})
export default class LoginPage extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

Preview of both components.

Auth Forms

By the way, if you are wondering about the layout, I am using Bootstrap for this example.

Setting Feathers.

Before starting with the validation of emails, it is necessary to download some packages.

npm i @feathersjs/feathers @feathersjs/authentication-client @feathersjs/rest-client

  • @feathersjs/feathers: Package main.
  • @feathersjs/authentication-client: Add authentication support.
  • @feathersjs/rest-client: Add support REST. This is necessary because somehow we have to communicate with the server. If you want to add communication in real time it will be necessary to install @feathersjs/socketio-client socket.io-client

Once the packages are installed, we create a config/feathers.ts file.

src > config > feathers.ts

import feathers, { Service } from "@feathersjs/feathers";
import authentication from "@feathersjs/authentication-client";
import _rest from "@feathersjs/rest-client";
// import socketio from "@feathersjs/socketio-client";
// import io from "socket.io-client";

import { User } from "@/services/auth.service";

// Initialize transport.
const rest = _rest("http://localhost:3030");

// Initialize socket.io
// const socket = io("http://localhost:3030");

// Initialize feathers app
const app = feathers();
// Add support real-time with socket.io
// app.configure(socketio(socket));

// Configure transport REST API.
app.configure(rest.fetch(window.fetch));
// Add support authentication-client.
app.configure(
  authentication({
    storage: window.localStorage,
    // By default it is <authentication> but, if it has changed, it is necessary to add its URL
    // path: "authentication"
  })
);

// Export authentication-cient
export const Auth = app.authentication;
// Export auth management.
export const AuthManagement: Service<any> = app.service("authManagement");
// Export user service.
export const UserService: Service<User> = app.service("users");

Enter fullscreen mode Exit fullscreen mode

Perfect, we've configured feathers.

Create a Auth Service.

This service will be responsible for taking care of all the authentication logic of our application. Copy The following content.

src > services > auth.service.ts

import { AuthenticationClient } from "@feathersjs/authentication-client/lib";
import { Auth, AuthManagement, UserService } from "@/config/feathers";

export interface User {
  _id: string;
  email: string;
  password: string;
  fullname: string;
}

export interface AuthResult {
  accessToken: string;
  authentication: {
    strategy: string;
  };
  user: User;
}

export class AuthService {
  private readonly auth: AuthenticationClient;

  constructor() {
    this.auth = Auth;
  }

  async login(email: string, password: string): Promise<AuthResult> {
    return this.auth.authenticate({
      strategy: "local",
      email,
      password,
    }) as Promise<AuthResult>;
  }

  async signup(user: Omit<User, "_id">): Promise<User> {
    return await UserService.create(user);
  }

  /**
   * Verify the email account.
   */
  async verifySignUp(token: string): Promise<User> {
    return (await AuthManagement.create({
      action: "verifySignupLong",
      value: token,
    })) as Promise<User>;
  }
}

Enter fullscreen mode Exit fullscreen mode

Note: Remember that in Feathers you can not only verify emails, also passwords, identity, etc. If you need more information visit the following link: https://github.com/feathersjs-ecosystem/feathers-authentication-management/blob/HEAD/docs.md In it you will find all the available strategies.

Add functionality to components.

Register.vue

src > views > Register.vue

<template>
  <div>
    <form action="">
      <div
        v-if="message"
        :class="`alert alert-${message.status} alert-dismissible fade show`"
        role="alert"
      >
        {{ message.text }}
      </div>
      <!-- Rest of the component. -->
    </form>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { AuthService } from "@/services/auth.service";

@Options({})
export default class RegisterPage extends Vue {
  message: Record<string, string> | null = null;
  user = {
    fullname: "",
    email: "",
    password: "",
  };

  async signup(): Promise<void> {
    try {
      const { fullname, email, password } = this.user;
      const { signup } = new AuthService();
      const data = await signup({ fullname, email, password });
      this.setMessage(
        `You have successfully registered, an email has been sent to ${data.email} to confirm that it is you. ✨`,
        "success"
      );
    } catch (error) {
      this.setMessage(error.message || "", "danger");
    }
  }

  setMessage(text: string, status: string): void {
    this.message = { text, status };
    setTimeout(() => (this.message = null), 5000);
  }
}
</script>

Enter fullscreen mode Exit fullscreen mode

Login.vue

src > views > Login.vue

<template>
  <div>
    <form action="">
      <div
        v-if="message"
        :class="`alert alert-${message.status} alert-dismissible fade show`"
        role="alert"
      >
        {{ message.text }}
      </div>
      <!-- Rest of the component -->
    </form>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";

import { AuthService } from "@/services/auth.service";

@Options({})
export default class LoginPage extends Vue {
  message: Record<string, string> | null = null;
  credentials: Record<string, string> = {
    email: "",
    password: "",
  };

  async login(): Promise<void> {
    try {
      const { email, password } = this.credentials;
      const auth = new AuthService();
      await auth.login(email, password);
      // Enter profile.
      this.$router.replace("/profile");
    } catch (error) {
      this.setMessage(error.message || "", "danger");
    }
  }

  setMessage(text: string, status: string): void {
    this.message = { text, status };
    setTimeout(() => (this.message = null), 5000);
  }
}
</script>

Enter fullscreen mode Exit fullscreen mode

Perfect, the logic has been added to the authentication components and display messages when something goes right or wrong.

Create email verification page.

This page is responsible for sending the request to verify the user's account. Copy the following content.

src > views > VerifyEmail.vue

<template>
  <div :class="`container-fluid bg-${notification.color}`">
    <div
      class="row justify-content-center align-items-center"
      style="min-height: 90vh"
    >
      <div class="col-12 col-sm-8 col-md-6 col-xl-4">
        <div :class="`card bg-white border-0`">
          <div class="card-body">
            <div class="text-center w-100">
              <img
                :src="require(`@/assets/${notification.picture}`)"
                alt="Picture"
              />
            </div>
            <h1 class="text-center mt-3">{{ notification.title }}</h1>
            <p class="text-muted text-center">{{ notification.subtitle }}</p>
            <div
              v-if="notification.status === 'pending'"
              class="progress"
              style="height: 10px"
            >
              <div
                class="progress-bar progress-bar-striped progress-bar-animated"
                role="progressbar"
                aria-valuenow="100"
                aria-valuemin="0"
                aria-valuemax="100"
                style="width: 100%"
              ></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import { LocationQuery } from "vue-router";

import { AuthService } from "@/services/auth.service";

enum STATUS {
  PENDING = "pending",
  COMPLETED = "completed",
  ERROR = "error",
}

@Options({})
export default class VerifyEmail extends Vue {
  notification: Record<string, string | boolean> | null = {
    picture: "picture-one.png",
    title: "Email verification",
    subtitle: "Your request is being processed.",
    status: STATUS.PENDING,
  };

  created(): void {
    // We carry out the verification.
    this.verifyAccount(this.$route.query);
  }

  private async verifyAccount(query: LocationQuery): Promise<void> {
    try {
      // Instance class.
      const { verifySignUp } = new AuthService();
      // Send request to the server.
      const user = await verifySignUp((query.token as string) || "");
      // Show sucess message.
      this.notification = {
        picture: "picture-three.png",
        title: "Verified account",
        subtitle: `${user.fullname} Your account has been verified successfully`,
        color: "primary",
        status: STATUS.ERROR,
      };
    } catch (error) {
      // Show failure message.
      this.notification = {
        picture: "picture-two.png",
        title: "Error",
        subtitle: error.message || "An error occurred during the operation.",
        color: "danger",
        status: STATUS.ERROR,
      };
    }
  }
}
</script>

<style scoped>
.card {
  border-radius: 20px;
}
img {
  width: 100px;
  height: 100px;
  object-fit: contain;
}
</style>

Enter fullscreen mode Exit fullscreen mode

We add the page to the vue routing.

src > router > index.ts

const routes: Array<RouteRecordRaw> = [
  {
    path: "/verifyEmail",
    name: "Verify-Email",
    component: () => import("@/views/VerifyEmail.vue"),
  },
  // Mores pages...
];

Enter fullscreen mode Exit fullscreen mode

Now, it is time to test what we have done.

  1. We create an account.
  2. We get a link to verify the email.
  3. We click on the link, and the email is verified.
  4. We received a confirmation email.

Previews email verification

Excellent! We have finished the client side verification with Vuejs. In the next article, we will do it with Angular. So don't miss it. Bye.

Articles previosly.

How to setup email verification in Feathers.js

In case you have any questions, I leave you the source code: https://github.com/IvanZM123/feathers-email-verification-vue

Follow me on social networks.

Top comments (0)