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.If you already have initialized your local
instance
, you return that instance.Otherwise, you initialize
instance
with the value of a newVue
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
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.
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 thewindow.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 thecreateAuth0Client()
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 asexample.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:
- The asynchronous
this.auth0Client.isAuthenticated()
method returns a boolean value to indicate whether the user has logged in or not. - The asynchronous
this.auth0Client.getUser()
method returns the user information if available, which the Auth0 SPA SDK decodes from the ID token.
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!