vue logo
javascript logo
spa
authentication

Vue.js User Authentication: Developer Guide with JavaScript

spa
authentication
Dan Arias
By Dan Arias, R&D Engineering ManagerLast update on March 25, 2022

This JavaScript guide will help you learn how to secure a Vue.js application. You'll learn how to use the Auth0 Identity Platform to implement the following security features:

  • Add user login, sign-up, and logout.
  • Retrieve user profile information.
  • Protect Vue.js application routes using guards.
  • Make API calls to request data from the protected endpoints of an API.

This guide uses the Auth0 SPA SDK, which provides Vue.js developers with a high-level API to handle a lot of authentication implementation details. You can now secure your Vue.js applications using security best practices while writing less code.

How Does Auth0 Work?

With the help of Auth0, you don't need to be an expert on identity protocols, such as OAuth 2.0 or OpenID Connect, to understand how to secure your web application stack.

You first integrate your application with Auth0. Your application will then redirect users to an Auth0 customizable login page when they need to log in. Once your users log in successfully, Auth0 redirects them back to your app, returning JSON Web Tokens (JWTs) with their authentication and user information.

Get the Starter Application

We have created a starter project using the Vue.js CLI to help you learn Vue.js security concepts through hands-on practice. You can focus on building Vue.js components to secure your application.

As such, clone the spa_vue_javascript_hello-world repository on its starter branch to get started:

git clone -b starter https://github.com/auth0-developer-hub/spa_vue_javascript_hello-world.git

Once you clone the repo, make spa_vue_javascript_hello-world your current directory:

cd spa_vue_javascript_hello-world

Install the Vue.js project dependencies:

npm install

This starter Vue.js project offers a functional application that consumes data from an external API to hydrate the user interface. For simplicity and convenience, the starter project simulates the external API locally using json-server. Later on, you'll integrate this Vue.js application with a real API server using a backend technology of your choice.

The compatible API server runs on http://localhost:6060 by default. As such, to connect your Vue.js application with that API server, create a .env file under the root project directory and populate it with the following environment variables:

VUE_APP_API_SERVER_URL=http://localhost:6060

Next, execute the following command to run the JSON server API:

npm run api

Finally, open another terminal tab and execute this command to run your Vue.js application:

npm run serve

You are ready to start implementing user authentication in this Vue.js project. First, you'll need to configure the Vue.js application to connect successfully to Auth0. Afterward, you'll be able to protect routes, display user profile information, and request protected data from an external API server to hydrate some of the application pages.

Configure Vue.js with Auth0

Follow these steps to get started with the Auth0 Identity Platform quickly:

Sign up and create an Auth0 Application

A free account also offers you:

During the sign-up process, you create something called an Auth0 Tenant, representing the product or service to which you are adding authentication.

Once you sign in, Auth0 takes you to the Dashboard. In the left sidebar menu, click on "Applications".

Then, click the "Create Application" button. A modal opens up with a form to provide a name for the application and choose its type. Use the following values:

Name
Auth0 Vue.js Code Sample
Application Type
Single Page Web Applications
Single Page Web Applications

Click the "Create" button to complete the process. Your Auth0 application page loads up.

In the next step, you'll learn how to help Vue.js and Auth0 communicate.

What's the relationship between Auth0 Tenants and Auth0 Applications?

Let's say that you have a photo-sharing Vue.js app called "Vuetigram". You then would create an Auth0 tenant called vuetigram. From a customer perspective, Vuetigram is that customer's product or service.

Now, say that Vuetigram is available on three platforms: web as a single-page application and Android and iOS as a native mobile application. If each platform needs authentication, you need to create three Auth0 applications to provide the product with everything it needs to authenticate users through that platform.

Vuetigram users belong to the Auth0 Vuetigram tenant, which shares them across its Auth0 applications.

Create a communication bridge between Vue.js and Auth0

When using the Auth0 Identity Platform, you don't have to build login forms. Auth0 offers a Universal Login Page to reduce the overhead of adding and managing authentication.

How does Universal Login work?

Your Vue.js application will redirect users to Auth0 whenever they trigger an authentication request. Auth0 will present them with a login page. Once they log in, Auth0 will redirect them back to your Vue.js application. For that redirecting to happen securely, you must specify in your Auth0 Application Settings the URLs to which Auth0 can redirect users once it authenticates them.

As such, click on the "Settings" tab of your Auth0 Application page, locate the "Application URIs" section, and fill in the following values:

Allowed Callback URLs

The above value is the URL that Auth0 can use to redirect your users after they successfully log in.

Allowed Logout URLs

The above value is the URL that Auth0 can use to redirect your users after they log out.

Allowed Web Origins

Using the Auth0 SPA SDK, your Vue.js application will make requests under the hood to an Auth0 URL to handle authentication requests. As such, you need to add your Vue.js application origin URL to avoid Cross-Origin Resource Sharing (CORS) issues.

Scroll down and click the "Save Changes" button.

Do not close this page yet. You'll need some of its information in the next section.

Add the Auth0 configuration variables to Vue.js

From the Auth0 Application Settings page, you need the Auth0 Domain and Client ID values to allow your Vue.js application to use the communication bridge you created.

**What exactly is an Auth0 Domain and an Auth0 Client ID?**

Domain

When you created a new Auth0 account, Auth0 asked to pick a name for your Tenant. This name, appended with auth0.com, is your Auth0 Domain. It's the base URL that you will use to access the Auth0 APIs and the URL where you'll redirect users to log in.

You can also use custom domains to allow Auth0 to do the authentication heavy lifting for you without compromising your branding experience.

Client ID

