close icon
Vue

How to Create a Vue Plugin

Learn how to create a Vue plugin to manage user authentication

November 18, 2020

The focus of this tutorial is to help developers learn how to create a Vue plugin from scratch. You'll create a plugin that enables you to manage user authentication for Vue applications.

You will use the Auth0 SPA SDK as the engine to power Vue user authentication. Your Vue plugin will wrap the functionality of the SDK, which already provides a high-level API to handle a lot of authentication implementation details.

The implementation of user authentication requires different components to work. You need buttons to trigger login and logout events. Some components may have methods that need to request protected resources from an API. As such, you need to add the user authentication functionality to your Vue application at a global level — a task where Vue plugins shine.

What Are Vue Plugins?

The main goal of a Vue plugin is to expose functionality at a global level in your Vue application. While there is no strictly defined scope for Vue plugins, these are their most common use cases:

  • Add some global methods or properties.

  • Add one or more global assets, such as directives or filters.

  • Add component options using a global mixin.

  • Add methods to a Vue instance by attaching them to the Vue.prototype.

In this tutorial, you'll create a user authentication plugin that provides an API of its own while implementing a combination of some of the use cases mentioned above : adding global methods and properties and enhancing the Vue.prototype.

Vue plugins can take advantage of Vue's reactive nature. In the case of user authentication, a Vue plugin lets you create a reusable and reactive wrapper around the Auth0 SPA SDK, making it much easier to work with the asynchronous methods of the SDK.

You can easily implement that user authentication reactive wrapper using a Vue object. Let's get started!

Get the Starter Application

I have created a starter project using the Vue CLI to help you learn Vue security concepts through hands-on practice. The starter application uses Bootstrap with a custom theme to take care of your application's styling and layout. You can focus on building Vue components to secure your application.

As such, clone the auth0-vue-sample repository on its starter branch to get started:

git clone -b starter git@github.com:auth0-blog/auth0-vue-sample.git

Once you clone the repo, make auth0-vue-sample your current directory:

cd auth0-vue-sample

Install the Vue project dependencies:

npm install

Run the Vue project:

npm run serve

Finally, open the Vue starter project, auth0-vue-sample, in your favorite code editor or IDE.

Create the Plugin Template

Since the Auth0 SPA SDK is at the heart of your user authentication strategy, execute the following command to install it:

npm install @auth0/auth0-spa-js

You need a place to host your authentication-related files to keep your application organized. Hence, create an auth directory within the src directory:

mkdir src/auth

Create an auth0-plugin.js file within the src/auth directory to define your Vue plugin module:

touch src/auth/auth0-plugin.js

Populate src/auth/auth0-plugin.js with the following template to make it easier for you to follow the steps on how to create a Vue plugin:

/**
 *  External Modules
 */

import Vue from "vue";

/**
 *  Vue Instance Definition
 */

let instance;

export const getInstance = () => instance;

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue();

  return instance;
};

/**
 *  Vue Plugin Definition
 */

The template defines at a high level the architecture of your plugin module:

  • You import Vue.

  • You define a local instance variable to hold your Vue instance definition.

  • You create a getter method for your local instance — a JavaScript closure.

  • You define a useAuth0(), which is a function that initializes the local instance — as you can see, closures are a powerful pattern in JavaScript.

    • If you already have initialized your local instance, you return that instance.

    • Otherwise, you initialize instance with the value of a new Vue instance, which can take a configuration object that you'll define later.

    • Finally, you return the freshly initialized instance.

  • Later on, at the end of the module, you'll define your authentication Vue plugin, which will consume the local instance through useAuth0().

With this Vue plugin template in place, you are ready to start filling it in.

Define the Vue Plugin Properties and Methods

Locate the Vue Instance Initialization section, and pass an options object to the new Vue() constructor:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue({ data: {}, methods: {} });

  return instance;
};

When you create a Vue instance, Vue adds all the properties found in the data property to its reactivity system. Vue mixes all the properties found in the methods property into the Vue instance. All the mixed methods have their this context bound to the created Vue instance automatically.

