How to Build Production-ready Vue Authentication
Tags: vue, vuerouter, authentication
Published: 2019-03-04 12:31:14

---
title: How to Build Production-ready Vue Authentication
published: true
description: How to implement a real user authentication flow in Vue with Vue Router & AWS Amplify.
tags: vue, vue router, authentication
---

![Production-ready Vue Authentication with AWS](https://thepracticaldev.s3.amazonaws.com/i/y99r9vv53zvu298c4mev.jpg)

> To view the final code for this project at any time, click [here](https://github.com/dabit3/production-ready-vue-authentication).

In this tutorial, you'll learn how to build a real authentication flow into your Vue application using [Vue Router](https://router.vuejs.org/), [AWS Amplify](https://aws-amplify.github.io/), & [Amazon Cognito](https://aws.amazon.com/cognito/). While the identity provider we'll be using is AWS with Amazon Cognito, the fundamental design of our application will be provider agnostic meaning you should be able to follow along using the provider of your choice.

{% twitter 1102548380801290243 %}

### Authentication overview

If you've ever tried rolling your own authentication service & implementation (both on the front & back end), you're already aware of the pain that comes along with it.

Thankfully today we have many amazing identity services & providers that handle all of this for us. You may already be familiar with services like [Auth0](https://auth0.com/), [Okta](https://www.okta.com/), & [Amazon Cognito](https://aws.amazon.com/cognito/) that do the hard work behind the scenes so you don't have to by implementing the user & identity management that is a necessary requirement for most modern applications.

In this tutorial you'll learn how you can manage everything from user sign up, user sign in, forgot password, & MFA. You'll also learn how to implement protected client side routing with Vue Router so you can define which routes can be public & which routes need to be protected for only logged-in users.

By the end of this tutorial you'll have a good grasp on building & deploying Vue applications with enterprise-level security & authentication enabled.

# Getting Started

### Creating the Vue project

The first thing we'll do is scaffold a new Vue application using the Vue CLI. If you do not already have the Vue CLI installed, click [here](https://cli.vuejs.org/guide/installation.html) to follow the installation instructions.

```sh
~ vue create vue-auth

? Please pick a preset: default

cd vue-auth
```

Once the project has been created & you are inside of the directory, let's install the necessary dependencies we'll need using either npm or yarn:

```sh
~ yarn add vue-router aws-amplify @aws-amplify/ui-vue
```

> What is `aws-amplify-vue `? AWS Amplify has platform specific components that allow us to quickly scaffold & get up and running with important functionality like authentication flows, image uploads, & more.

#### Creating the folder structure

Let's now create the files that we'll be using to implement the authentication flow. Inside the __src__ directory, create the following files:

```sh
~ touch router.js components/Auth.vue components/Home.vue components/Profile.vue components/Protected.vue
```

# Working with Amplify

### Installing the Amplify CLI

To add the authentication service we'll be using the AWS Amplify CLI. Let's go ahead and install that now:

```sh
~ npm install -g @aws-amplify/cli
```

Next, we'll need to configure the CLI. To do so, run the following command:

```sh
~ amplify configure
```

> For a full walkthrough of how to configure the CLI, check out [this](https://www.youtube.com/watch?v=n4DuYTzpvdE) video.

Now that we have our project created & the CLI installed we can create the authentication service we'll be using. To do so, we'll initialize a new Amplify project & then add authentication to it.

#### Initializing the Amplify project

To initialize a new Amplify project, run the `init` command:

```sh
~ amplify init
```

> The `init` will initialize the project & walk you through some steps to configure your project name, environment, & other build settings. Choose a project & environment name, & then choose the default for the rest of the questions.

#### Adding the authentication service

Now that the Amplify project has been initialized, we can add the authentication service:

```sh
~ amplify add auth

? Do you want to use the default authentication and security configuration? Default configuration
? How do you want users to be able to sign in? Username
? Do you want to configure advanced settings? No

~ amplify push
```

> The default security configuration will give us smart defaults for this project. If you would like a custom configuration you can choose `No`, or you can later run `amplify update auth`.

After `amplify push` finished running successfully, the authentication has been successfully created & we can now start writing our code!

You should notice you now have a file called __aws-exports.js__ (holds base project configuration) in your __src__ directory & a folder called __amplify__ (hold detailed project configuration & custom code) in your root directory.

# Writing the code

We'll be implementing authentication in two ways:

1. Part 1 - Using the preconfigured `amplify-authenticator` component from AWS Amplify Vue to quickly get our auth flow up & running.
2. Part 2 - Building out an entirely custom authentication flow.

## Part 1 - Using the preconfigured `amplify-authenticator` component

Next we'll need to update __main.js__ to configure the Vue project to work with Amplify & our new __aws-exports.js__ file. We'll also need to let our application know about the router that we will be creating in the next step.

#### src/main.js

```js
// src/main.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Amplify from 'aws-amplify'
import '@aws-amplify/ui-vue'
import config from './aws-exports';

import App from './App'
import router from './router'

Amplify.configure(config)
Vue.use(VueRouter)
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')
```

Next, we'll configure our router. This is where we will also place the custom logic for the protected routes.

#### src/router.js

```js
// src/router.js
import VueRouter from 'vue-router'
import { Auth } from 'aws-amplify'

import Home from './components/Home'
import Profile from './components/Profile'
import AuthComponent from './components/Auth'
import Protected from './components/Protected'

const routes = [
  { path: '/', component: Home },
  { path: '/auth', component: AuthComponent },
  { path: '/protected', component: Protected, meta: { requiresAuth: true} },
  { path: '/profile', component: Profile, meta: { requiresAuth: true} }
]

const router = new VueRouter({
  routes
})

router.beforeResolve((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    Auth.currentAuthenticatedUser().then(() => {
      next()
    }).catch(() => {
      next({
        path: '/auth'
      });
    });
  }
  next()
})

export default router
```

##### Details of src/router.js
1. We import Vue & VueRouter
2. We import the components we'll be using in our routes
3. We define an array of routes. We add an additional meta property to specify the routes requiring authentication by using a Boolean named `requiresAuth`.
4. We create the router variable
5. We use the [beforeResolve](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards) guard from Vue Router, which will be called right before the navigation is confirmed, to check if the user is authenticated. If they are authenticated, we allow them onto the next route. If they are not, we redirect them to the sign up page (__/auth__).

Next, let's create the authentication component.

#### src/components/Auth.vue

```vue
// src/components/Auth.vue
<template>
  <div class="auth">
    <amplify-authenticator></amplify-authenticator>
  </div>
</template>

<script>

export default {
  name: 'auth'
}
</script>

<style>
.auth {
  margin: 0 auto;
  width: 460px;
}
</style>
```

#### Details of src/components/Auth.vue
This is a pretty basic component that does quite a bit under the hood! The `amplify-authenticator` Vue component will actually scaffold out the entire authentication flow for us (Sign-up, Sign-in, & forgot password).

Now we'll update the __App__ component. This component will be doing a few things:

1. Displaying the navigation links
2. Rendering the router
3. Holding most authentication logic for listening to user sign-in / sign-out.

#### src/App.vue

```vue
// src/App.vue
<template>
  <div id='app'>
    <div  class='nav'>
      <router-link tag="p" to="/">
        <a>Home</a>
      </router-link>
      <router-link tag="p" to="/profile">
        <a>Profile</a>
      </router-link>
      <router-link tag="p" to="/protected">
        <a>Protected</a>
      </router-link>
      <router-link tag="p" to="/auth" v-if="!signedIn">
        <a>Sign Up / Sign In</a>
      </router-link>
    </div>
    <router-view></router-view>
    <div class='sign-out'>
      <amplify-sign-out v-if="signedIn"></amplify-sign-out>
    </div>
  </div>
</template>

<script>
import { Auth, Hub } from 'aws-amplify'

export default {
  name: 'app',
  data() {
    return {
      signedIn: false
    }
  },
  beforeCreate() {
    Hub.listen('auth', data => {
      console.log('data:', data)
      const { payload } = data
      if (payload.event === 'signIn') {
        this.signedIn = true
        this.$router.push('/profile')
      }
      if (payload.event === 'signOut') {
        this.$router.push('/auth')
        this.signedIn = false
      }
    })
    Auth.currentAuthenticatedUser()
      .then(() => {
        this.signedIn = true
      })
      .catch(() => this.signedIn = false)
  }
}
</script>

<style>
.nav {
  display: flex;
}
.nav p {
  padding: 0px 30px 0px 0px;
  font-size: 18px;
  color: #000;
}
.nav p:hover {
  opacity: .7;
}
.nav p a {
  text-decoration: none;
}
.sign-out {
  width: 160px;
  margin: 0 auto;
}
</style>
```
#### Details of src/components/App.vue
1. We use the `amplify-sign-out` component to render a sign out button if the user is signed in.
2. We create a Boolean called `signedIn` & set it to false when the app loads
3. In the `beforeCreate` lifecycle method we listen for the `authState` event by using the `Hub` API. If we detect a sign in, we redirect them to view their profile & set `signedIn` to true. If we detect a sign out, we redirect them to the `/auth` route & set `signedIn` to false.
4. When the app loads, we also call `Auth.currentAuthenticatedUser` to check whether or not the user is signed in & set the `signedIn` variable appropriately.

Next, let's add the __Profile__ component.

This basic component will display the user's __username__ that we'll be retrieving using Amplify.

#### src/components/Profile.vue

```vue
// src/components/Profile.vue
<template>
  <h1>Welcome, {{user.username}}</h1>
</template>

<script>
import { Auth } from 'aws-amplify'

export default {
  name: 'Profile',
  data() {
    return {
      user: {}
    }
  },
  beforeCreate() {
    Auth.currentAuthenticatedUser()
      .then(user => {
        this.user = user
      })
      .catch(() => console.log('not signed in...'))
  }
}
</script>
```

#### Details of src/components/Profile.vue
The main thing to note about this component is that we're retrieving information about the user by calling the `Auth.currentAuthenticatedUser` method. This method will return a `user` object containing metadata about the logged in user or it will error out if the user is not signed in.

Now we can create the last two basic components.

#### src/components/Home.vue

```vue
// src/components/Home.vue
<template>
  <h1>Home</h1>
</template>

<script>
export default {
  name: 'home',
}
</script>
```

#### src/components/Protected.vue
```vue
// src/components/Protected.vue
<template>
  <h1>Hello from protected route!</h1>
</template>

<script>

export default {
  name: 'protected',
}
</script>
```

## Testing it out

Part 1 of our application is finished, so let's test it out:

```sh
~ npm run serve
```

When the app loads we should be able to only view the __Home__ route. If we try navigate to one of the protected routes, we should be redirected to the authentication screen.

![](https://thepracticaldev.s3.amazonaws.com/i/u5jwiokisa2hh811wnb1.jpg)

Once we're signed in we should be able to view the protected pages.

![](https://thepracticaldev.s3.amazonaws.com/i/m9vdqggm4plbyzaesicm.jpg)

You'll notice that the user is persisted. This is handled for you by the Amplify client library. In order to sign out, you must explicitly click the sign out button we've rendered or use the `Auth.signOut` method from the Auth category.

Now that we've gotten this up & running, what's next? Well, the `amplify-authenticator` component can be customized to a certain extent to control fields rendered as well as the styling (to learn how, check out the docs [here](https://aws-amplify.github.io/docs/js/vue#authenticator)) but what if we'd like to have an entirely customized authentication flow? Let's do this now.

# Part 2 - Building out a custom authentication flow.

Now that we've gotten authentication working, let's update what we have to be able to be customized. Right now all of our auth functionality is stored in __Auth.vue__. In this file we're using the `amplify-authenticator` component to scaffold out our entire authentication flow. Let's update our app to have custom authentication.

The first thing we'll need to do is create a couple of new files in our __components__ directory, one for signing users in & one for signing up new users.

```sh
touch src/components/SignIn.vue src/components/SignUp.vue
```

Next, let's update __Auth.vue__ to use the new files & add some new functionality. In this file we'll render the __SignUp__ & __SignIn__ components depending on some component state. We'll also render a link that allows us to toggle between sign up & sign in state:

#### src/components/Auth.vue
```vue
// src/components/Auth.vue
<template>
  <div class="auth">
    <sign-up :toggle='toggle' v-if="formState === 'signUp'"></sign-up>
    <sign-in v-if="formState === 'signIn'"></sign-in>
    <p v-on:click="toggle" class="toggle">{{ formState === 'signUp' ?
      'Already sign up? Sign In' : 'Need an account? Sign Up'
      }}</p>
  </div>
</template>

<script>
import SignUp from './SignUp'
import SignIn from './SignIn'

export default {
  name: 'app',
  data() {
    return {
      formState: 'signUp'
    }
  },
  methods: {
    toggle() {
      this.formState === 'signUp' ? this.formState = 'signIn' : this.formState = 'signUp'
    }
  },
  components: {
    SignUp,
    SignIn
  }
}
</script>

<style>
.auth {
  margin: 0 auto;
  width: 460px;
}
.toggle {
  cursor: pointer;
  font-size: 18px;
}
</style>
```

#### Details of src/components/Auth.vue
The main thing to take into consideration here is that we are importing our two new components & rendering either of them based on the value of the `formState` Boolean. Nothing really too interesting yet.

Next, let's create the sign up form.
#### src/components/SignUp.vue

```vue
// src/components/SignUp.vue
<template>
  <div>
    <h2>{{ formState === 'signUp' ? 'Sign Up' : 'Confirm Sign Up' }}</h2>
    <div class='formcontainer' v-if="formState === 'signUp'">
      <input placeholder="username" v-model='form.username' class='input' />
      <input placeholder="password" type='password' v-model='form.password' class='input' />
      <input placeholder="email" v-model='form.email' class='input' />
      <button v-on:click='signUp' class='button'>Sign Up</button>
    </div>
    <div class='formcontainer' v-if="formState === 'confirmSignUp'">
      <input placeholder="confirmation code" v-model='form.authCode' class='input' />
      <button v-on:click='confirmSignUp' class='button'>Confirm Sign Up</button>
    </div>
  </div>
</template>

<script>
import { Auth } from 'aws-amplify'

export default {
  name: 'home',
  props: ['toggle'],
  data() {
    return {
      formState: 'signUp',
      form: {
        username: '',
        password: '',
        email: ''
      }
    }
  },
  methods: {
    async signUp() {
      const { username, password, email } = this.form
      await Auth.signUp({
        username, password, attributes: { email }
      })
      this.formState = 'confirmSignUp'
    },
    async confirmSignUp() {
      const { username, authCode } = this.form
      await Auth.confirmSignUp(username, authCode)
      alert('successfully signed up! Sign in to view the app.')
      this.toggle()
    }
  }
}
</script>

<style>
.formcontainer {
  display: flex;
  flex-direction: column;
  width: 500px;
  margin: 0 auto;
}
.input {
  margin-bottom: 7px;
  height: 38px;
  border: none;
  outline: none;
  border-bottom: 2px solid #ddd;
  font-size: 20px;
}
.button {
  height: 45px;
  border: none;
  outline: none;
  background-color: #dddddd;
  margin-top: 8px;
  cursor: pointer;
  font-size: 18px;
}
.button:hover {
  opacity: .7
}
</style>
```

#### Details of src/components/SignUp.vue
1. We have two separate forms - one for signing up & one for confirming sign up (MFA confirmation)
2. We have a `formState` Boolean that we will use to toggle between the two forms.
3. We have a form property on our data object that will keep up with the `username`, `password`, & `email` when a new user signs up.
4. The `signUp` method calls the Amplify `Auth.signUp` method, passing in the form properties.
5. The `confirmSignUp` method calls the Amplify `Auth.confirmSignUp` method, passing in the `username` & `authCode`. Once the user has successfully signed up we toggle the view to show the __SignUp__ component.

Finally, let's take a look at the __SignIn__ component. This component is very similar to __SignUp__ in the sense that it has a form & calls a method on the Amplify `Auth` class.

#### src/components/SignIn.vue

```vue
// src/components/SignIn.vue
<template>
  <div>
    <h2>Sign In</h2>
    <div class='formcontainer'>
      <input placeholder="username" v-model='form.username' class='input' />
      <input placeholder="password" type='password' v-model='form.password' class='input' />
      <button v-on:click='signIn' class='button'>Sign In</button>
    </div>
  </div>
</template>

<script>
import { Auth } from 'aws-amplify'
export default {
  name: 'home',
  data() {
    return {
      form: {
        username: '',
        password: ''
      }
    }
  },
  methods: {
    async signIn() {
      const { username, password } = this.form
      await Auth.signIn(username, password)
    }
  }
}
</script>
```

#### Details of src/components/SignIn.vue
1. We have a form that allows the user to sign in
2. We sign the user in calling the Amplify `Auth.signIn` method.
3. In __App.vue__, we are listening for the `signIn` event, and the user will be routed to the __Profile__ route after successfully signing in.

## Testing it out

Part 2 of our application is finished so let's try it out!

```sh
~ npm run serve
```

![](https://thepracticaldev.s3.amazonaws.com/i/tt2ltwhac7c8ed1snyjc.jpg)


You should now see your app load with the new sign up / sign in forms we created.

## Next Steps

The [Amplify Auth class](https://aws-amplify.github.io/amplify-js/api/classes/authclass.html) has over 30 different methods on it, including things like `forgotPassword`, `setPreferredMFA`, & `signOut`. Using these methods you can continue tailoring your authentication flow to be more robust.

The styling we used was minimal in order to keep this already long blog post from being _too_ verbose, but since you can have full control over the authentication flow you can style it as you'd like.

Amplify authentication also supports federated sign in from providers like Facebook, Twitter, Google and Amazon. To learn more check out the documentation [here](https://aws-amplify.github.io/docs/js/authentication).

# Conclusion

To view the final repo & source code, click [here](https://github.com/dabit3/production-ready-vue-authentication).

To learn more about Amplify check out the documentation [here](https://aws-amplify.github.io/docs/js/start?platform=purejs).

Also check out the [Awesome AWS Amplify Repo](https://github.com/dabit3/awesome-aws-amplify) for more tutorials & starter projects.

> My Name is [Nader Dabit](https://twitter.com/dabit3). I am a Developer Advocate at Amazon Web Services working with projects like AWS AppSync and AWS Amplify. I also specialize in cross-platform application development.