Each application is assigned a Client ID upon creation, which is an alphanumeric string, and it's the unique identifier for your application (such as q8fij2iug0CmgPLfTfG1tZGdTQyGaTUA). You cannot modify the Client ID. You will use the Client ID to identify the Auth0 Application to which the Auth0 SPA SDK needs to connect.

Warning: Another critical piece of information present in the "Settings" is the Client Secret. This secret protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources.

Head back to your Auth0 application page and click on the "Settings" tab.

Locate the "Basic Information" section and follow these steps to get the Auth0 Domain and Auth0 Client ID values:

Auth0 application settings to enable user authentication

When you enter a value in the input fields present on this page, any code snippet that uses such value updates to reflect it. Using these input fields makes it easy to copy and paste code as you follow along. For security, these values are stored in memory and only used locally. They are gone as soon as you refresh the page! As an extra precaution, you should use values from an Auth0 test application instead of a production one.

As such, enter the "Domain" and "Client ID" values in the following fields to set up your single-page application in the next section:

These variables let your Vue.js application identify as an authorized party to interact with the Auth0 authentication server.

Now, update the .env file under the Vue.js project directory as follows:

.env
VUE_APP_AUTH0_DOMAIN=AUTH0-DOMAIN
VUE_APP_AUTH0_CLIENT_ID=AUTH0-CLIENT-ID
VUE_APP_AUTH0_CALLBACK_URL=http://localhost:4040/callback
VUE_APP_API_SERVER_URL=http://localhost:6060

Re-run your application for Vue.js to recognize the .env changes:

npm run serve

Once you reach the "Call a Protected API" section of this guide, you'll learn how to use VUE_APP_API_SERVER_URL along with an Auth0 Audience value to request protected resources from an external API. For now, the application is using json-server to mock the API.

Handle the Auth0 post-login behavior

Notice that the Auth0 Callback URL, VUE_APP_AUTH0_CALLBACK_URL, points to http://localhost:4040/callback, which is the URL that Auth0 uses to redirect your users after they successfully log in.

If you were to use the root URL of your Vue.js application, http://localhost:4040, as the Auth0 Callback URL, you may hurt the user experience as follows:

  • This Vue application offers a /profile page that will display user profile information such as name and email address.
  • That user profile information is private and must be protected against unauthorized access.
  • You'll use the Auth0 SPA SDK to create a route guard that will require users to log in before they can access the /profile page.
  • When a user who is not logged in clicks on the /profile tab from the navigation bar, Auth0 will redirect them to a page to log in.
  • Once your users log in, Auth0 will redirect them to your Vue.js application with some metadata that allows your application to redirect them to the protected page they intended to access.
  • When you use http://localhost:4040 as the Auth0 Callback URL, your users will see the home page first before Vue.js takes them to the /profile page. This quick re-routing makes your user interface "flash", which makes the user interface feel buggy or glitchy.
  • However, when you use http://localhost:4040/callback as the Auth0 Callback URL, Auth0 will take your users to a /callback page after they log in. You can then display a loading animation or nothing at all in that special route. Doing so makes the transition from /callback to /profile smoother as no unrelated or unexpected content shows up in the process.

For this Vue.js application, you'll render a simple page component for the /callback route.

Create a callback-page.vue file under the src/pages directory:

touch src/pages/callback-page.vue

Populate src/pages/callback-page.vue with the following code:

src/pages/callback-page.vue
<template>
<div class="page-layout">
<NavBar />
<MobileNavBar />
<div class="page-layout__content">
<slot />
</div>
</div>
</template>
<script>
import NavBar from "@/components/navigation/desktop/nav-bar.vue";
import MobileNavBar from "@/components/navigation/mobile/mobile-nav-bar.vue";
export default {
components: {
NavBar,
MobileNavBar,
},
};
</script>

The callback-page.vue component will only render the navigation bar and an empty content container to help you create a smooth transition between a route with no content, /callback, to a route with content, such as the /profile page:

  • Your users won't see any flashing of the home page component, which renders at the root path, /.
  • By showing the navigation bar in the /callback route, your user may feel that your Vue.js application loads fast.
  • By not showing the footer, your users may feel that your Vue.js application loads smoothly.
    • If you were to render the footer component, your application may appear to be jumpy as the footer may show up briefly and then it would be pushed down when the content above it loads.

The next step is to integrate your callback-page.vue component with the Vue.js router.

Locate the src/router/index.js file, which defines your Vue.js route module, and update it like so:

src/router/index.js
import HomePage from "@/pages/home-page.vue";
import CallbackPage from "@/pages/callback-page.vue";
import NotFoundPage from "@/pages/not-found-page.vue";
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
export const router = new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "Home",
component: HomePage,
},
{
path: "/profile",
name: "profile",
component: () => import("../pages/profile-page.vue"),
},
{
path: "/public",
name: "public",
component: () => import("../pages/public-page.vue"),
},
{
path: "/protected",
name: "protected",
component: () => import("../pages/protected-page.vue"),
},
{
path: "/admin",
name: "admin",
component: () => import("../pages/admin-page.vue"),
},
{
path: "/callback",
name: "callback",
component: CallbackPage,
},
{
path: "*",
name: "Not Found",
component: NotFoundPage,
},
],
});

Once you add a login and logout button to this app, you can verify this user experience improvement by using your browser's developer tools. In the case of Google Chrome, you could do the following:

Auth0 and Vue.js connection set

You have completed setting up an authentication service that your Vue.js application can consume. All that is left is for you to continue building up the starter project throughout this guide by implementing components to trigger and manage the authentication flow.

Feel free to dive deeper into the Auth0 Documentation to learn more about how Auth0 helps you save time implementing and managing identity.

Create a Vue.js Authentication Plugin

You'll create different Vue.js components to trigger the authentication flow in your application. As such, you need to add the user authentication functionality to your Vue.js application at a global level.

