developers

How to Create a Vue Plugin

Learn how to create a Vue plugin to manage user authentication

Nov 18, 202020 min read

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!