The data properties will hold the state of your authentication plugin. Those state properties should answer the following questions:

  • Is there an active Auth0 client?
  • Has the Auth0 SPA SDK loaded?
  • Is the user authenticated?
  • What is the profile information of the user?
  • Has an error occurred during the authentication process?

As such, define the data property as a function that returns an object with the state properties that answer those questions:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue({
    data() {
      return {
        auth0Client: null,
        isLoading: true,
        isAuthenticated: false,
        user: {},
        error: null,
      };
    },
    methods: {},
  });

  return instance;
};

Each property returned by data() is initialized. You start by assuming that the Auth0 SPA SDK has not loaded (there's no Auth0 client), and the user has not logged in yet (there's no user profile information available). These are safe initial assumptions to make. this.auth0Client will hold an instance of the Auth0 SPA SDK and give you access to all of its time-saving methods.

To define the methods property for the Vue instance, it helps to understand that user authentication is a mechanism to monitor who is accessing your application and control what they can do. For example, you can prevent users who have not logged in from accessing parts of your application. In that scenario, Auth0 can act as your application bouncer.

A bouncer is a person employed by a nightclub or similar establishment to prevent troublemakers from entering or to eject them from the premises. Vue security is not too different from nightclub security.

If users want to enter a protected route from your application, Auth0 will stop them and ask them to present their credentials. If Auth0 can verify who they are and that they are supposed to go in there, Auth0 will let them in. Otherwise, Auth0 will take them back to a public application route.

Now, it's important to reiterate that the authentication process won't happen within your application layer. Your Vue application will redirect your users to the Auth0 Universal Login page, where Auth0 asks for credentials and redirects the user back to your application with the result of the authentication process.

You'll need the following methods to cover the scenario above:

  • A loginWithRedirect() method to redirect your users to Auth0 for logging in.

  • A handleRedirectCallback() method to handle the redirect from Auth0 back to your Vue application and to consume the results of the user authentication process.

Have you heard the term "what goes up must come down"? I have also heard "who logs in must log out". As such, you also need the following method:

  • A logout method to log users out and remove their session on the authorization server.

Finally, whenever your Vue application needs to request protected resources from an API, it needs to do so securely. You can use access tokens to allow your Vue application to access an API.

Your Vue application can receive an access token after a user successfully authenticates and authorizes access. It passes the access token as a credential when it calls the target API. The passed token informs the API that the bearer of the token has been authorized to access the API and perform specific actions on behalf of a user.

Thus, with security in mind, you need a method that returns the access token. Additionally, if the token is invalid or missing, the method should get a new one. Usually, getting new access tokens requires the user to log in again. However, The Auth0 SPA SDK lets you get one in the background without interrupting the user. You can implement a method to getTokenSilently ()... 🤫😶

Now that you have a vision of the methods that you need to implement user authentication in Vue, let's add entries for them in your instance:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue({
    data() {
      return {
        auth0Client: null,
        isLoading: true,
        isAuthenticated: false,
        user: {},
        error: null,
      };
    },
    methods: {
      async handleRedirectCallback() {},

      loginWithRedirect() {},

      logout() {},
      
      getTokenSilently() {},
    },
  });

  return instance;
};

Let's define each method and explain what they do.

Handle the Auth0 redirect

Define the handleRedirectCallback() method as follows:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue({
    data() {...},
    methods: {
      async handleRedirectCallback() {
        this.isLoading = true;
        try {
          await this.auth0Client.handleRedirectCallback();
          this.user = await this.auth0Client.getUser();
          this.isAuthenticated = true;
        } catch (error) {
          this.error = error;
        } finally {
          this.isLoading = false;
        }
      },

      loginWithRedirect() {},

      logout() {},
      
      getTokenSilently() {},
    },
  });

  return instance;
};

The handleRedirectCallback() method is a wrapper method that uses the async and await keywords and a try/catch block to handle asynchronous code cleanly. It starts by setting the isLoading state property to true. Then, you invoke the this.auth0Client.handleRedirectCallback() SDK method to let your Auth0 SPA SDK instance handle the redirect event and get you the results of the authentication process.

If this.auth0Client.handleRedirectCallback() resolves successfully, you can use the this.auth0Client.getUser() SDK method to populate the user state property and set the isAuthenticated property to true.