One approach to add global-level functionality to Vue.js is using plugins, which take advantage of Vue's reactive nature. In the case of user authentication, a Vue.js 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.

Follow these instructions to create the Vue.js authentication plugin quickly.

Execute the following command to install the Auth0 SPA SDK:

npm install @auth0/auth0-spa-js

Create an auth0-plugin.js file under the src directory:

touch src/auth0-plugin.js

Populate src/auth0-plugin.js with the following code that defines a Vue.js plugin module:

src/auth0-plugin.js
/**
* External Modules
*/
import Vue from "vue";
import createAuth0Client from "@auth0/auth0-spa-js";
/**
* Vue.js Instance Definition
*/
let instance;
export const getInstance = () => instance;
/**
* Vue.js 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() {
return {
auth0Client: null,
isLoading: true,
isAuthenticated: false,
user: {},
error: null,
};
},
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(options) {
return this.auth0Client.loginWithRedirect(options);
},
logout(options) {
return this.auth0Client.logout(options);
},
getAccessTokenSilently(o) {
return this.auth0Client.getTokenSilently(o);
},
},
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;
};
/**
* Vue.js Plugin Definition
*/
export const Auth0Plugin = {
install(Vue, options) {
Vue.prototype.$auth0 = useAuth0(options);
},
};

If you want to learn all the details on how to create this Vue.js authentication plugin from scratch, head to the "How to Create a Vue.js Plugin" tutorial.

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

src/main.js
import Vue from "vue";
import App from "./app.vue";
import "./assets/css/styles.css";
import { Auth0Plugin } from "./auth0-plugin";
import { router } from "./router";
Vue.config.productionTip = false;
Vue.use(Auth0Plugin, {
domain: process.env.VUE_APP_AUTH0_DOMAIN,
clientId: process.env.VUE_APP_AUTH0_CLIENT_ID,
redirectUri: process.env.VUE_APP_AUTH0_CALLBACK_URL,
onRedirectCallback: (appState) => {
router.push(
appState && appState.targetPath
? appState.targetPath
: window.location.pathname
);
},
});
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");

Recall that once you integrate Auth0 into your Vue.js application, the application will redirect users to a login page provided by Auth0 when they need to log in. Once your users log in successfully, Auth0 redirects them back to your Vue.js application. The onRedirectCallback configuration option of the Auth0Plugin defines how your application will handle that redirect event.

Notice that the Vue.js authentication plugin has a default value for its onRedirectCallback configuration property:

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

However, when you install the plugin, you define a different function as its value:

Vue.use(Auth0Plugin, {
// ... other configuration options
onRedirectCallback: (appState) => {
router.push(
appState && appState.targetPath
? appState.targetPath
: window.location.pathname
);
},
});

The onRedirectCallback property is now a function that uses the Vue.js router to redirect your users to the page they intended to access before logging in. Some methods of the Auth0 SPA SDK accept an appState configuration option that lets you store any data that you want to after the user authenticates with Auth0 and returns to your application.

The appState object acts as a dictionary where you can store any data you'd like. For this use case, appState may have a targetPath property that specifies to which route Vue.js should take your users after they authenticate with Auth0. If appState and appState.targetPath are undefined, Vue.js takes your users to whatever the value of window.location.pathname is. Since your Auth0 Callback URL is /callback, that would be the default path for your application.

You'll see the benefits and behavior of appState in action once you implement the login and logout buttons in the next sections. Stay tuned!

The Auth0 SPA SDK is all set up. You are ready to implement user authentication using Vue.js components in the next section using your Auth0Plugin.

Create a Vue.js Login Button

Create a buttons directory under the src/components directory:

mkdir src/components/buttons

Create a login-button.vue file under the src/components/buttons directory:

touch src/components/buttons/login-button.vue

Why are we using kebab-case and not PascalCase to create Vue.js component files, *.vue? According to the Vue Style Guide, "PascalCase works best with autocompletion in code editors, as it's consistent with how we reference components in JS(X) and templates, wherever possible. However, mixed case filenames can sometimes create issues on case-insensitive file systems, which is why kebab-case is also perfectly acceptable".

Populate src/components/buttons/login-button.vue like so:

src/components/buttons/login-button.vue
<template>
<button class="button__login" @click="handleLogin">Log In</button>
</template>
<script>
export default {
methods: {
handleLogin() {
this.$auth0.loginWithRedirect({
appState: {
targetPath: "/",
},
});
},
},
};
</script>

As mentioned during the authentication plugin creation, you can pass a configuration object to loginWithRedirect() to customize the login experience. Here you are using the appState redirect login option that we mentioned before.

You are telling the Auth0 SPA SDK the following by setting up the value of appState.targetPath to /: When my users log in with Auth0 and return to my Vue.js application, take them from the default callback URL path, /callback, to the home page path, /. If you don't specify this appState option, your users will get stuck in the /callback path after they log in.

You can also pass options to redirect users to an Auth0 Universal Login page optimized for signing up for your Vue.js application. See RedirectLoginOptions for more details on these options.

Create a Vue.js Sign-Up Button

You can make users land directly on a sign-up page instead of a login page by specifying the screen_hint=signup property in the configuration object of loginWithRedirect():

{
screen_hint: "signup",
}

To see this in practice, create a signup-button.vue file under the src/components/buttons directory:

touch src/components/buttons/signup-button.vue

Populate src/components/buttons/signup-button.vue like so to define a sign-up button component:

src/components/buttons/signup-button.vue
<template>
<button class="button__sign-up" @click="handleSignUp">Sign Up</button>
</template>
<script>
export default {
methods: {
handleSignUp() {
this.$auth0.loginWithRedirect({
screen_hint: "signup",
});
},
},
};
</script>

