TL;DR: To date, passwords and passcodes remain the most used means of gaining access to our protected accounts on the online platforms we love. However, if you are like me with loads of accounts across various online platforms, remembering all passwords can quickly become a herculean task mentally especially when certain online platforms require you to provide strong passwords with special characters, capitalized words, and numbers like you're trying to gain access to Fort Knox. Wouldn't it be wonderful not to have to remember passwords when logging into an application? That is the type of application we will be building in this article.

What You Will Build

In this article, you will be building an application that will help users create events and organize a checklist of tasks that need to be completed to make the event a success.

An interesting angle to this application is that users will not need a password to log into it. Anytime users want to have access to this application, they are sent a one-time token, which they can then use to gain access to the app. This will be made possible by an amazing Auth0 product called Passwordless.

You can find the final GitHub repo here.

"Wouldn't it be wonderful not to have to remember passwords when logging into an application? @auth0's Passwordless makes that possible!"

Prerequisites

To follow along with this article, a few things are required:

  1. Basic knowledge of Vue.js
  2. Nodejs installed on your system
  3. Vue CLI installed on your system
  4. A Firebase account

Creating the Auth0 Application

The first step is to create the Auth0 application that you will be using to handle authentication in the application. Head to Auth0's website and sign up for a free Auth0 account or log into an existing account.

Once logged in, click on Applications on the left-hand side menu. On the Applications page, click on the big orange CREATE APPLICATION button.

On the Create Application dialog that pops up, enter an appropriate name for your application (in this case Passwordless Event Planner) and select Single Page Web Applications from the options below the application name field.

Now click the Create button to complete the process.

After the successful creation of the application, go to the Settings section of your newly created app. In the Allowed Callback URLs, Allowed Web Origins, Allowed Logout URLs and Allowed Origins (CORS) fields, enter http://localhost:8080. This address is the default address of the Vue.js application you will be creating later on.

Once you've entered these values, scroll down and hit the SAVE CHANGES button.

Enabling Passwordless Connections

To make use of Auth0's Passwordless service, you need to enable it and ensure that it is activated for your application.

To do this, go to the side menu on your Auth0 Dashboard and click on Connections, then under that, click on Passwordless. On the Passwordless Connections page, enable the Email option as we will be sending the one-time token to the user's email address.

Enable Passwordless - Auth0 Dashboard

Another option available is the SMS option. This option will have the one-time token sent to the user's phone number. This option requires configuring an SMS gateway like Twilio to enable sending SMS.

The next step is to make sure your application is enabled to use the Passwordless service. Click on the Email tab you just enabled. On the modal, click the Applications tab to confirm that your application is enabled. If not, enable it as shown below:

Enable Application - Auth0 Console

The modal also has a Settings tab where you can customize some of the default values for the email that will be sent to the user containing the one-time password. Make sure you know what you are doing before touching any of these values. The default values are good enough in most cases.

Ensure that you click the Save button when you make any change on the modal.

"Adding in @auth0's Passwordless is so easy!"

Creating the Firestore Database

Next up, you need to create a Firebase project and Cloud Firestore database.

Head over to your Firebase console and create a new project using any preferred name.

Next, create a new database by clicking on Database under the Develop section of the side menu. This displays a page where you have the option of creating Cloud Firestore or Realtime Database database, go ahead and create a Cloud Firestore database.

On the Secure rules section of your database creation modal, select Start in test mode to enable read and write access to your database (ensure to set up security rules in production). Click Next.

On the Set Cloud Firestore location section, go with the default location and hit the Done button to complete the database creation process. Once done, you will be redirected to the Database page.

The next line of action is to create a collection in your database. Click the Start Collection button to create one. Enter the name eventsdb in the Collection ID field as shown below, this will represent your collection name:

Create Collection on the Firebase Console

Click the Next button to define the very first document in this collection.

Our collection will contain documents with the following fields:

  • user: Email of the user
  • event_name: Title for the event, e.g Road Trip
  • due_date: The date the event will take place
  • tasks: An array of tasks that need to be completed before the event

For the first document, leave the Document ID field blank and enter some dummy values for the other fields as shown below:

Create Collection

Click Save and you are all set.

