developers

The Complete Guide to Vue.js User Authentication with Auth0

Learn how to add login, logout, and sign-up to Vue.js using a plugin, how to create vue-router guards, and how to make secure API calls from a Vue.js app.

Nov 18, 20201 min read

Check out the "Vue Authentication By Example" developer guide for the latest guidance on how to secure a Vue application using token-based authentication.
This guide has been deprecated. It uses an older version of the Auth0 Vue SDK. Please follow the "Vue.js Authentication By Example" developer guide instead.
This guide has been deprecated.

Learn how to secure a Vue.js application by implementing user authentication, which includes user login, logout, and sign-up, along with protecting Vue.js routes. You'll enhance a starter Vue.js application to practice the following security concepts:

  • Add user login and logout.
  • Retrieve user information.
  • Protect application routes.
  • Call protected endpoints from an API.

Look for the 🛠️️ emoji if you'd like to skim through the content while focusing on the build steps.

This guide uses the Auth0 SPA SDK to secure Vue.js applications, which provides Vue.js developers with an easier way to add user authentication to Vue.js applications using a Vue.js plugin. The Auth0 SPA SDK provides 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.

Auth0 Vue.js sample app

This guide covers Vue.js 2.

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.

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

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. 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.js 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.js project dependencies:

npm install

🛠 Run the Vue.js project:

npm run serve

Connect Vue.js with Auth0

The best part of the Auth0 platform is how streamlined it is to get started by following these steps:

Sign up and create an Auth0 Application

If you haven't already,

sign up for a free Auth0 account →

A free account 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:

  • Name: Auth0 Vue.js Sample

  • Application Type: 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 as a native mobile app for Android and iOS. 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 would belong to the Auth0 Vuetigram tenant, which shares them across its Auth0 applications.

Create a communication bridge between Vue.js and Auth0

When you use Auth0, 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 and fill in the following values:

🛠 Allowed Callback URLs

http://localhost:4040

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

🛠 Allowed Logout URLs

http://localhost:4040

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

🛠 Allowed Web Origins

http://localhost:4040

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.

🛠 Open the Vue.js starter project,

auth0-vue-sample
, and create an
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": "",
  "serverUrl": ""
}

🛠 Head back to your Auth0 application page. Follow these steps to get the

domain
and
clientId
values:

Auth0 application settings to enable user authentication

  1. 🛠 Click on the "Settings" tab, if you haven't already.

  2. 🛠 Use the "Domain" value from the "Settings" as the value of

    domain
    in
    auth_config.json
    .

  3. 🛠 Use the "Client ID" value from the "Settings" as the value of

    clientId
    in
    auth_config.json
    .

You'll learn more about the

audience
and
serverUrl
properties in the Call an API section. For now, you can leave those values as empty strings.

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

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 on 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.

If you want to learn how to create the Vue.js authentication plugin from scratch, head to the How to Create a Vue.js Plugin tutorial. If you already completed the Vue.js plugin tutorial, you can jump to the Add User Authentication section.

Otherwise, follow the instructions in this section to quickly create the Vue.js authentication plugin.

🛠 Execute the following command to install the Auth0 SPA SDK:

npm install @auth0/auth0-spa-js

🛠 Create an

auth
directory within the
src
directory:

mkdir src/auth

🛠 Create an

auth0-plugin.js
file within the
src/auth
directory:

touch src/auth/auth0-plugin.js

🛠 Populate

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