Using the Auth0 Signup feature requires you to enable the Auth0 New Universal Login Experience in your tenant.

Open the Universal Login section of the Auth0 Dashboard and choose the "New" option under the "Experience" subsection.

Auth0 Universal Login Experience options

Scroll down and click on the "Save Changes" button.

The difference between the login and sign-up user experience will be more evident once you integrate those components with your Vue.js application and see them in action. You'll do that in the following sections.

Create a Vue.js Logout Button

Create a logout-button.vue file under the src/components/buttons directory:

touch src/components/buttons/logout-button.vue

Populate src/components/buttons/logout-button.vue like so:

src/components/buttons/logout-button.vue
<template>
<button class="button__logout" @click="handleLogout">Log Out</button>
</template>
<script>
export default {
methods: {
handleLogout() {
this.$auth0.logout({
returnTo: window.location.origin,
});
},
},
};
</script>

When using the this.$auth.logout() method, the Auth0 SPA SDK clears the application session and redirects to the Auth0 /v2/logout endpoint to clear the Auth0 session under the hood.

As with the login methods, you can pass an object argument to logout() to define parameters for the /v2/logout call. This process is fairly invisible to the user. See LogoutOptions for more details on the configurations options available.

Read more about how Logout works at Auth0.

Here, you pass the returnTo option to specify the URL where Auth0 should redirect your users after they log out. Right now, you are working locally, and your Auth0 application's "Allowed Logout URLs" point to http://localhost:4040.

However, if you were to deploy your Vue.js application to production, you need to add the production logout URL to the "Allowed Logout URLs" list and ensure that Auth0 redirects your users to that production URL and not localhost. Setting returnTo to window.location.origin will do just that.

A best practice when working with Auth0 is to have different tenants for your different project environments. For example, it's recommended for developers to specify a production tenant. A production tenant gets higher rate limits than non-production tenants. Check out the "Set Up Multiple Environments" Auth0 document to learn more about how to set up development, staging, and production environments in the Auth0 Identity Platform.

Render Components Conditionally

In this section, you'll learn how to render Vue.js components conditionally based on the status of the Auth0 SPA SDK or the authentication status of your users.

Render the authentication buttons conditionally

The Vue.js starter application features a desktop and mobile navigation experience.

When using your Vue.js application on a viewport large enough to fix a desktop or tablet experience, you'll see a navigation bar at the top of the page.

When using a viewport that fits the screen constraints of a mobile device, you'll see a menu button at the top-right corner of the page. Tapping or clicking on the menu buttons opens a modal that shows you the different pages that you can access in the application.

In this section, you'll expose the button components that trigger login, sign-up, and logout transactions through these page navigation elements.

Let's start with the desktop navigation user experience. You'll show both the login and sign-up buttons on the navigation bar when the user is not logged in. Naturally, you'll show the logout button when when the user is logged in.

Update src/components/navigation/desktop/nav-bar-buttons.vue as follows to implement the user experience defined above:

src/components/navigation/desktop/nav-bar-buttons.vue
<template>
<div class="nav-bar__buttons">
<template v-if="!$auth0.isAuthenticated">
<SignupButton />
<LoginButton />
</template>
<template v-if="$auth0.isAuthenticated">
<LogoutButton />
</template>
</div>
</template>
<script>
import LoginButton from "@/components/buttons/login-button.vue";
import LogoutButton from "@/components/buttons/logout-button.vue";
import SignupButton from "@/components/buttons/signup-button.vue";
export default {
components: { LoginButton, LogoutButton, SignupButton },
};
</script>

The $auth0.isAuthenticated value reflects the authentication state of your users as tracked by the authentication plugin, Auth0Plugin. This value is true when the user has been authenticated and false when not. As such, you can use the $auth0.isAuthenticated value to render UI elements conditionally depending on the authentication state of your users, as you did above.

The mobile navigation experience works in the same fashion except that the authentication-related buttons are tucked into the mobile menu modal.

Update src/components/navigation/mobile/mobile-nav-bar-buttons.vue as follows:

src/components/navigation/mobile/mobile-nav-bar-buttons.vue
<template>
<div class="mobile-nav-bar__buttons">
<template v-if="!$auth0.isAuthenticated">
<SignupButton />
<LoginButton />
</template>
<template v-if="$auth0.isAuthenticated">
<LogoutButton />
</template>
</div>
</template>
<script>
import LoginButton from "@/components/buttons/login-button.vue";
import LogoutButton from "@/components/buttons/logout-button.vue";
import SignupButton from "@/components/buttons/signup-button.vue";
export default {
components: { LoginButton, LogoutButton, SignupButton },
};
</script>

Go ahead and try to log in. Your Vue.js application redirects you to the Auth0 Universal Login page. You can use a form to log in with a username and password or a social identity provider like Google. Notice that this login page also gives you the option to sign up.

New Auth0 Universal Login Experience Form

However, when you click the sign-up button from your application directly, Vue.js takes you instead to a page with language optimized to encourage users to sign up for your Vue.js application. Try it out!

New Auth0 Universal Login Experience Signup Page

You can customize the appearance of New Universal Login pages. You can also override any text in the New Experience using the Text Customization API.

Notice that when you finish logging in or signing up, Auth0 redirects you to your Vue.js app, but the login and sign-up buttons briefly show up before the logout button renders. You'll fix that next.

Render the application conditionally

The user interface flashes because your Vue.js app doesn't know if Auth0 has authenticated the user yet. Your Vue.js application will know the user authentication status after Auth0Plugin initializes.

To fix that UI flashing, use the $auth0.isLoading value to render the app.vue component once the authentication plugin, Auth0Plugin, has finished loading.