The getUser() SDK method returns the user profile information.

You catch any error that happens during the async operations within handleRedirectCallback() and store the error information in the error state property. As a best practice, you should inform the user if an error happened through the user interface. Doing so lets you set this.isLoading to false whether you succeeded in handling the redirect event or not. You don't want to leave your users in the blank.

Logs users in

Next, define the loginWithRedirect() method as follows:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue({
    data() {...},
    methods: {
      async handleRedirectCallback() {...},

      loginWithRedirect(options) {
        return this.auth0Client.loginWithRedirect(options);
      },

      logout() {},
      
      getTokenSilently() {},
    },
  });

  return instance;
};

The loginWithRedirect() method is a simple wrapper method that leverages the this.auth0Client.loginWithRedirect SDK method to carry out the login transaction. It takes an options object that lets you customize the login user experience. Check out the RedirectLoginOptions document to learn more about the available properties.

Logs users out

Next, define the logout() method as follows:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue({
    data() {...},
    methods: {
      async handleRedirectCallback() {...},

      loginWithRedirect(options) {...},

      logout(options) {
        return this.auth0Client.logout(options);
      },
      
      getTokenSilently() {},
    },
  });

  return instance;
};

The logout() method is also a simple wrapper method that uses the this.auth0Client.logout() SDK method to carry out the logout transaction. It takes an options object to customize the user logout experience. As before, you can check the LogoutOptions document to learn more about the available properties.

Get an access token

Finally, define the getTokenSilently() method as follows:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = () => {
  if (instance) return instance;

  instance = new Vue({
    data() {...},
    methods: {
      async handleRedirectCallback() {...},

      loginWithRedirect(options) {...},

      logout(options) {...},
      
      getTokenSilently(o) {
        return this.auth0Client.getTokenSilently(o);
      },
    },
  });

  return instance;
};

Once again, the getTokenSilently() method wraps the this.auth0Client.getTokenSilently() SDK method, which gets your Vue application a new access token under the hood without requiring the user to log in again.

These instance methods that you have defined all rely on the this.auth0Client property that represents an Auth0 SPA SDK instance, but how can you initialize that SDK instance? You can use Vue lifecycle hooks!

Use the created() Hook with Vue Plugins

You can use the created() Vue lifecycle hook to instantiate the Auth0 SPA SDK client. As the name may imply, Vue calls created() after it creates the Vue instance. Vue has finished processing all the instance options at that point, which means that Vue has set up all data observation and methods.

I will risk sounding like a broken record but recall that Vue takes your users to an Auth0 page to log in, and then Auth0 takes your users back to your Vue application with the results of the authentication process. But, where are those authentication process results, you may ask? In the URL of the application.

Under the hood, the Auth0 SPA SDK implements the Authorization Code Flow with Proof Key for Code Exchange (PKCE) and does most of the heavy-lifting for you.

In a nutshell, if the user login goes well, you'll receive an HTTP 302 response similar to this one:

HTTP/1.1 302 Found
Location: https://YOUR_APP/callback?code=AUTHORIZATION_CODE&state=xyzABC123

A 302 response indicates that the resource requested has been temporarily moved to the URL given by the Location header.

As you can see, when a user is returning to your Vue app after authentication, the URL will have code and state parameters in the URL. In that case, you need to call the this.auth0Client.handleRedirectCallback() SDK method to grab those parameters and finish the authentication process. It will exchange the code for tokens: an ID token that has user profile information and an access token that you can use to make secure API calls.

Now, Vue will need to remember where users wanted to go before login and, if authentication were successful, take them to that route. You'll need to access the pre-authentication state of the application and use the window.history to complete that task.

Since the mounting phase has not started when Vue calls the created() lifecycle hook, this is a good place to ingest the results of the authentication process. Let's gradually build this critical element of the authentication plugin. Easy does it.

Start by locating the External Modules and importing the createAuth0Client method:

/**
 *  External Modules
 */

import Vue from "vue";
import createAuth0Client from "@auth0/auth0-spa-js";