Before leaving your console, you will need some information about your project and database that will be needed later in the course of the exercise:

  • Project ID: This can be found by clicking the cog icon on the side menu and going to the Project Settings page to retrieve it.
  • Database URL: https://YOUR_PROJECT_NAME.firebaseio.com/

Scaffolding the Vue Project

Create a new Vue application by running the following command in the folder where you want your application to be located.

vue create event-planner

When the Please pick a preset? prompt comes up in the interactive CLI, select default.

After your selection, the Vue CLI begins scaffolding your new Vue.js application. Once finished, use the following command to get into the project:

cd event-planner

Installing a component library

Once the scaffolding process is done, the next step is to install a components library. Why do you need a components library? Glad you asked. The project requires a calendar and modal component; thus, it makes sense to install a component library that has these (and many more) components.

For this purpose, you will be installing the Vuetify UI Library. To install the library, run the following command:

vue add vuetify

At some point in the installation you will be prompted to select a preset, simply go with the Default preset to continue the installation.

This installation makes a few changes to your Vue.js application, and these changes help set up the vuetify library. The most important one is a plugins folder in the src folder containing a Vue.js plugin set up for vuetify.

Creating an Auth0 Passwordless Authentication Plugin

To work with Passwordless, you will need the Auth0 Lock NPM package. This package contains all you need for authenticating your application with Passwordless.

To use this package effectively in the Vue.js application just scaffolded, a Vue.js authentication plugin will be created and exported to be used within the application. This plugin will make use of the Auth0 Lock package to set up authentication functions and parameters that can be made use of in the application pages and components.

In the root of your project, install the Auth0 Lock package by running the command:

npm install auth0-lock

Note: If after installing this package or at any point in this exercise, you encounter an error regarding core-js, simply run npm install core-js to fix the issue.

Within the src folder, create a new folder named auth and within this folder, create a file named index.js and place the following code inside the file:

// src/auth/index.js

import Vue from "vue";
import { Auth0LockPasswordless } from "auth0-lock";
let instance;
export const getInstance = () => instance;
export const useAuth0 = options => {
  if (instance) return instance;
  instance = new Vue({
    data() {
      return {
        loading: true,
        isAuthenticated: false,
        user: {},
        auth0Lock: null,
        popupOpen: false,
        error: null,
        accessToken: null
      };
    },
    methods: {
      login(loginOptions) {
        this.popupOpen = true;
        this.auth0Lock.show(loginOptions);
      },
      checkSession() {
        return new Promise((resolve, reject) => {
          this.auth0Lock.checkSession({}, (error, authResult) => {
            if (error || !authResult) {
              this.auth0Lock.show();
            } else {
              this.auth0Lock.getUserInfo(
                authResult.accessToken,
                (error, profile) => {
                  if (error) {
                    reject(error);
                  }
                  this.setProfileDetails(authResult.accessToken, profile);
                  resolve(profile);
                }
              );
            }
          });
        });
      },
      logout(o) {
        return this.auth0Lock.logout(o);
      },
      setProfileDetails(token, profile) {
        this.accessToken = token;
        this.user = profile;
        this.isAuthenticated = true;
      }
    },
    async created() {
      this.auth0Lock = new Auth0LockPasswordless(
        options.clientId,
        options.domain
      );
      this.auth0Lock.on("authenticated", authResult => {
        this.auth0Lock.getUserInfo(
          authResult.accessToken,
          (error, profileResult) => {
            if (error) {
              throw error;
            }
            this.setProfileDetails(authResult.accessToken, profileResult);
          }
        );
      });
    }
  });
  return instance;
};
// Create a simple Vue plugin to expose the wrapper object throughout the application
export const Auth0Plugin = {
  install(Vue, options) {
    Vue.prototype.$auth = useAuth0(options);
  }
};

This service creates a wrapper object around the Auth0 Lock SDK and implements a Vue.js plugin that exposes this wrapper object to the rest of the application. This wrapper API consists of user authentication methods like:

  • login: Displays the Auth0 Passwordless login pop up
  • logout: Ends the user's session
  • checkSession: Checks if a user's session is still active

Useful variables like isAuthenticated and user are also exposed to represent the user's authenticated state and profile, respectively.

The next step is to enable the application to use this plugin. First, you need a place to store your Auth0 credentials, which the plugin makes use of in creating a new instance of the SDK.