Open src/app.vue and update it as follows:

src/app.vue
<template>
<div v-if="$auth0.isLoading" class="page-layout">
<PageLoader />
</div>
<router-view v-else />
</template>
<script>
import PageLoader from "@/components/page-loader.vue";
export default {
components: {
PageLoader,
},
};
</script>

While the SDK is loading, the page-loader.vue component renders, which shows up an animation. Log out and log back in to see this in action. No more UI flashing should happen.

Render navigation tabs conditionally

There may be use cases where you want to hide user interface elements from users who have not logged in to your application. For this starter application, only authenticated users should see the navigation tabs to access the /protected and /admin pages.

To implement this use case, you'll rely on the $auth0.isAuthenticated value once again.

Open the src/components/navigation/desktop/nav-bar-tabs.vue component file that defines your desktop navigation tabs and update it like so:

src/components/navigation/desktop/nav-bar-tabs.vue
<template>
<div class="nav-bar__tabs">
<NavBarTab path="/profile" label="Profile" />
<NavBarTab path="/public" label="Public" />
<template v-if="$auth0.isAuthenticated">
<NavBarTab path="/protected" label="Protected" />
<NavBarTab path="/admin" label="Admin" />
</template>
</div>
</template>
<script>
import NavBarTab from "@/components/navigation/desktop/nav-bar-tab.vue";
export default {
components: {
NavBarTab,
},
};
</script>

Next, open the src/components/navigation/mobile/mobile-nav-bar-tabs.vue component file that defines your mobile navigation tabs and update it like so:

src/components/navigation/mobile/mobile-nav-bar-tabs.vue
<template>
<div class="mobile-nav-bar__tabs">
<MobileNavBarTab
path="/profile"
label="Profile"
:handle-click="closeMenu"
/>
<MobileNavBarTab path="/public" label="Public" :handle-click="closeMenu" />
<template v-if="$auth0.isAuthenticated">
<MobileNavBarTab
path="/protected"
label="Protected"
:handle-click="closeMenu"
/>
<MobileNavBarTab path="/admin" label="Admin" :handle-click="closeMenu" />
</template>
</div>
</template>
<script>
import MobileNavBarTab from "@/components/navigation/mobile/mobile-nav-bar-tab.vue";
export default {
components: { MobileNavBarTab },
props: {
closeMenu: Function,
},
};
</script>

Log out from your Vue.js application and notice how now you can only see the tabs for the /profile and /public pages in the navigation bar along with the login and sign-up buttons. Log in and then see the rest of the navigation bar show up.

Keep in mind that this does not restrict access to the /admin and /protected pages at all. You'll learn how to use the Auth0 SPA SDK to protect Vue routes in the next section.

Protect Access to Vue.js Routes

You can create an authentication route guard to protect Vue.js routes. Vue.js will ask users who visit the route to log in if they haven't done so already. Once they log in, Vue.js takes them to the route they tried to access.

You can apply the guard to any route that you have defined in the Vue.js router module.

To start, create an authentication-guard.js file in the src directory:

touch src/authentication-guard.js

You'll use the getInstance() method from the authentication plugin module (src/auth0-plugin.js) to implement a function that prevents unauthenticated users from accessing a route.

Populate src/authentication-guard.js with the following Vue.js navigation guard code:

src/authentication-guard.js
import { getInstance } from "./auth0-plugin";
export const authenticationGuard = (to, from, next) => {
const authService = getInstance();
const guardAction = () => {
if (authService.isAuthenticated) {
return next();
}
authService.loginWithRedirect({ appState: { targetPath: to.fullPath } });
};
// If the Auth0Plugin has loaded already, check the authentication state
if (!authService.isLoading) {
return guardAction();
}
authService.$watch("isLoading", (isLoading) => {
if (isLoading === false) {
return guardAction();
}
});
};

Let's break down what is happening in the src/authentication-guard.js file.

You are defining a Vue router guard function, authenticationGuard:

export const authenticationGuard = (to, from, next) => {...};

Every Vue router guard function receives three arguments:

  • to is the target to where the user is navigating.

  • from is the current route that the user is navigating away from.

  • next is a function that Vue.js must call to resolve the hook. The action depends on the arguments provided to next.

Within the Vue router guard function, you get the instance from your auth0-plugin module and set up a watcher on the isLoading property from the plugin:

import { getInstance } from "./auth0-plugin";
export const authenticationGuard = (to, from, next) => {
const authService = getInstance();
authService.$watch("isLoading", (isLoading) => {
if (isLoading === false) {
// Perform an action
}
});
};

You want to wait for the isLoading property to be false before checking if the user has logged in.

You are defining a guardAction() function within authenticationGuard to run once the Auth0Plugin has loaded:

import { getInstance } from "./auth0-plugin";
export const authenticationGuard = (to, from, next) => {
const authService = getInstance();
const guardAction = () => {
if (authService.isAuthenticated) {
return next();
}
authService.loginWithRedirect({ appState: { targetPath: to.fullPath } });
};
authService.$watch("isLoading", (isLoading) => {
if (isLoading === false) {
return guardAction();
}
});
};

Within guardAction(), if the user is authenticated, you invoke next() to move on to the next hook in the Vue router pipeline. If there are no hooks left, the navigation is confirmed, and the user can access the route.

However, if the user is not authenticated, Vue.js redirects them to the Auth0 Universal Login page to log in.

Notice that you pass an options object to the authService.loginWithRedirect() method:

