Sometimes the best examples towards learning a new framework is through a simple user login sample. Login involves many concepts, including forms, data binding, routing, and potentially HTTP to a remote service, all of which are core concepts in any web application.
We’re going to see how to implement a simple login form in a web application that uses the Vue.js JavaScript framework.
The animated image below is an example of what we’re going to try to accomplish.
We’re going to have a login screen with some hard coded credential data. When the correct information is entered, the application will navigate to a potentially secure page. Trying to access the page manually will result in navigation back to the login page because the application hadn’t been authenticated. The authorization status and logout is controlled by a parent component.
To make our lives easier, we’re going to use the Vue CLI to create a new project. It is not absolutely necessary to use the Vue CLI, but I totally recommend it.
With the CLI installed and configured, execute the following command:
vue create simple-login-project
When prompted, choose to specify the features that you’d like included. For this application, the only required feature will be the vue-router. It doesn’t matter how you choose to store the configuration data.
At this point we can start development.
For this project we’re going to focus on two components which will act as our routes as well as a parent component that the routes will pass through.
In your project, create a src/views/secure.vue file with the following code:
<template>
<div id="secure">
<h1>Secure Area</h1>
<p>
This is a secure area
</p>
</div>
</template>
<script>
export default {
name: 'Secure',
data() {
return {};
}
}
</script>
<style scoped>
#secure {
background-color: #FFFFFF;
border: 1px solid #CCCCCC;
padding: 20px;
margin-top: 10px;
}
</style>
The above code represents our protected application area. For example, it should only be accessed if the user has logged in. Because this is our end goal, we don’t really need to add any logic to the page. Getting to the page is enough for us.
Now create a src/views/login.vue file with the following code:
<template>
<div id="login">
<h1>Login</h1>
<input type="text" name="username" v-model="input.username" placeholder="Username" />
<input type="password" name="password" v-model="input.password" placeholder="Password" />
<button type="button" v-on:click="login()">Login</button>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
input: {
username: "",
password: ""
}
}
},
methods: {
login() {
if(this.input.username != "" && this.input.password != "") {
if(this.input.username == this.$parent.mockAccount.username && this.input.password == this.$parent.mockAccount.password) {
this.$emit("authenticated", true);
this.$router.replace({ name: "secure" });
} else {
console.log("The username and / or password is incorrect");
}
} else {
console.log("A username and password must be present");
}
}
}
}
</script>
<style scoped>
#login {
width: 500px;
border: 1px solid #CCCCCC;
background-color: #FFFFFF;
margin: auto;
margin-top: 200px;
padding: 20px;
}
</style>
More is happening in our login.vue file than the previous, so let’s break it down, starting with the <script>
block.
We know this page will have a login form so we need to initialize the variables that will bind it:
data() {
return {
input: {
username: "",
password: ""
}
}
},
When we are ready to sign in, the login
method will be called:
login() {
if(this.input.username != "" && this.input.password != "") {
if(this.input.username == this.$parent.mockAccount.username && this.input.password == this.$parent.mockAccount.password) {
this.$emit("authenticated", true);
this.$router.replace({ name: "secure" });
} else {
console.log("The username and / or password is incorrect");
}
} else {
console.log("A username and password must be present");
}
}
Both the username and password need to be present to move on. Since we aren’t actually passing the username and password in an HTTP call, we’re going to be comparing against some mock data. The mock data will exist in the parent component which we’ll see next.
If you’d like to see how to make HTTP requests from your Vue.js application, check out my previous tutorial titled, Consume Remote API Data via HTTP in a Vue.js Web Application.
If the user input matches the mock data, we need to emit that we’re authenticated and navigate to the protected component. Don’t worry, we’re going to define our routes later.
With the logic out of the way, we have the HTML template that powers the UI:
<template>
<div id="login">
<h1>Login</h1>
<input type="text" name="username" v-model="input.username" placeholder="Username" />
<input type="password" name="password" v-model="input.password" placeholder="Password" />
<button type="button" v-on:click="login()">Login</button>
</div>
</template>
Using the v-model
attribute, we can bind our variables in a two-way fashion. We can also bind our login
function via the v-on:click
attribute. Nothing fancy is happening in the UI. It more or less strings things together.
This brings us to our parent component in which our two previous components pass through.
Open the project’s src/App.vue file and include the following code:
<template>
<div id="app">
<div id="nav">
<router-link v-if="authenticated" to="/login" v-on:click.native="logout()" replace>Logout</router-link>
</div>
<router-view @authenticated="setAuthenticated" />
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
authenticated: false,
mockAccount: {
username: "nraboy",
password: "password"
}
}
},
mounted() {
if(!this.authenticated) {
this.$router.replace({ name: "login" });
}
},
methods: {
setAuthenticated(status) {
this.authenticated = status;
},
logout() {
this.authenticated = false;
}
}
}
</script>
<style>
body {
background-color: #F0F0F0;
}
h1 {
padding: 0;
margin-top: 0;
}
#app {
width: 1024px;
margin: auto;
}
</style>
Again, we’re going to start by analyzing the <script>
block which contains our application logic.
The first thing that we do is initialize the variables that will be used throughout this component:
data() {
return {
authenticated: false,
mockAccount: {
username: "nraboy",
password: "password"
}
}
},
You’ll notice that we’re initializing our mock data as well as an authentication variable. Remember, we emitted an authentication status from the login.vue file.
When the application mounts, we can do some checks:
mounted() {
if(!this.authenticated) {
this.$router.replace({ name: "login" });
}
},
If we’re not currently authenticated, we should navigate to the login component. This will prevent anyone from trying to directly navigate to a protected page while not authenticated.
Next we have two methods:
setAuthenticated(status) {
this.authenticated = status;
},
logout() {
this.authenticated = false;
}
Remember when we emitted the authentication status? We’re going to update it via the setAuthenticated
method, but not yet. When we want to sign out, we have the logout
method which will set our authentication status to false.
Now let’s check out the UI for this particular component:
<template>
<div id="app">
<div id="nav">
<router-link v-if="authenticated" to="/login" v-on:click.native="logout()" replace>Logout</router-link>
</div>
<router-view @authenticated="setAuthenticated" />
</div>
</template>
We have a link in our parent component that will only show if we are not authenticated. This link will be our means to sign out of the application. Notice the <router-view>
tag and the @authenticated
attribute. This is a listener. In the login.vue file we are emitting data on an event called authenticated, hence the @authenticated
that we use. If we find some data, we’ll call the callback method which will change the authentication status.
More information on accessing or changing parent data from a child component can be found in my previous article titled, Access and Change Parent Variables from a Child Component with Vue.js.
The last thing to accomplish is our routing definitions.
Open the project’s src/router/index.js file and include the following:
import Vue from 'vue'
import VueRouter from 'vue-router'
import LoginComponent from "../views/login.vue"
import SecureComponent from "../views/secure.vue"
Vue.use(VueRouter)
export default new VueRouter({
routes: [
{
path: '/',
redirect: {
name: "login"
}
},
{
path: "/login",
name: "login",
component: LoginComponent
},
{
path: "/secure",
name: "secure",
component: SecureComponent
}
]
})
We’ve imported each of our two components and set the root path to redirect to our login screen. We’ve linked our components and given them a path as well as a name.
More information on routing in a Vue.js application can be found in a previous article I wrote titled, Use a Router To Navigate Between Pages in a Vue.js Application.
You just saw how to create simple login logic for a Vue.js web application. Using the vue-router, simple form binding, and interaction between components, we can make all this happen.
You might be thinking that this example is incomplete because we’re not validating against real data from a database over HTTP. Technically the only thing you’d change is adding an HTTP call. Everything would pretty much remain the same.