Create Biometric-Powered Login Pages in Vue.js
Tags: vue, passwordless, security, tutorial
Published: 2022-02-08 17:46:23

In this article, you will learn how to build a simple Vue 3 application and add biometric authentication using Passage.

Users will log in to your application using the biometrics built into their devices (e.g. Face ID, Windows Hello, etc) or with magic links sent to their email. This app is built such that it only allows authenticated users to view a simple dashboard and blocks unauthenticated users. This guide will walk through creating a Vue 3 app using the Vue CLI, creating basic components, and adding authentication to the application using Passage.

If you are already familiar with Vue, you can go straight to our full example application on [GitHub](https://github.com/passageidentity/example-vue) or skip to [this section](#add-authentication-with-passage) to learn how to integrate biometric authentication into an existing application.

## Setup

To get started, first install the Vue CLI. The Vue CLI lets you get up and running quickly with pre-configured build settings.

```bash
npm install -g @vue/cli
```

Then create a new application using the Vue CLI. The tool will provide you with options to manually select versions and features you want. For this tutorial, use the "Manually select features" option and select the "Router" feature. Make sure to select Vue 3. You can just hit enter through the remaining features.

```bash
vue create example-app 
cd example-app
```

You can test your app by running the following command and then visiting *http://localhost:8080*.

```bash
npm run serve
```

You can leave this up and running throughout the tutorial to see your progress.

## Build Components for App

### Set up routes for Home and Dashboard pages

Our application will have two pages, a home page with a login screen and a dashboard page that is authenticated. First, create the directory structure and routes for those pages. Create the following directories and files to set up for the router and our two main components.

```bash
cd src/
mkdir views
touch views/Dashboard.vue
touch views/Home.vue
```

Now let's start filling out these files. Copy the following code into the *router/index.js* file to replace the default router.

```jsx
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Dashboard from '../views/Dashboard.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: Dashboard
  }
]

const router = createRouter({
  history: createWebHistory(),
  mode: 'history',
  routes
})

export default router
```

## Create a Banner component

Create a banner component that will be used on both the home and dashboard pages. Copy the following code to *components/Banner.vue*.

```html
<template>
  <div class="mainHeader">
    <a href="https://passage.id/"><div class="passageLogo"></div></a>
    <div class="header-text">Passage + Vue.js 3 Example App</div>
    <div class="spacer"></div>
    <a class="link" href="https://passage.id/">Go to Passage</a>
  </div>
</template>
<script>
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Banner',
})
</script>
<style scoped>
.mainHeader{
  width: 100%;
  padding: 20px 30px;
  display: flex;
  align-items: center;
  background-color: #27417E;
  color: white;
}

.header-text {
  font-size: 24px;
  margin-left: 10px;
}
  
.passageLogo {
  background-image: url('https://storage.googleapis.com/passage-docs/passage-logo.svg');
  background-repeat: no-repeat;
  width: 60px;
  height: 60px;
  cursor: pointer;
}

.spacer {
  flex-grow: 1;
}
  
.link {
  margin-left: 20px;
  color: white;
  text-decoration-color: white;
}
</style>
```

Replace the template and script tags in *App.vue* to use the router and add some simple styling.

```html
<template>
<div>
  <Banner />
  <div class="main-container">
    <router-view/>
  </div>
  <div className="footer">
    Learn more with our 
    <a href="https://docs.passage.id">Documentation</a> and
    <a href="https://github.com/passageidentity">Github</a>.
    <br>       
  </div>
</div>
</template>
<style>
body {
  margin: 0px;
  height: 100vh;
  font-family: sans-serif;
  background-color: #E5E5E5;
}

.main-container {
  background: white;
  box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
  border-radius: 20px;
  width: 310px;
  min-height: 310px;
  margin: 30px auto;
}

.footer {
  text-align: center;
  font-size: 18px;
}
</style>
```

and add the router and banner to *main.js*.

```jsx
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Banner from './components/Banner.vue'

createApp(App)
  .use(router)
  .component('Banner', Banner)
  .mount('#app')
```

This means that once the components are created, the home page will be accessible at *http://localhost:8080/* and the dashboard will be at *http://localhost:8080/dashboard*.

## Build Home Component

Add the following code to *views/Home.vue* to create the home page.

```html
<script>
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Home',
})
</script>
```

## Build Dashboard Component

Add the following code to *views/Dashboard.vue* to create the simple dashboard page.

```html
<template>
  <div class="dashboard">
        <div class="title">Welcome!</div>
  </div>
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Dashboard',
})
</script>

<style scoped>
.dashboard{
  padding: 30px 30px 20px;
}

.title {
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 30px;
}

.message {
  overflow-wrap: anywhere;
}

.link {
  color: black;
  text-decoration-color: black;
}
</style>
```

# Add Authentication with Passage

Now it is time to add authentication to our application using Passage! First, install Passage from the root directory of your example app.

```bash
npm install --save @passageidentity/passage-auth
```

Then import the package in the module where you intend to use the custom element, in this case the *Home.vue* view.

```jsx
import '@passageidentity/passage-auth'
```

Importing this script will register the Passage custom element for use in your Vue components. For more information about custom elements refer to the [online documentation.](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)

Create an application in the [Passage Console](https://console.passage.id/register) with the following settings:

- Authentication Origin: `http://localhost:8080`
- Redirect URL: `/dashboard`


![Create app page in Passage Console.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m57qzetjo2xg3lknhjqf.png)

Once you've created your application, copy your Application ID out the console and into a *.env* file in the root of your example repository.

```yaml
# .env
VUE_APP_PASSAGE_APP_ID=
```

In the Home component, import Passage and add the custom element to the template. ‍

```html
<template>
  <passage-auth :app-id="appId"></passage-auth>
</template>

<script>
import { defineComponent } from 'vue'
import '@passageidentity/passage-auth'

export default defineComponent({
  name: 'Home',
  setup() {
    const appId = process.env.VUE_APP_PASSAGE_APP_ID
    return {
      appId,
    }
  },
})
</script>
```

Your application now has a full login and register experience!


![Sample app with Passage login.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubz5dk5cg30rvr4brwux.png)


You might notice a warning in the console about the custom element. Vue works with custom elements out of the box, but by default it will log a warning to the console that it could not resolve the component for the custom element. To configure Vue with information that the `<passage-auth>` tag is a custom element and suppress this warning, you need to add this configuration to your *vue.config.js* file. Create this file at the top-level directory of your repository. 

```jsx

module.exports = {
  publicPath: '/',
  chainWebpack: (config) => {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap((options) => ({
        ...options,
        compilerOptions: {
          // treat any tag that starts with passage- as custom elements
          isCustomElement: (tag) => tag.startsWith('passage-'),
        },
      }))
  },
}
```

Once you’ve added this, you will need to restart the server for the changes to take effect.

# Verifying User Authentication

Last but not least, the application needs to prevent unauthenticated users from accessing the dashboard page. You will set up protections that will show an error message to unauthenticated users trying to access the dashboard, but that does not prevent them from reading any data that might be on the dashboard, since it is all stored in the JavaScript files.

For simplicity, there is not a backend server in this example. A simple authentication check using the `PassageUser` class will be implemented to protect the dashboard page from unauthorized access.

<aside>
💡 Please keep in mind that this dashboard protection will not protect sensitive API endpoints. Your server should always use one of the [Passage backend libraries](https://docs.passage.id/backend-libraries/overview) to authorize users before returning sensitive data.

</aside>

This check is implemented by creating a composable to check the authentication status of the current user using Passage. Create a file called *useAuthStatus.js* in the *composables* directory.

```bash
mkdir composables/
touch composables/useAuthStatus.js
```

Copy the following code into that file. This code uses Passage to check if the current user is authenticated.

```jsx
import { ref } from 'vue'
import { PassageUser } from '@passageidentity/passage-auth/passage-user'

export function useAuthStatus(){
  const isLoading = ref(true)
  const isAuthorized = ref(false)
  const username = ref('')

  new PassageUser().userInfo().then(userInfo => {
      if(userInfo === undefined){
          isLoading.value = false
          return
      }
      username.value = userInfo.email ? userInfo.email : userInfo.phone
      isAuthorized.value = true
      isLoading.value = false
  })

  return {
    isLoading,
    isAuthorized,
    username,
  }
}
```

Next, incorporate this check into the Dashboard component, since authentication is required before showing the dashboard. The dashboard will show two different messages based on the result of the authentication check. The final *Dashboard.vue* will look like this.

```html
<template>
  <div class="dashboard">
    <div v-if="isLoading"/>
      <div v-else-if="isAuthorized">
        <div class="title">Welcome!</div>
        <div class="message">
          You successfully signed in with Passage.
          <br/><br/>
          Your Passage User ID is: <b>{{username}}</b>
        </div>
      </div>
    <div v-else>
      <div class="title">Unauthorized</div>
      <div class="message">
        You have not logged in and cannot view the dashboard.
        <br/><br/>
        <a href="/" class="link">Login to continue.</a>
      </div>
    </div>
  </div>
</template>

<script>
import { defineComponent } from 'vue'
import { useAuthStatus } from '../composables/useAuthStatus'

export default defineComponent({
  name: 'Dashboard',
  setup() {
    const {isLoading, isAuthorized, username} = useAuthStatus()
    return {
      isLoading,
      isAuthorized,
      username,
    }
  },
})
</script>

<style scoped>
.dashboard{
  padding: 30px 30px 20px;
}

.title {
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 30px;
}

.message {
  overflow-wrap: anywhere;
}

.link {
  color: black;
  text-decoration-color: black;
}
</style>
```

Unauthenticated users who try to visit */dashboard* will be shown an "Unauthorized" message, while authorized users will see the dashboard that includes their Passage User ID.

# Conclusion

Now you can try out the biometrics authentication in the application you just built! Your application should look something like this and you can see the login experience as your users would. 

![Example login screens for users.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ur7e42fcgwjqzsejibf.png)

To recap, you have just:

- created an application with Vue.js
- added biometric authentication to your app with Passage
- learned how to verify the authentication status of your users with Passage

Keep an eye out for part 2 of this post, where we show you how to use Passage to protect your backend API endpoints in a Vue.js + Express.js web application!

To learn more about Passage and biometric authentication for web applications, you can:

- Explore our [dashboard](https://console.passage.id/) to view and create users, customize your application, and add friends
- Read our [guides](https://docs.passage.id/) for other tech stacks and learn how to authorize requests in your backend server
- Join our [Discord](https://discord.com/invite/445QpyEDXh) and say hi

‍Passage is in beta and actively seeking feedback on the product. If you have feedback, bug reports, or feature requests, we would love to hear from you. You can email me at [anna@passage.id](mailto:anna@passage.id) or fill out [this form](https://airtable.com/shriJ6n4asgxuGAdU).

---

This article was originally published on the [Passage blog](https://passage.id/blog).