authService.loginWithRedirect({ appState: { targetPath: to.fullPath }

The appState.targetPath property from the object argument represents the URL that the user wants to access, to.fullPath. Auth0 will return that URL to your Vue.js app after the user logs in. The onRedirectCallback() function that you used as a configuration property for useAuth0 in src/auth0-plugin.js will use that appState object to redirect the user to that URL:

onRedirectCallback = () =>
window.history.replaceState({}, document.title, window.location.pathname);

At that point, the authenticationGuard function covers the scenario where the user may be accessing a protected route directly, and your Vue.js application is loading for the first time. What about the scenario where the application has already loaded and the user is navigating from a public page to a protected one?

For that, you amend authenticationGuard to check if the authentication plugin has already loaded:

import { getInstance } from "./auth0-plugin";
export const authenticationGuard = (to, from, next) => {
const authService = getInstance();
const guardAction = () => {
if (authService.isAuthenticated) {
return next();
}
authService.loginWithRedirect({ appState: { targetPath: to.fullPath } });
};
// If the Auth0Plugin has loaded already, check the authentication state
if (!authService.isLoading) {
return guardAction();
}
authService.$watch("isLoading", (isLoading) => {
if (isLoading === false) {
return guardAction();
}
});
};

If the user visits the route and the Auth0Plugin has loaded already, you check the authentication state by invoking the guardAction() function. It's pretty much the same logic as in the watcher. The difference is that the watcher triggers when Auth0Plugin goes from the "loading" state to the "loaded" one. The logic gate triggers when the Auth0Plugin is already in its "loaded" state.

You have completed the definition of the navigation guard that grants access to routes based on the user authentication status. All that's left is for you to use authenticationGuard in your route module.

Open src/router/index.js and update it like so:

src/router/index.js
import HomePage from "@/pages/home-page.vue";
import CallbackPage from "@/pages/callback-page.vue";
import NotFoundPage from "@/pages/not-found-page.vue";
import Vue from "vue";
import Router from "vue-router";
import { authenticationGuard } from "../authentication-guard";
Vue.use(Router);
export const router = new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "Home",
component: HomePage,
},
{
path: "/profile",
name: "profile",
component: () => import("../pages/profile-page.vue"),
beforeEnter: authenticationGuard,
},
{
path: "/public",
name: "public",
component: () => import("../pages/public-page.vue"),
},
{
path: "/protected",
name: "protected",
component: () => import("../pages/protected-page.vue"),
beforeEnter: authenticationGuard,
},
{
path: "/admin",
name: "admin",
component: () => import("../pages/admin-page.vue"),
beforeEnter: authenticationGuard,
},
{
path: "/callback",
name: "callback",
component: CallbackPage,
},
{
path: "*",
name: "Not Found",
component: NotFoundPage,
},
],
});

You use the authenticationGuard to protect the /profile, /protected, and /admin routes by adding it as the value of the beforeEnter route configuration property. Since beforeEnter is a pre-route guard, Vue.js will run authenticationGuard before accessing that route and rendering its components.

If the conditions defined by authenticationGuard pass, the component renders. Otherwise, authenticationGuard instructs Vue.js to take you to the Auth0 Universal Login Page to authenticate.

You can now test that these guarded paths require users to log in before accessing them. Log out and try to access the Profile page, Protected page, or the Admin page. If it works, Vue.js redirects you to log in with Auth0.

Once you log in, Vue.js should take you back to the page that you intended to access before logging in.

Client-side guards improve the user experience of your Vue.js application, not its security.

In Security StackExchange, Conor Mancone explains that server-side guards are about protecting data while client-side guards are about improving user experience.

The main takeaways from his response are:

  • You can't rely on client-side restrictions, such as navigation guards and protected routes, to protect sensitive information.
    • Attackers can potentially get around client-side restrictions.
  • Your server should not return any data that a user should not access. The wrong approach is to return all the user data from the server and let the front-end framework decide what to display and what to hide based on the user authentication status.
    • Anyone can open the browser's developer tools and inspect the network requests to view all the data.
  • The use of navigation guards helps improve user experience, not user security.
    • Without guards, a user who has not logged in may wander into a page with restricted information and see an error, like "Access Denied".
    • With guards that match the server permissions, you can prevent users from seeing errors by preventing them from visiting the restricted page.

Retrieve User Information

After a user successfully logs in, Auth0 sends an ID token to your Vue.js application. Authentication systems, such as Auth0, use ID Tokens in token-based authentication to cache user profile information and provide it to a client application. The caching of ID tokens can contribute to improvements in performance and responsiveness for your Vue.js application.

You can use the data from the ID token to personalize the user interface of your Vue.js application. The Auth0 SPA SDK decodes the ID token and stores its data in the user object exposed by the Auth0Plugin via the $auth global property. Some of the ID token information includes the name, nickname, picture, and email of the logged-in user.

How can you use the ID token to create a profile page for your users?

Update src/pages/profile-page.vue as follows:

src/pages/profile-page.vue
<template>
<PageLayout>
<div class="content-layout">
<h1 class="content__title">Profile</h1>
<div class="content__body">
<p>
<strong>Only authenticated users should access this page.</strong>
</p>
<div class="profile-grid">
<div class="profile__header">
<img :src="user.picture" alt="Profile" class="profile__avatar" />
<div class="profile__headline">
<h2 class="profile__title">{{ user.name }}</h2>
<span class="profile__description">{{ user.email }}</span>
</div>
</div>
<div class="profile__details">
<CodeSnippet title="User Profile Object" :code="code" />
</div>
</div>
</div>
</div>
</PageLayout>
</template>
<script>
import CodeSnippet from "@/components/code-snippet.vue";
import PageLayout from "@/components/page-layout.vue";
export default {
components: {
PageLayout,
CodeSnippet,
},
data() {
return {
user: this.$auth0.user,
};
},
computed: {
code() {
return JSON.stringify(this.user, null, 2);
},
},
};
</script>

