Learn how to create a Vue plugin to manage user authentication
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 localinstance
— as you can see, closures are a powerful pattern in JavaScript – 1. If you already have initialized your localinstance
, you return that instance. 2. Otherwise, you initializeinstance
with the value of a newVue
instance, which can take a configuration object that you’ll define later. 3. Finally, you return the freshly initializedinstance
. - Later on, at the end of the module, you’ll define your authentication Vue plugin, which will consume the local
instance
throughuseAuth0()
.
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.
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";