Create a new file named auth_config.json at the root of your project and paste the following snippet inside:

{
  "domain": "YOUR_AUTH0_DOMAIN",
  "clientId": "YOUR_APP_ID"
}

Replace the placeholders with your Auth0 details and ensure to ignore this file in .gitignore.

Setting Up Connection to the Firestore Database

Before setting up the authentication plugin to be used in the application, you need to also create a service that would allow the application to make use of the Firestore database.

First, create a file named firebase_auth.json at the root of your project and place the following content in it:

{
  "project_id": "YOUR_FIREBASE_PROJECT_NAME",
  "db_url": "https://YOUR_FIREBASE_PROJECT_NAME.firebaseio.com/",
  "events_db": "eventsdb"
}

Replace the project name (for example, passwordless-event) with the one you created from your Firebase console earlier and ensure to ignore this file in .gitignore.

To work with Firebase in Vue.js, we will need the firebase and vuefire npm packages.

The Vuefire package makes working with Firebase in Vue.js a breeze by providing the necessary APIs that abstract a lot of boilerplate, error-prone code. For more details on how to work with Vuefire, check out the documentation here.

Now install the firebase and vuefire packages by running the following command:

npm install vuefire firebase

Next, in the src folder, create a file named db.js and place the following code in it:

// src/db.js

import firebase from "firebase/app";
import "firebase/firestore";
import { project_id, db_url } from "../firebase_auth.json";
const db = firebase
  .initializeApp({ projectId: project_id, databaseURL: db_url })
  .firestore();
export default db;

Perfect!

Now you can set up the application to use both the authentication plugin we created earlier and the firebase service by replacing the code in the main.js file with the following:

// src/main.js

import Vue from "vue";
import App from "./App.vue";
import vuetify from "./plugins/vuetify";
import { domain, clientId } from "../auth_config.json";
import { Auth0Plugin } from "./auth";
import { firestorePlugin } from "vuefire";

// Install the authentication plugin here
Vue.use(Auth0Plugin, {
  domain,
  clientId
});
Vue.use(firestorePlugin);
Vue.config.productionTip = false;
new Vue({
  vuetify,
  render: h => h(App)
}).$mount("#app");

In the above code, the authentication plugin is set up using the Auth0 client ID and domain. The Firestore and vuetify plugins are also imported and set up in the application.

Building the Event Planner

It's finally time to create the user-facing part of the application. The application consists of a single page with the following features:

  1. A welcome page for a non-authenticated user
  2. Display of the Auth0 Passwordless login for non-authenticated users
  3. A form to create a new event
  4. A list that displays all events
  5. A button on each event to add tasks to the event
  6. A button to sign out of the application

All that is needed for the application is now in place. Go into the App.vue file and replace everything inside it with the code below:

<!-- src/App.vue -->
<template>
  <v-app>
    <v-app-bar app color="primary" dark>
      <div class="d-flex align-center">
        <v-toolbar-title>
        Event Planner
          <span v-if="$auth.isAuthenticated">
            ( {{ $auth.user.name }} )
          </span>
        </v-toolbar-title>
      </div>
      <v-spacer></v-spacer>
      <v-btn v-if="!$auth.isAuthenticated" @click="login()" text>
        Login
      </v-btn>
      <v-btn v-else @click="logout()" text>
        Logout
      </v-btn>
    </v-app-bar>
    <v-content>
      <v-container>
        <v-row class="text-center" v-if="!$auth.isAuthenticated">
          <v-col class="mb-4">
            <h1 class="display-2 font-weight-bold mb-3">
              Welcome to Event Planner
            </h1>
            <p class="subheading font-weight-regular">
              Manage all your personal Events in One convenient location
            </p>
            <p>
              <v-btn color="primary" @click="login()">
                Login with Auth0
              </v-btn>
            </p>
          </v-col>
        </v-row>
        <v-row v-else>
          <v-col cols="6">
            <h2>Events</h2>
            <div>
              <v-list two-line>
                <v-list-item-group v-model="selectedEvent" multiple>
                  <template v-for="(item, index) in events">
                    <v-list-item :key="item.event_name">
                      <template>
                        <v-list-item-content>
                          <v-list-item-title
                            class="blue--text"
                            v-text="item.event_name">
                          </v-list-item-title>
                          <v-list-item-subtitle
                            class="text--primary"
                            v-text="item.description">
                          </v-list-item-subtitle>
                          <v-list-item-subtitle>
                            {{item.tasks.length}} Task(s)
                          </v-list-item-subtitle>
                        </v-list-item-content>
                        <v-list-item-action>
                          <v-list-item-action-text
                            v-text="item.due_date">
                          </v-list-item-action-text>
                          <v-btn
                            @click="manageTasks(item)"
                            text
                            color="primary"
                            class="mt-1">
                            Manage Tasks
                          </v-btn>
                        </v-list-item-action>
                      </template>
                    </v-list-item>
                    <v-divider :key="index"></v-divider>
                  </template>
                </v-list-item-group>
              </v-list>
            </div>
          </v-col>
          <v-col cols="4" class="offset-md-1">
            <h2>Create Event</h2>
            <v-form ref="form">
              <v-text-field
                v-model="eventForm.event_name"
                label="Event Name"
                required>
              </v-text-field>
              <v-text-field
                v-model="eventForm.description"
                label="Describe event"
                required>
              </v-text-field>
              <v-menu
                v-model="menu"
                :close-on-content-click="false"
                :nudge-right="40"
                transition="scale-transition"
                offset-y
                min-width="290px">
                <template v-slot:activator="{ on }">
                  <v-text-field
                    v-model="eventForm.due_date"
                    label="Pick a Date"
                    readonly
                    v-on="on">
                  </v-text-field>
                </template>
                <v-date-picker
                  v-model="eventForm.due_date"
                  @input="menu = false">
                </v-date-picker>
              </v-menu>
              <v-btn
                color="success"
                class="mr-4"
                @click="addEvent()"
                :disabled="processing">
                {{ processing ? "Adding..." : "Add Event" }}
              </v-btn>
            </v-form>
          </v-col>
        </v-row>
      </v-container>
      <v-dialog v-model="dialog" persistent max-width="600px">
        <v-card>
          <v-card-title>
            <span class="headline">Tasks</span>
          </v-card-title>
          <v-card-text>
            <v-container>
              <v-row>
                <v-col cols="12">
                  <v-list>
                    <template v-for="(task, index) in eventInView.tasks">
                      <v-list-item :key="task.title">
                        <v-list-item-content>
                          <v-list-item-title
                            v-text="task.title"
                            :class="{ strike: task.done }">
                          </v-list-item-title>
                        </v-list-item-content>
                        <v-list-item-avatar>
                          <v-checkbox v-model="task.done"></v-checkbox>
                        </v-list-item-avatar>
                      </v-list-item>
                      <v-divider :key="index"></v-divider>
                    </template>
                  </v-list>
                </v-col>
                <v-col cols="12">
                  <v-text-field
                    label="Enter Task Name"
                    required
                    v-model="newTask">
                  </v-text-field>
                  <p>
                    <v-btn @click="addNewTask()">
                      Add Task
                    </v-btn>
                  </p>
                </v-col>
              </v-row>
            </v-container>
          </v-card-text>
          <v-card-actions>
            <v-spacer></v-spacer>
            <v-btn color="blue darken-1" text @click="dialog = false">
              Close
            </v-btn>
            <v-btn
              color="blue darken-1"
              text
              @click="saveEvent()"
              :disabled="updating">
              {{ updating ? "Saving..." : "Save Event" }}
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-dialog>
    </v-content>
  </v-app>