What's happening within the Profile component?

  • You display three properties from the $auth.user object in the user interface: name, picture, and email.

  • Since the data comes from a simple object, you don't have to fetch it using any asynchronous calls.

  • Finally, you display the full content of the decoded ID token within a code box. You can now see all the other properties available for you to use.

The profile-page.vue component renders user information that you could consider private or sensitive. Additionally, the user property is null if there is no logged-in user. So either way, this component should only render if Auth0 has authenticated the user. You are already restricting access to this page component by using the authenticationGuard in the /profile route definition of your Vue router module, src/router/index.js.

If you are logged in to your application, visit http://localhost:4040/profile to see your user profile details.

Integrate Vue.js with an API Server

This section shows you how to get an access token in your Vue.js application and how to use it to make API calls to protected API endpoints.

When you use Auth0, you delegate the authentication process to a centralized service. Auth0 provides you with functionality to log in and log out users from your Vue.js application. However, your application may need to access protected resources from an API.

You can also protect an API with Auth0. There are multiple API quickstarts to help you integrate Auth0 with your backend platform.

When you use Auth0 to protect your API, you also delegate the authorization process to a centralized service that ensures only approved client applications can access protected resources on behalf of a user.

How can you make secure API calls from Vue.js?

Your Vue.js application authenticates the user and receives an access token from Auth0. The application can then pass that access token to your API as a credential. In turn, your API can use Auth0 libraries to verify the access token it receives from the calling application and issue a response with the desired data.

What is the difference between an ID token and an access token?

Instead of creating an API from scratch to test the authentication and authorization flow between the client and the server, you can pair this client application with an API server that matches the technology stack you use at work. The Vue.js "Hello World" client application that you have been building up can interact with any of the "Hello World" API server samples from the Auth0 Developer Hub.

The "Hello World" API server samples run on http://localhost:6060 by default, which is the same origin URL and port where the mock JSON server is running. As such, before you set up the "Hello World" API server, locate the tab where you are running npm run api and stop the mock JSON server.

Pick an API code sample in your preferred backend framework and language from the list below and follow the instructions on the code sample page to set it up. Once you complete the sample API server set up, please return to this page to learn how to integrate that API server with your Vue.js application.

Call a Protected API

Once you have set up the API server code sample, you should have created an Auth0 Audience value. Store that value in the following field so that you can use it throughout the instructions present on this page easily:

Now, update the .env file under the Vue.js project directory as follows:

.env
VUE_APP_AUTH0_DOMAIN=AUTH0-DOMAIN
VUE_APP_AUTH0_CLIENT_ID=AUTH0-CLIENT-ID
VUE_APP_AUTH0_CALLBACK_URL=http://localhost:4040/callback
VUE_APP_API_SERVER_URL=http://localhost:6060
VUE_APP_AUTH0_AUDIENCE=AUTH0-AUDIENCE

Re-run your application for Vue.js to recognize the .env changes:

npm run serve

You are using VUE_APP_AUTH0_AUDIENCE to add the value of your Auth0 API Audience so that your Vue.js client application can request resources from the API that such audience value represents.

Let's understand better what the VUE_APP_AUTH0_AUDIENCE and VUE_APP_API_SERVER_URL values represent.

The VUE_APP_API_SERVER_URL is the URL where your sample API server listens for requests. In production, you'll change this value to the URL of your live server.

Your Vue.js application must pass an access token when it calls a target API to access protected resources. You can request an access token in a format that the API can verify by passing the audience to the Auth0 SPA SDK.

The value of the Auth0 Audience must be the same for both the Vue.js client application and the API server you decided to set up.

Why is the Auth0 Audience value the same for both apps? Auth0 uses the value of the audience prop to determine which resource server (API) the user is authorizing your Vue.js application to access. It's like a phone number. You want to ensure that your Vue.js application "texts the right API".

As such, update the src/main.js file from your Vue project as follows:

src/main.js
import Vue from "vue";
import App from "./app.vue";
import "./assets/css/styles.css";
import { Auth0Plugin } from "./auth0-plugin";
import { router } from "./router";
Vue.config.productionTip = false;
Vue.use(Auth0Plugin, {
domain: process.env.VUE_APP_AUTH0_DOMAIN,
clientId: process.env.VUE_APP_AUTH0_CLIENT_ID,
audience: process.env.VUE_APP_AUTH0_AUDIENCE,
redirectUri: process.env.VUE_APP_AUTH0_CALLBACK_URL,
onRedirectCallback: (appState) => {
router.push(
appState && appState.targetPath
? appState.targetPath
: window.location.pathname
);
},
});
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");

You are now including an audience property in the configuration object that Vue uses to create an instance of Auth0Plugin.

What about using scopes?

A property that you are not configuring directly for Auth0Plugin is the scope property. When you don't pass a scope option to Auth0 SPA SDK, which powers Auth0Plugin, the SDK defaults to using the OpenID Connect Scopes: openid profile email.

  • openid: This scope informs the Auth0 Authorization Server that the Client is making an OpenID Connect (OIDC) request to verify the user's identity. OpenID Connect is an authentication protocol.

  • profile: This scope value requests access to the user's default profile information, such as name, nickname, and picture.

  • email: This scope value requests access to the email and email_verified information.

The details of the OpenID Connect Scopes go into the ID Token. However, you can define custom API scopes to implement access control. You'll identify those custom scopes in the calls that your client applications make to that API. Auth0 includes API scopes in the access token as the scope claim value.

The concepts about API scopes or permissions are better covered in an Auth0 API tutorial such as "Use TypeScript to Create a Secure API with Node.js and Express: Role-Based Access Control".