/**
 *  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);
      },

      getTokenSilently(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.$auth = useAuth0(options);
  },
};

🛠 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 the Auth0 configuration and plugin
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');

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
.

Add User Authentication

You need to provide UI elements for your users to trigger authentication events: login, logout, and sign up.

Create a login button

🛠 Create a

LoginButton.vue
file under the
src/components/
directory:

touch src/components/LoginButton.vue

🛠 Populate

src/components/LoginButton.vue
like so:

<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>

As mentioned during the authentication plugin creation, you can pass a configuration object to

loginWithRedirect()
to customize the login experience. For example, you can 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 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",
}

🛠 Create a

SignupButton.vue
file under the
src/components/
directory:

touch src/components/SignupButton.vue

🛠 Populate

src/components/SignupButton.vue
like so to define a
SignupButton
component:

<template>
  <button
    class="btn btn-primary btn-block"
    v-if="!$auth.isAuthenticated"
    @click="signup"
  >
    Sign Up
  </button>
</template>

<script>
  export default {
    name: 'SignupButton',
    methods: {
      signup() {
        this.$auth.loginWithRedirect({
          screen_hint: 'signup',
        });
      },
    },
  };
</script>

Using the 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

LoginButton
and
SignupButton
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 next sections.

Create a logout button

🛠 Create a

LogoutButton.vue
file under the
src/components/
directory:

touch src/components/LogoutButton.vue

Populate

src/components/LogoutButton.vue
like so:

<template>
  <button
    class="btn btn-danger btn-block"
    v-if="$auth.isAuthenticated"
    @click.prevent="logout"
  >
    Log Out
  </button>
</template>

<script>
  export default {
    name: 'LogoutButton',
    methods: {
      logout() {
        this.$auth.logout();
        this.$router.push({ path: '/' });
      },
    },
  };
</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.

Here, you pass the

returnTo
option to specify the URL where Auth0 should redirect your users after they logout. 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.

Read more about how Logout works at Auth0.

Integrate the login and logout buttons

Let's wrap the

LoginButton
and
LogoutButton
into a component called
AuthenticationButton
.

🛠 Create an

AuthenticationButton.vue
file under the
src/components/
directory:

touch src/components/AuthenticationButton.vue

🛠 Populate

src/components/AuthenticationButton.vue
with the following code:

<template>
  <div>
    <LogoutButton v-if="$auth.isAuthenticated" />
    <LoginButton v-else />
  </div>
</template>

<script>
  import LoginButton from '@/components/LoginButton';
  import LogoutButton from '@/components/LogoutButton';

  export default {
    name: 'AuthenticationButton',
    components: { LogoutButton, LoginButton },
  };
</script>

There are some advantages to using this

AuthenticationButton
component wrapper:

You can build flexible interfaces.

AuthenticationButton
serves as a "log in/log out" switch that you can put anywhere you need that switch functionality. However, you still have separate
LoginButton
and
LogoutButton
components for cases when you need their functionality in isolation. For example, you may have a
LogoutButton
on a page that only authenticated users can see.

You can build extensible interfaces. You can easily swap the

LoginButton
component with the
SignupButton
component in
AuthenticationButton
to create a "sign up/log out" switch. You could also wrap the "sign up/log out" switch in a
NewAuthenticationButton
component.

You can build declarative interfaces. Using

AuthenticationButton
, you can add login and logout functionality to your
NavBar
component, for example, without thinking about the implementation details of how the authentication switch works.

🛠 With that in mind, create an

AuthNav.vue
file under the
src/components/
directory:

touch src/components/AuthNav.vue

🛠 Populate

src/components/AuthNav.vue
with the following code:

<template>
  <div class="navbar-nav ml-auto">
    <AuthenticationButton />
  </div>
</template>

<script>
  import AuthenticationButton from '@/components/AuthenticationButton';

  export default {
    name: 'AuthNav',
    components: { AuthenticationButton },
  };
</script>

🛠 Finally, open

NavBar.vue
under the
src/components/
directory and update it like so, adding
AuthNav
to it:

<template>
  <div class="nav-container mb-3">
    <nav class="navbar navbar-expand-md navbar-light bg-light">
      <div class="container">
        <div class="navbar-brand logo"></div>
        <MainNav />
        <AuthNav />
      </div>
    </nav>
  </div>
</template>

<script>
  import MainNav from '@/components/MainNav';
  import AuthNav from '@/components/AuthNav';

  export default {
    name: 'NavBar',
    components: { AuthNav, MainNav },
  };
</script>

By having different navigation bar subcomponents, you can extend each as you need without reopening and modifying the main

NavBar
component.

🛠 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

Experiment: Use the

SignupButton
component

Swap the

LoginButton
component with the
SignupButton
component in the
<template>
of the
AuthenticationButton
component.

<template>
  <div>
    <LogoutButton v-if="$auth.isAuthenticated" />
    <SignupButton v-else />
  </div>
</template>

<script>
  import SignupButton from '@/components/SignupButton';
  import LogoutButton from '@/components/LogoutButton';

  export default {
    name: 'AuthenticationButton',
    components: { LogoutButton, SignupButton },
  };
</script>

When you click the "Sign Up" button, you'll land on a page with language optimized to encourage you to sign up for your Vue.js application.

Try this out!

New Auth0 Universal Login Experience Signup Page

Once you complete this experiment, swap back

SignupButton
with
LoginButton
to continue with the rest of this guide:

<template>
  <div>
    <LogoutButton v-if="$auth.isAuthenticated" />
    <LoginButton v-else />
  </div>
</template>

<script>
  import LoginButton from '@/components/LoginButton';
  import LogoutButton from '@/components/LogoutButton';

  export default {
    name: 'AuthenticationButton',
    components: { LogoutButton, LoginButton },
  };
</script>

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 and Auth0 redirects you to your Vue.js app, the login button briefly shows up (blue color), and then the logout button renders (red color).

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

Auth0Plugin
initializes.

🛠 To fix that UI flashing, use the

isLoading
boolean value exposed by the
Auth0Plugin
through the
$auth
global property to render the
App
component once the Auth0 SPA SDK has finished loading.

🛠 Open

src/App.vue
and update it as follows:

<template>
  <Loading v-if="$auth.isLoading" />
  <div v-else id="app" class="d-flex flex-column h-100">
    <template>
      <NavBar />
      <div class="container flex-grow-1">
        <div class="mt-5">
          <router-view />
        </div>
      </div>
      <footer />
    </template>
  </div>
</template>

<script>
  import NavBar from '@/components/NavBar';
  import Footer from '@/components/Footer';
  import Loading from '@/components/Loading';

  export default {
    components: {
      Loading,
      Footer,
      NavBar,
    },
  };
</script>

While the SDK is loading, the

Loading
component, which has a cool animation, renders. Log out and log back in to see this in action.

Retrieving 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 the

Profile
component in
src/views/Profile.vue
as follows:

<template>
  <div>
    <div class="row align-items-center profile-header">
      <div class="col-md-2 mb-3">
        <img
          :src="$auth.user.picture"
          alt="User's profile picture"
          class="rounded-circle img-fluid profile-picture"
        />
      </div>
      <div class="col-md text-center text-md-left">
        <h2>{{ $auth.user.name }}</h2>
        <p class="lead text-muted">{{ $auth.user.email }}</p>
      </div>
    </div>

    <div class="row">
      <pre class="col-12 text-light bg-dark p-4">
{{
        JSON.stringify($auth.user, null, 2)
      }}</pre
      >
    </div>
  </div>
</template>

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
component renders user information that you could consider protected. 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.

As such, you should protect the route that renders this component,

http://localhost:4040/profile
. You'll learn how to do just that in the next section.

Protecting 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

authenticationGuard.js
file in the
src/auth
directory:

touch src/auth/authenticationGuard.js

You'll use the

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

🛠 Populate

src/auth/authenticationGuard.js
with the following Vue.js navigation guard template:

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

Every guard function receives three arguments:

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

  • 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
    .

🛠 Next, you'll get the instance from your

auth0-plugin
module and set up a watcher on its
isLoading
property:

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.

🛠 Next, define a

guardAction()
function to execute in
authenticationGuard
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: { targetUrl: 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 pipeline. But, since there are no hooks left, the navigation is confirmed, and the user can access the route.

Otherwise, 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: { targetUrl: to.fullPath }

The

appState.targetUrl
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/auth/auth0-plugin.js
will use that
appState
object to redirect the user to that URL:

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

🛠 So far, your

authenticationGuard
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 need to make a small addition to your

authenticationGuard
like so:

import { getInstance } from './auth0-plugin';

export const authenticationGuard = (to, from, next) => {
  const authService = getInstance();

  const guardAction = () => {
    if (authService.isAuthenticated) {
      return next();
    }

    authService.loginWithRedirect({ appState: { targetUrl: 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 a "loading" state to "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:

import Vue from 'vue';
import Router from 'vue-router';
import Home from '../views/Home.vue';
import Profile from '../views/Profile.vue';
import ExternalApi from '@/views/ExternalApi';
import { authenticationGuard } from '@/auth/authenticationGuard';

Vue.use(Router);

const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/profile',
      name: 'profile',
      component: Profile,
      beforeEnter: authenticationGuard,
    },
    {
      path: '/external-api',
      name: 'external-api',
      component: ExternalApi,
      beforeEnter: authenticationGuard,
    },
  ],
});

export default router;

You use the

authenticationGuard
to protect two routes,
/profile
and
/external-api
, by adding it as the value of the
beforeEnter
route configuration property. This means that Vue.js will run your
authenticationGuard
before it accesses that route and renders any of its components.

🛠 You can now test that these two paths require users to log in before they can access them. Log out and try to access the Profile or External API page. If it works, Vue.js redirects you to log in with Auth0.

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.
    • Returning all the user data from the server and letting the front-end framework decide what to display and what to hide based on the user authentication status is the wrong approach.
    • 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.

Calling an API

This section focuses on showing 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.

Instead of creating an API from scratch to test the authentication and authorization flows between the client and the server, you'll use a demo Express API that I've prepared for you.

Get the Express API demo

🛠 Open a new terminal window and clone the

auth0-express-js-sample
repo somewhere in your system. Ensure that you clone it outside your Vue.js project directory.

git clone git@github.com:auth0-blog/auth0-express-js-sample.git

🛠 Once you clone this repo, make the

auth0-express-js-sample
directory your current directory:

cd auth0-express-js-sample

🛠 Install the Node.js project dependencies:

npm install

Connect the Express API with Auth0

Create a communication bridge between Express and Auth0

This process is similar to how you connected Vue.js with Auth0.

🛠 Head to the APIs section in the Auth0 Dashboard, and click the "Create API" button.

🛠 Then, in the form that Auth0 shows:

  • Add a Name to your API:
Auth0 Express Sample
  • Set its Identifier value:
https://express.example.com
  • Leave the signing algorithm as
    RS256
    as it's the best option from a security standpoint.

Auth0 Dashboard new API form

Identifiers are unique strings that help Auth0 differentiate between your different APIs. We recommend using URLs to facilitate creating unique identifiers predictably; however, Auth0 never calls these URLs.

🛠 With these values in place, hit the "Create" button. Keep this page open as you'll need some of its values in the next section.

Add the Auth0 configuration variables to Express

🛠 Create a

.env
file for the API Server under the
auth0-express-js-sample
directory:

touch .env

🛠 Populate this

auth0-express-js-sample/.env
file as follows:

SERVER_PORT=6060
CLIENT_ORIGIN_URL=http://localhost:4040
AUTH0_AUDIENCE=
AUTH0_DOMAIN=

🛠 Head back to your Auth0 API page, and follow these steps to get the Auth0 Audience:

Get the Auth0 Audience to configure an API

  1. 🛠 Click on the "Settings" tab.

  2. 🛠 Locate the "Identifier" field and copy its value.

  3. 🛠 Paste the "Identifier" value as the value of

    AUTH0_AUDIENCE
    in
    .env
    .

Now, follow these steps to get the Auth0 Domain value:

Get the Auth0 Domain to configure an API

  1. 🛠 Click on the "Test" tab.
  2. 🛠 Locate the section called " Asking Auth0 for tokens from my application".
  3. 🛠 Click on the cURL tab to show a mock
    POST
    request.
  4. 🛠 Copy your Auth0 domain, which is part of the
    --url
    parameter value:
    tenant-name.region.auth0.com
    .
  5. 🛠 Paste the Auth0 domain value as the value of
    AUTH0_DOMAIN
    in
    .env
    .
Tips to get the Auth0 Domain
  • The Auth0 Domain is the substring between the protocol,

    https://
    and the path
    /oauth/token
    .

  • The Auth0 Domain follows this pattern:

    tenant-name.region.auth0.com
    .

  • The

    region
    subdomain (
    au
    ,
    us
    , or
    eu
    ) is optional. Some Auth0 Domains don't have it.

  • Click on the image above, please, if you have any doubt on how to get the Auth0 Domain value.

🛠 With the

.env
configuration values set, run the API server by issuing the following command:

npm start

Configure Vue.js to connect with the Express API

🛠 Head back to the

auth0-vue-sample
project directory that stores your Vue.js application.

🛠 Open the

auth_config.json
file and update it as follows, providing a value to
audience
and
serverUrl
:

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

Let's understand better what the

audience
and
serverUrl
properties represent.

The

serverUrl
is simply the URL where your Express server is listening for requests. In production, you'll change this value to the URL of your live server.

Your Vue.js application needs to 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

Auth0Plugin
.

Notice how the value of

audience
is the same as
AUTH0_AUDIENCE
from
auth0-express-js-sample/.env
.

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".

The actions that your Vue.js application can perform on the API depend on the scopes that your access token contains, which you could define as a

scope
options for
Auth0Plugin
.

Remember that screen you saw when you first logged in with Auth0 asking you for permission to access your profile information? Your Vue.js application will request authorization from the user to access the requested scopes, and the user will approve or deny the request. You may have seen something similar when sharing your contacts or photos from a social media platform with a third-party application.

When you don't pass a

scope
option to
Auth0Plugin
as in the example above, Auth0 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".

Your

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 build up the "External API" page to let users retrieve public and protected messages.

🛠 Update

src/views/ExternalApi.vue
as follows:

<template>
  <div>
    <h1>External API</h1>
    <p>
      You use will use a button to call an external API using an access token,
      and the API will validate it using the API's audience value.
      <strong>This route should be private</strong>.
    </p>
    <div
      class="btn-group mt-5"
      role="group"
      aria-label="External API Requests Examples"
    >
      <button type="button" class="btn btn-primary" @click="callApiEndpoint">
        Get Public Message
      </button>
      <button
        type="button"
        class="btn btn-primary"
        @click="callApiSecuredEndpoint"
      >
        Get Private Message
      </button>
    </div>

    <div v-if="apiMessage" class="mt-5">
      <h6 class="muted">Result</h6>
      <div class="container-fluid">
        <div class="row">
          <code class="col-12 text-light bg-dark p-4">
            {{ JSON.stringify(apiMessage, null, 2) }}
          </code>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import { serverUrl } from '../../auth_config.json';
  export default {
    name: 'Api',
    data() {
      return {
        apiMessage: null,
      };
    },
    methods: {
      async callApiEndpoint() {
        try {
          const response = await fetch(
            `${serverUrl}/api/messages/public-message`,
          );

          const json = await response.json();
          this.apiMessage = json.message;
        } catch (e) {
          this.apiMessage = `Error: the server responded with '${e.response.status}: ${e.response.statusText}'`;
        }
      },
      async callApiSecuredEndpoint() {
        const accessToken = await this.$auth.getTokenSilently();

        try {
          const response = await fetch(
            `${serverUrl}/api/messages/protected-message`,
            {
              headers: {
                Authorization: `Bearer ${accessToken}`,
              },
            },
          );

          const json = await response.json();
          this.apiMessage = json.message;
        } catch (e) {
          console.log(e);
          this.apiMessage = `Error: the server responded with '${e.response.status}: ${e.response.statusText}'`;
        }
      },
    },
  };
</script>

What is happening now within the

ExternalApi
component?

  • You add a

    callApiEndpoint()
    method that performs a public API request.

  • You add a

    callApiSecuredEndpoint()
    method that performs a secure API request as follows:

    • (a) Get the access token from Auth0 using the

      this.$auth.getTokenSilently()
      method, which gets your Vue.js application a new access token under the hood without requiring the user to log in again.

    • (b) Pass that access token as a bearer credential in the authorization header of the request.

  • You use the

    this.apiMessage
    property to update the user interface whenever any of the described API calls complete successfully.

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.

You should not store tokens in
localStorage
. Why?

Storing tokens in browser local storage provides persistence across page refreshes and browser tabs, however if an attacker can run JavaScript in the Single-Page Application (SPA) using a cross-site scripting (XSS) attack, they can retrieve the tokens stored in local storage.

A vulnerability leading to a successful XSS attack can be either in the SPA source code or in any third-party JavaScript code included in the SPA, such as Bootstrap, jQuery, or Google Analytics.

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

🛠 Visit

http://localhost:4040/external-api
and click any of the buttons on the External API page to test the responses.

Get Public Message:

The API doesn't require an access token to share this message.

Get Protected Message:

The API successfully validated your access token.

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 tutorial covered the most common authentication use case for a Vue.js application: simple login and logout. However, Auth0 is an extensible and flexible 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.

In a follow-up guide, we'll cover advanced authentication patterns and tooling, 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.

Let me know in the comments below what you thought of this tutorial. Thank you for reading, and stay tuned, please.