Next, head back to the Vue Instance Initialization section and add the following object argument to the useAuth0 function:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = ({
  onRedirectCallback = () =>
    window.history.replaceState({}, document.title, window.location.pathname),
  redirectUri = window.location.origin,
  ...pluginOptions
}) => {
  if (instance) return instance;

  instance = new Vue({...});

  return instance;
};

This new object argument defines the following properties:

  • onRedirectCallback defines a default action to perform after the user logs in. It uses the window.history.replaceState() method to take users to the route they intended to access before login.
  • redirectUri is the URL to where Auth0 will redirect your users with the authentication result. You must list this URL in the "Allowed Callback URLs" field in your Auth0 Application's settings, which you'll do later on.
  • pluginOptions are a set of values that you need to configure the Auth0 client using the createAuth0Client() method.

Now, you'll define an entry for the created() lifecycle hook in the new Vue() constructor:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = ({
  onRedirectCallback = () =>
    window.history.replaceState({}, document.title, window.location.pathname),
  redirectUri = window.location.origin,
  ...pluginOptions
}) => {
  if (instance) return instance;

  instance = new Vue({
    data() {...},
    methods: {...},

    /** Use this lifecycle method to instantiate the SDK client */
    async created() {},
  });

  return instance;
};

The created() property is at the same level of the data and methods properties.

Next, create a new instance of the Auth0 SPA SDK client using properties of the given pluginOptions object:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = ({
  onRedirectCallback = () =>
    window.history.replaceState({}, document.title, window.location.pathname),
  redirectUri = window.location.origin,
  ...pluginOptions
}) => {
  if (instance) return instance;

  instance = new Vue({
    data() {...},
    methods: {...},

    /** Use this lifecycle method to instantiate the SDK client */
    async created() {
      this.auth0Client = await createAuth0Client({
        ...pluginOptions,
        domain: pluginOptions.domain,
        client_id: pluginOptions.clientId,
        audience: pluginOptions.audience,
        redirect_uri: redirectUri,
      });
    },
  });

  return instance;
};

The createAuth0Client() method takes an Auth0ClientOptions object as argument, which lets you configure your Auth0 client instance. You'll pass down these options when you install your plugin in your Vue application.

Let's briefly explore the options that you are defining:

  • domain: Your Auth0 account domain such as example.auth0.com, example.us.auth0.com, example.mycompany.com (when using custom domains).
  • client_id: The Client ID of an Auth0 application.
  • audience: The intended recipient of your access tokens, such as the API your Vue application wants to access.
  • redirect_uri: The default URL to where Auth0 will redirect your users once they log in successfully.
  • You also spread out the pluginOptions object in case that there are other configuration options that you are passing to further customize the Auth0 client.

You are getting there! Next, you need to check if the user is returning to the app after authentication. If so, you need to handle the redirect and update the application's state.

Update the created() lifecycle hook as follows:

/**
 *  Vue Instance Initialization
 */

export const useAuth0 = ({
  onRedirectCallback = () =>
    window.history.replaceState({}, document.title, window.location.pathname),
  redirectUri = window.location.origin,
  ...pluginOptions
}) => {
  if (instance) return instance;

  instance = new Vue({
    data() {...},
    methods: {...},

    /** Use this lifecycle method to instantiate the SDK client */
    async created() {
      this.auth0Client = await createAuth0Client({
        ...pluginOptions,
        domain: pluginOptions.domain,
        client_id: pluginOptions.clientId,
        audience: pluginOptions.audience,
        redirect_uri: redirectUri,
      });

      try {
        if (
          window.location.search.includes("code=") &&
          window.location.search.includes("state=")
        ) {
          const { appState } = await this.auth0Client.handleRedirectCallback();

          onRedirectCallback(appState);
        }
      } catch (error) {
        this.error = error;
      } finally {
        this.isAuthenticated = await this.auth0Client.isAuthenticated();
        this.user = await this.auth0Client.getUser();
        this.isLoading = false;
      }
    },
  });

  return instance;
};

Let's break down what's happening now in the hook:

You created the instance of the Auth0 client.

Then, you use a try/catch block to manage the asynchronous handling of the redirect from Auth0.

If the URL has the code and state parameters, you handle the redirect and retrieve tokens from Auth0. These tokens may be an ID token with user profile information and an access token that lets you make secure API calls.