The authentication plugin, Auth0Plugin, provides you with a method to get an access token from Auth0: getTokenSilently(). If you already have an access token stored in memory, but the token is invalid or expired, this method will get you 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. As the name implies, it's a method to getTokenSilently()... 🤫😶

It's now time to update the /protected and /admin pages to let users retrieve private data from the API server. Please ensure that your sample API server is running as you complete the following steps.

Start by updating the service methods present in the src/services/message-service.js file as follows:

src/services/message-service.js
import { callExternalApi } from "./external-api-service";
const apiServerUrl = process.env.VUE_APP_API_SERVER_URL;
export const getPublicResource = async () => {
const config = {
url: `${apiServerUrl}/api/messages/public`,
method: "GET",
headers: {
"content-type": "application/json",
},
};
const { data, error } = await callExternalApi({ config });
return {
data: data || null,
error,
};
};
export const getProtectedResource = async (accessToken) => {
const config = {
url: `${apiServerUrl}/api/messages/protected`,
method: "GET",
headers: {
"content-type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
};
const { data, error } = await callExternalApi({ config });
return {
data: data || null,
error,
};
};
export const getAdminResource = async (accessToken) => {
const config = {
url: `${apiServerUrl}/api/messages/admin`,
method: "GET",
headers: {
"content-type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
};
const { data, error } = await callExternalApi({ config });
return {
data: data || null,
error,
};
};

You are changing the signature of the getProtectedResource() and getAdminResource() methods to include an accessToken parameter.

You then pass that accessToken value as a bearer credential in the authorization header of the request config object. You make requests from your Vue.js application using the callExternalApi() helper method defined in the src/services/external-api-service.js module. callExternalApi() uses axios to make its API calls.

With the message service methods in place, proceed to update the src/pages/protected-page.vue component as follows:

src/pages/protected-page.vue
<template>
<PageLayout>
<div class="content-layout">
<h1 class="content__title">Protected Page</h1>
<div class="content__body">
<p>
This page retrieves a <strong>protected message</strong> from an
external API.
<br />
<br />
<strong>Only authenticated users should access this page.</strong>
</p>
<CodeSnippet title="Protected Message" :code="message" />
</div>
</div>
</PageLayout>
</template>
<script>
import CodeSnippet from "@/components/code-snippet.vue";
import PageLayout from "@/components/page-layout.vue";
import { getProtectedResource } from "@/services/message-service";
export default {
components: {
PageLayout,
CodeSnippet,
},
data() {
return {
message: "",
};
},
async mounted() {
const accessToken = await this.$auth0.getAccessTokenSilently();
const { data, error } = await getProtectedResource(accessToken);
if (data) {
this.message = JSON.stringify(data, null, 2);
}
if (error) {
this.message = error.message;
}
},
};
</script>

What is happening now within the protected-page.vue component?

  • You use the mounted lifecycle method to request data from your API server to hydrate your page.

  • You call the async this.$auth0.getAccessTokenSilently() method to fetch a new access token from the Auth0 Authorization Server. Under the hood, the Auth0 SPA SDK triggers a call to the Auth0 /oauth/token endpoint.

    • Your previous login request did not include an audience parameter. As such, the Auth0 SPA SDK doesn't have an access token stored in memory, and it requests a new one.
    • The SDK will now store that access token in memory. For future getAccessTokenSilently() calls, the SDK will use the stored access token and only request a new one when the stored access token expires.
  • You then use the getProtectedResource() service method to fetch the protected message from your API server.

    • You pass to this service method the access token that you fetched.
    • In turn, getProtectedResource() passes that access token as a bearer credential in the authorization header of the request it makes to your API server.
  • Finally, if you retrieve the data from the server correctly, you display that message data in the message box. Otherwise, you show the corresponding error message.

Log out from your Vue.js application and log back in to get a new access token from Auth0 that includes the audience information.

Visit the http://localhost:4040/protected page and verify that you are getting a valid response from the server.

Now, update the src/pages/admin-page.vue component to implement the same business logic outlined before but this time using the getAdminResource() service method to retrieve the correct message:

src/pages/admin-page.vue
<template>
<PageLayout>
<div class="content-layout">
<h1 class="content__title">Admin Page</h1>
<div class="content__body">
<p>
This page retrieves an <strong>admin message</strong> from an external
API.
<br />
<br />
<strong>
Only authenticated users with the
<code>read:admin-messages</code> permission should access this page.
</strong>
</p>
<CodeSnippet title="Admin Message" :code="message" />
</div>
</div>
</PageLayout>
</template>
<script>
import CodeSnippet from "@/components/code-snippet.vue";
import PageLayout from "@/components/page-layout.vue";
import { getAdminResource } from "@/services/message-service";
export default {
components: {
PageLayout,
CodeSnippet,
},
data() {
return {
message: "",
};
},
async mounted() {
const accessToken = await this.$auth0.getAccessTokenSilently();
const { data, error } = await getAdminResource(accessToken);
if (data) {
this.message = JSON.stringify(data, null, 2);
}
if (error) {
this.message = error.message;
}
},
};
</script>

That's all it takes to integrate Vue.js with an external API server that is also secured by Auth0 and to use an access token to consume protected server resources from your Vue.js client application.

Conclusion

You have implemented user authentication in Vue.js to identify your users, get user profile information, and control the content that your users can access by protecting routes and API resources.

This guide covered the most common authentication use case for a Vue.js application: simple login and logout. However, Auth0 is an extensible and flexible identity platform that can help you achieve even more. If you have a more complex use case, check out the Auth0 Architecture Scenarios to learn more about the typical architecture scenarios we have identified when working with customers on implementing Auth0.

We'll cover advanced authentication patterns and tooling in future guides, such as using a pop-up instead of a redirect to log in users, adding permissions to ID tokens, using metadata to enhance user profiles, and much more.