</template>
<script>
import db from "./db.js";
import { events_db } from "../firebase_auth.json";
export default {
  name: "App",
  data() {
    return {
      eventForm: {},
      menu: false,
      events: [],
      selectedEvent: [],
      dialog: false,
      eventInView: {},
      newTask: null,
      processing: false,
      updating: false,
      user: this.$auth.user
    };
  },
  async created() {
    let profile = await this.$auth.checkSession();
    this.$bind(
      "events",
      db.collection(events_db).where("user", "==", profile.email || "")
    );
  },
  methods: {
    login() {
      this.$auth.login();
    },
    logout() {
      this.$auth.logout();
    },
    async addEvent() {
      if (
        this.eventForm.event_name &&
        this.eventForm.description &&
        this.eventForm.due_date
      ) {
        this.eventForm.tasks = [];
        this.eventForm.user = this.$auth.user.email;
        this.processing = true;
        await db.collection(events_db).add(this.eventForm);
        this.processing = false;
        //Clear form
        this.eventForm = {};
      } else {
        this.showModal("Error", "All fields are required");
      }
    },
    manageTasks(event) {
      this.eventInView = event;
      this.dialog = true;
    },
    addNewTask() {
      let taskId;
      if (this.eventInView.tasks.length > 0) {
        taskId =
          this.eventInView.tasks[this.eventInView.tasks.length - 1].id + 1;
      } else {
        taskId = 1;
      }
      let newTask = {
        id: taskId,
        title: this.newTask,
        done: false
      };
      this.eventInView.tasks.push(newTask);
      this.newTask = null;
    },
    async saveEvent() {
      this.updating = true;
      //Update tasks in firestore
      await db
        .collection(events_db)
        .doc(this.eventInView.id)
        .update({
          tasks: this.eventInView.tasks
        });
      this.updating = false;
      this.dialog = false;
    }
  }
};
</script>
<style scoped>
.strike {
  text-decoration: line-through;
}
</style>

Now take a deep breath as we break down the code above starting with the Vue.js instance.

The required services and files are first imported:

import db from "./db.js";
import { events_db } from "../firebase_auth.json";

The required application variables are then declared in the data parameter of the object.

data() {
  return {
    eventForm: {},
    menu: false,
    events: [],
    selectedEvent: [],
    dialog: false,
    eventInView: {},
    newTask: null,
    processing: false,
    updating: false,
    user: this.$auth.user
  };
}

Then, in the created lifecycle method, the user session is checked, if the session is still active, the user's profile is returned and used to load the saved accounts from the Firestore database and bind it to the events data variable:

async created() {
  let profile = await this.$auth.checkSession();
  this.$bind(
    "events",
    db.collection(events_db).where("user", "==", profile.email || "")
  );
}

Now to the methods in the application:

  1. login: Calls the login method in the authentication service to pop up a new Auth0 Passwordless login window
  2. logout: Calls the logout method in the authentication service to end the user's session
  3. addEvent: Validates the form entry, and then adds a new event to our Firestore database
  4. manageTasks: Displays the tasks modal to add and check off completed tasks
  5. addNewTask: Adds a new task to the selected event
  6. saveEvent: Save an event when new tasks have been added or existing tasks have been checked off

In the application template, Vuejs's v-if is used to manage the authenticated and non-authenticated interfaces displayed to the user. When a non-authenticated user visits the page, he/she sees a welcome screen and a Log In button.

When authenticated, the user sees a form to create new events and a list displaying saved events with each event having a Manage Tasks button for the user to add new tasks or check off completed ones.

Running the Application

Time to take the application for a spin. Serve the application in your browser by running the following command:

npm run serve

If all instructions have been properly followed you should see the screen below at the address http://localhost:8080:

Note: As stated earlier in the article, if you run into any error regarding core-js, simply run the following command to install the package:

npm install core-js

Application Login - Event Planner Login View

The Passwordless login popup prompts the user for an email address. Simply enter your email address and hit Submit to get the one-time token in your mail, then enter it in the next screen as shown below:

Application Login - Entering in One Time Password

Upon successful login, you should see the form. Go ahead and create some events:

Application Home Page View with some added events

Click on Manage Tasks on any of the events to manage the tasks under that event.

Managing tasks in application

Conclusion

Not having to remember the password to an application is a dream that has been made a reality using Passwordless by Auth0. There is so much more you can do with Passwordless, so feel free to check the Auth0 Passwordless home page for more information.

I hope you enjoyed this article as much as I did writing it. Happy coding!

About Auth0

Auth0 provides a platform to authenticate, authorize, and secure access for applications, devices, and users. Security and development teams rely on Auth0's simplicity, extensibility, and expertise to make identity work for everyone. Safeguarding more than 4.5 billion login transactions each month, Auth0 secures identities so innovators can innovate, and empowers global enterprises to deliver trusted, superior digital experiences to their customers around the world.

For more information, visit https://auth0.com or follow @auth0 on Twitter.