You can see those tokens in action in the "Retrieving User Information" and "Calling an API" sections of the "Complete Guide to Vue User Authentication". You can head there once you complete creating your authentication plugin.

The return value of the this.auth0Client.handleRedirectCallback() method has an appState property that has the state stored when Vue made the redirect request to Auth0. As such, you can pass that object to the onRedirectCallback you defined earlier to take your users back to the protected route they wanted to access.

You can see the implementation of Vue route guards in the "Protecting Routes" section of the "Complete Guide to Vue User Authentication".

If there is any error, you catch it and update the state accordingly.

Finally, you update the state of the application with the results of the authentication process using Auth0 SPA SDK methods:

You also set the isLoading state to false.

That's it for defining the useAuth0() function. You are almost done! Now, you need to use that function to define your authentication plugin.

Define the Vue Plugin

Update the Vue Plugin Definition section as follows to create a simple Vue plugin that exposes the local instance throughout the application via the useAuth0() function:

/**
 *  Vue Plugin Definition
 */

export const Auth0Plugin = {
  install(Vue, options) {
    Vue.prototype.$auth = useAuth0(options);
  },
};

A Vue plugin must expose an install() method, which Vue calls with the Vue constructor and some options as arguments. You pass those options to useAuth0(), which map to its pluginOptions parameter. You then store the instance that useAuth0() returns as a property of the Vue.prototype object, $auth. The $auth property is now a global Vue property.

Once you install this plugin in your application, you can access the user authentication functionality through the this context of any Vue component.

Install the Vue Plugin

To use your Vue plugins, you have to call them using the Vue.use() global method before you start your Vue application — before you call the new Vue() method that bootstraps your Vue application.

Open src/main.js and use the Vue.use() method to install the plugin:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";

import { domain, clientId, audience } from "../auth_config.json";
import { Auth0Plugin } from "@/auth/auth0-plugin";

import "./assets/css/styles.css";

// Install the authentication plugin
Vue.use(Auth0Plugin, {
  domain,
  clientId,
  audience,
  onRedirectCallback: (appState) => {
    router.push(
      appState && appState.targetUrl
        ? appState.targetUrl
        : window.location.pathname,
    );
  },
});

Vue.config.productionTip = false;

new Vue({
  router,
  render: (h) => h(App),
}).$mount("#app");

By default, Vue.use() prevents you from using the same plugin more than once. As such, calling your Auth0Plugin multiple times will install the plugin only once.

Vue.use() takes as arguments your plugin along with some options to configure the plugin.

Notice this line where you are importing some Auth0 configuration:

import { domain, clientId, audience } from "../auth_config.json";

You haven't set up an Auth service yet. However, you can mock the configuration values for the time being.

Create a auth_config.json file under the project directory:

touch auth_config.json

Populate auth_config.json as follows:

{
  "domain": "YOUR_AUTH0_DOMAIN",
  "clientId": "YOUR_AUTH0_CLIENT_ID",
  "audience": "https://express.sample",
  "serverUrl": "http://localhost:6060"
}

If you want to learn how to configure an Auth0 service and use the Auth0Plugin you created to implement user authentication in Vue, head to The Complete Guide to Vue User Authentication with Auth0.

That guide will show you how to create components that use the Auth0Plugin to carry out the authentication process, such as LoginButton, LogoutButton, and SignupButton components. You'll also learn how to make secure API calls from Vue components.

For example, this is what a LoginButton component that uses your Auth0Plugin may look like:

<template>
  <button
    class="btn btn-primary btn-block"
    v-if="!$auth.isAuthenticated"
    @click="login"
  >
    Log In
  </button>
</template>

<script>
export default {
  name: "LoginButton",
  methods: {
    login() {
      this.$auth.loginWithRedirect();
    },
  },
};
</script>

⏰⚡️ If you are short of time, check out the Auth0 Vue Quickstart to get up and running with user authentication for Vue in just a few minutes.

Conclusion

You have learned how to create a Vue plugin to add user authentication functionality to Vue at a global level. You can use the patterns you have learned in this tutorial to create plugins for other use cases.

Let me know in the comments below what you thought of this tutorial. Thank you for reading. Until the next time!

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon