Kita akan coba membuat autentikasi login dan registrasi user dengan vue.js dan laravel. Vue.js akan kita gunakan sebagai frontend dan laravel sebagai backend.
Backend
Backend nya sendiri saya tidak menulis ulang untuk langkah pembuatannya karena sudah dibuat dan cara pembuatannya sudah saya publish di artikel sebelumnya.
Pembuatan backend: 👇
Membuat Autentikasi JWT (JSON Web Token) dengan Laravel 7
Silahkan buka link diatas untuk mengerjakan bagian backend jika belum membuatnya.
Frontend
Kita akan gunakan vue.js sebagai frontend untuk autentikasi ini. Penginstalan kita gunakan vue CLI (Command Line Interface) dengan scaffolding yang disediakan akan mempermudah kita dalam membangun aplikasi. Kita juga akan install vue-router dan vuex.
Vuex merupakan state management dari vue.js yang akan kita gunakan untuk pengelolaan data user.
Instalasi Vue CLI
Kita langsung ke proses instalasi. Pertama kita install vue CLI dengan cara global ke komputer kita, silahkan jalankan perintah berikut:
npm install -g @vue/cli @vue/cli-service-global # atau yarn global add @vue/cli @vue/cli-service-global
Silahkan install dengan package manager yang ingin digunakan, npm atau yarn.
Membuat Project Baru
Setelah proses instalasi Vue CLI selesai, kita gunakan vue create untuk membuat project baru. Saya membuat project baru dengan nama frontend:
vue create frontend
Lalu kita akan diminta untuk memilih preset seperti gambar dibawah ini:
Jika dilihat pada gambar diatas terdapat 2 versi vuejs, kita akan gunakan vuejs versi 2 untuk tutorial ini.
Kita akan memilih fitur secara manual. Silahkan pilih Manually select features dan kita pilih fitur yang ingin ditambahkan. Seperti gambar berikut:
Silahkan pilih beberapa fitur dengan menekan spasi untuk menandai.
Seperti pada gambar diatas, silahkan pilih Choose Vue Version, Babel, Router, Vuex dan Linter / Formatter. Setelah diberi tanda, kemudian tekan enter.
Kita akan kembali diberi beberapa pilihan, silahkan selesaikan seperti gambar dibawah ini:
Silahkan samakan pilihan seperti pada gambar diatas, dan untuk Pick a linter / formatter config, pilih saja ESLint with error prevention only (Basic).
Sampai dipertanyaan 'Save this as a preset for future projects?', silahkan pilih 'y' untuk membuat preset yang dapat mempermudah kita ketika membuat project baru dengan fitur yang sama seperti yang sudah kita pilih diatas. Jika tidak, silahkan pilh 'N' untuk langsung ke proses instalasi.
Setelah proses instalasi selesai, jalankan perintah serve untuk menjalankan aplikasi mode development.
cd frontend npm run serve # atau yarn serve
Vue Devtools
Untuk membantu dan mempermudah kita dalam development, kita perlu menambahkan / install add-ons / extensions vue devtools pada browser yang kita gunakan.
Silahkan gunakan link dibawah untuk menambahkan pada browser jika belum menginstallnya.
Mulai Pengerjaan
Mari kita langsung ke proses pengerjaan. Kita mulai mengerjakan aplikasi yang akan kita buat dengan menambahkan library / depedensi yang kita perlukan.
npm install axios bootstrap jquery popper.js # atau yarn add axios bootstrap jquery popper.js
Kita akan gunakan Axios untuk http request ke backend API dan Bootstrap untuk desain halaman.
Desain Halaman
Setelah bootstrap dan axios ditambahkan, kita akan buat terlebih dahulu halaman untuk login dan registrasi.
Pertama kita import terlebih modul bootstrap pada entry point aplikasi di src > main.js.
//main.js import 'bootstrap' import 'bootstrap/dist/css/bootstrap.min.css'
Kemudian pada direktori views buat 2 file baru dengan nama Login.vue dan Register.vue, lalu copas html berikut:
//Login.vue <template> <div class="row justify-content-md-center"> <div class="col-md-6"> <div class="card"> <div class="card-header">Login</div> <div class="card-body"> <form> <div class="form-group"> <label for="email">Email address</label> <input type="email" class="form-control" placeholder="Email.."> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" class="form-control" placeholder="Password.."> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> </div> </div> </div> </div> </template>
//Register.vue <template> <div class="row justify-content-md-center"> <div class="col-md-6"> <div class="card"> <div class="card-header"> Register </div> <div class="card-body"> <form> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" placeholder="Name.."> </div> <div class="form-group"> <label for="email">Email address</label> <input type="email" class="form-control" placeholder="Email.."> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" class="form-control" placeholder="Password.."> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> </div> </div> </div> </div> </template>
Kemudian tambahkan route untuk login dan register pada router > index.js.
... { path: '/login', name: 'Login', component: () => import('../views/Login.vue') }, { path: '/register', name: 'Register', component: () => import('../views/Register.vue') } ...
Setelah itu kita buat sebuah komponen untuk membuat navbar. Silahkan buat file baru pada direktori components dengan nama Navbar.vue.
//Navbar.vue <template> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <router-link class="navbar-brand" to="/">Navbar</router-link> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <router-link class="nav-link" to="/">Home</router-link> </li> <li class="nav-item"> <router-link class="nav-link" to="/about">About</router-link> </li> </ul> <ul class="navbar-nav"> <li class="nav-item"> <router-link class="nav-link" to="/login">Login</router-link> </li> <li class="nav-item"> <router-link class="nav-link" to="/register">Register</router-link> </li> </ul> </div> </nav> </template>
Kemudian kita import komponen Navbar pada file App.vue.
//App.vue <template> <div id="app"> <Navbar/> <div class="container pt-4"> <router-view/> </div> </div> </template> <script> import Navbar from '@/components/Navbar.vue' export default { components: { Navbar } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; } #app .navbar-nav a{ color: #2c3e50; cursor: pointer; } #app .navbar-nav a.router-link-exact-active { color: #42b983; } </style>
Hasilnya seperti gambar dibawah ini:
Koneksi ke Backend
Kita lanjutkan dengan menghubungkan frotend dengan API backend. Kita akan gunakan axios dan membuat request endpoint secara global dengan mengatur default baseURL axios.
Silahkan buka main.js lalu import axios dan atur baseURL nya.
import axios from 'axios' axios.defaults.baseURL = 'http://localhost:8000/api/'
Vuex Store
Selanjutnya kita berkerja dengan Vuex. Kita akan membuat request api, menyimpan data pada store, autoriasi http header, dll.
Mengenai vuex sendiri saya tidak bahas secara detail disini, kiranya sudah memahami konsep dari vuex.
Membuat Modul
Langsung saja, pertama kita buat modul, silahkan buat file baru pada direktori store dengan nama auth.js.
//auth.js export default{ state: { }, mutations: { }, getters:{ }, actions: { } }
Kemudian import pada store > index.js.
import Vue from 'vue' import Vuex from 'vuex' import auth from './auth' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { auth } })
Setelah itu buat folder baru pada direktori root dengan nama utils dan tambahkan file baru dengan nama auth.js, lalu buat seperti berikut:
//utils/auth.js import axios from 'axios' export function setHeaderToken (token) { axios.defaults.headers.common['Authorization'] = 'Bearer ' + token } export function removeHeaderToken () { delete axios.defaults.headers.common['Authorization'] }
Login User
Kita lanjutkan mengelola store dengan mengerjakan proses login user. Silahkan buka kembali store > auth.js dan buat seperti dibawah ini:
state: { user: null, isLoggedIn: false, },
Pada store kita memiliki state yang merupakan objek, kita tambahkan properti user dan isLoggedIn.
mutations: { set_user (state, data) { state.user = data state.isLoggedIn = true }, reset_user (state) { state.user = null state.isLoggedIn = false } },
Untuk menyimpan, mengubah atau memanipulasi state, kita gunakan mutasi. Kita buat dua method yaitu set_user dan reset_user.
Pada set_user jika permintaan diterima / tidak ada kesalahan akan mengirim data user pada state yang merupakan objek, dan merubah status isLoggedIn menjadi true.
getters:{ isLoggedIn (state){ return state.isLoggedIn }, user (state) { return state.user } },
Kita buat getters untuk mengakses properti state yang akan kita gunakan untuk melakukan kueri pada komponen yang kita buat.
actions: { login({ dispatch, commit }, data) { return new Promise((resolve, reject) => { axios.post('login', data) .then(response => { const token = response.data.token localStorage.setItem('token', token) setHeaderToken(token) dispatch('get_user') resolve(response) }) .catch(err => { commit('reset_user') localStorage.removeItem('token') reject(err) }) }) }, async get_user({commit}){ if(!localStorage.getItem('token')){ return } try{ let response = await axios.get('user') commit('set_user', response.data.data) } catch (error){ commit('reset_user') removeHeaderToken() localStorage.removeItem('token') return error } } }
Pada actions, kita buat promise request axios method post ke endpoint login pada method login. Jika request suskses, kita simpan response data token pada local storage, header, dan dispatch method get_user.
Pada method get_user kita request ke endpoint user dan mutasi data objek user.
Update Form Login
Selanjutnya kita akan update form login. Silahkan buka Login.vue dan buat seperti dibawah ini:
//Login.vue <template> <div class="row justify-content-md-center"> <div class="col-md-6"> <div class="card"> <div class="card-header">Login</div> <div class="card-body"> <div class="alert alert-danger" v-for="(error, index) in errors" :key="index"> {{ error[0] }} </div> <form @submit.prevent="userLogin"> <div class="form-group"> <label for="email">Email address</label> <input v-model="form.email" type="email" class="form-control" placeholder="Email.."> </div> <div class="form-group"> <label for="password">Password</label> <input v-model='form.password' type="password" class="form-control" placeholder="Password.."> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> </div> </div> </div> </div> </template> <script> export default { data(){ return { form: { email: '', password: '', }, errors: null } }, methods: { userLogin () { this.$store.dispatch('login', this.form) .then(response => { console.log(response) this.$router.push({name: 'Home'}) }).catch(error => { this.errors = error.response.data.errors }) } } } </script>
Kita membuat form data binding, validasi, dan pada method userLogin kita dispatch ke vuex store. Ketika login berhasil maka akan return ke halaman / route name 'Home', namun jika gagal akan menampilkan array respon data error.
Sampai disini kita bisa coba melakukan login. Silahkan jalankan kedua server, npm run serve untuk frontend dan php artisan serve untuk backend.
Silahkan coba login dengan akun yang sudah dibuat sebelumnya.
Pada gambar diatas saya berhasil login dan store berisi data objek user, namun ketika melakukan refresh halaman yang terjadi adalah state menjadi kosong / null, akan tetapi pada local storage (Ctrl+Shift+i > Aplications > Local Storage) masih terdapat token dari user.
Dalam hal ini kita perlu melakukan pre-fetch data user. Kita request data user sebelum komponen selesai dirender, dan tentu harus melewati autorisasi.
Token yang valid atau belum expired pada localstorage kita gunakan untuk autorisasi user.
Silahkan buka main.js dan buat seperti dibawah ini.
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import axios from 'axios' import { setHeaderToken } from '../utils/auth' import 'bootstrap' import 'bootstrap/dist/css/bootstrap.min.css' axios.defaults.baseURL = 'http://localhost:8000/api/' Vue.config.productionTip = false const token = localStorage.getItem('token'); if (token) { setHeaderToken(token) } store.dispatch('get_user', token) .then(() => { new Vue({ router, store, render: h => h(App) }).$mount('#app') }).catch((error) => { console.error(error); })
Silahkan coba kembali untuk merefresh halaman dan lihat vuex store pada console. Mutasi akan dilakukan dan state berisikan data objek.
Halaman Profil
Selanjutnya kita buat halaman profil user. Silahkan buat file baru pada direktori views dengan nama profile.vue lalu copas html dibawah ini.
//Profile.vue <template> <div class="row"> <div class="col-md-4"> <div class="card" style="width: 18rem;"> <img class="card-img-top" src="../assets/profile.jpg" alt="Card image cap"> <div class="card-body"> <template v-if="isLoggedIn"> <h5 class="card-title">{{user.name}}</h5> <p class="card-text">{{user.email}}</p> </template> <a href="#" class="btn btn-primary">Follow</a> </div> </div> </div> <div class="col-md-8"> <div class="jumbotron"> <h1 class="display-4">Hello, world!</h1> <p class="lead">This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p> <hr class="my-4"> <p>It uses utility classes for typography and spacing to space content out within the larger container.</p> <p class="lead"> <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a> </p> </div> </div> </div> </template> <script> import { mapGetters } from 'vuex' export default { computed: { ...mapGetters({ isLoggedIn: 'isLoggedIn', user: 'user', }) }, } </script>
Kita mapping 2 getters menggunakan helper mapGetters pada properti computed.
Update Navbar
Pada komponen navbar, silahkan buat seperti berikut:
//Navbar.vue <template> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <router-link class="navbar-brand" to="/">Navbar</router-link> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <router-link class="nav-link" to="/">Home</router-link> </li> <li class="nav-item"> <router-link class="nav-link" to="/about">About</router-link> </li> </ul> <ul class="navbar-nav"> <template v-if="isLoggedIn"> <li class="nav-item"> <router-link class="nav-link" to="/profile">{{user.name}}</router-link> </li> </template> <template v-else> <li class="nav-item"> <router-link class="nav-link" to="/login">Login</router-link> </li> <li class="nav-item"> <router-link class="nav-link" to="/register">Register</router-link> </li> </template> </ul> </div> </nav> </template> <script> import {mapGetters} from 'vuex' export default { computed: { ...mapGetters({ isLoggedIn: 'isLoggedIn', user: 'user', }) }, } </script>
Pada navbar kita membuat statement kondisional dengan directive v-if v-else. Kita tambahkan menu profile dengan menampilkan nama dari user dan akan muncul jika dalam keadaan sudah login.
Proteksi Route
Selanjutnya kita buat guards untuk router. Silahkan buka router > index.js dan buat seperti dibawah ini.
import Vue from 'vue' import VueRouter from 'vue-router' import Home from '../views/Home.vue' import store from '../store/index.js' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: () => import('../views/About.vue') }, { path: '/login', name: 'Login', component: () => import('../views/Login.vue'), meta: { guest: true } }, { path: '/register', name: 'Register', component: () => import('../views/Register.vue') }, { path: '/profile', name: 'Profile', component: () => import('../views/Profile.vue'), meta: { auth: true } } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.auth)) { if (store.getters.isLoggedIn && store.getters.user) { next() return } next('/login') } if (to.matched.some(record => record.meta.guest)) { if (!store.getters.isLoggedIn) { next() return } next('/profile') } next() }) export default router
Kita import modul store untuk akses properti dan kita buat meta untuk mengecek status login user.
Logout
Kita lanjutnya untuk membuat fungsi logout. Silahkan tambahkan method logout dibawah pada store > auth.js.
... logout({ commit }) { return new Promise((resolve) => { commit('reset_user') localStorage.removeItem('token') removeHeaderToken() resolve() }) } ...
Lalu buat method pada komponen navbar.vue dan tambahkan link pada menu dengan event click.
<template v-if="isLoggedIn"> ... <li class="nav-item"> <a class="nav-link" @click="logout">Logout</a> </li> </template>
... methods: { logout: function() { this.$store.dispatch("logout").then(() => { this.$router.push("/"); }); } ...
Sampai disini silahkan mencobanya.
Registrasi User
Terakhir yang akan kita kerjakan adalah proses registrasi user. Silahkan tambahkan method register dibawah pada store > auth.js.
... register({ commit }, data) { return new Promise((resolve, reject) => { axios.post('register', data) .then(resp => { resolve(resp) }) .catch(err => { commit('reset_user') reject(err) }) }) }, ...
Kemudian kita buat input binding, error handling dan method pada views > Register.vue.
//Register.vue <template> <div class="row justify-content-md-center"> <div class="col-md-6"> <div class="card"> <div class="card-header"> Register </div> <div class="card-body"> <div class="alert alert-danger" v-for="(error, index) in errors" :key="index"> {{ error[0] }} </div> <form @submit.prevent="register"> <div class="form-group"> <label for="name">Name</label> <input v-model="name" type="text" class="form-control" placeholder="Name.."> </div> <div class="form-group"> <label for="email">Email address</label> <input v-model="email" type="email" class="form-control" placeholder="Email.."> </div> <div class="form-group"> <label for="password">Password</label> <input v-model="password" type="password" class="form-control" placeholder="Password.."> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> </div> </div> </div> </div> </template> <script> export default { data() { return { name: "", email: "", password: "", errors: null }; }, methods: { register: function () { let data = { name: this.name, email: this.email, password: this.password, }; this.$store .dispatch("register", data) .then(response => { console.log(response) this.$router.push({ name: 'Login' }) }).catch(error => { this.errors = error.response.data.errors }) } } }; </script>
Simpan dan silahkan coba melakukan registrasi / pendaftaran user baru.
Jika berhasil melakukan registrasi akan redirect ke halaman login. Silahkan coba login dengan akun baru yang telah dibuat.
Sampai disini langkah-langkah pembuatan autentikasi login dan registrasi user dengan laravel yang kita gunakan sebagai backend dan vue.js sebagai frontend telah selesai, silahkan dibuat dan dikembangkan.