Berbicara tentang sistem informasi, tentunya tidak akan lepas dari yang namanya autentikasi atau login. Sebab, melalui mekanisme inilah kita bisa membatasi siapa saja yang dapat mengakses aplikasi menggunakan username / email dan password yang terdaftar di database. Pada pemrograman PHP monolitik, implementasi autentikasi / login biasanya menggunakan session yang disimpan di sisi server, namun kita tidak perlu menerapkan metode tersebut karena aplikasi yang kita bangun menggunakan konsep stateless – agar lebih jelas, Anda dapat membaca Lumen Laravel untuk Pembuatan REST API di Sisi Backend.

Sekilas JWT (JSON Web Token)

JSON Web Token atau yang lebih dikenal dengan JWT pada dasarnya adalah sebuah token atau string yang sangat panjang dengan nilai yang “sangat” acak, contoh : u6VtNtNXiPLNK9hXjHHnDgjgIsZHa29S5cOSw9O76913961pI3hiPsR7gEV8XmW4. Cara kerja JWT mirip seperti password. Ketika user berhasil melakukan autentikasi, server akan men-generate token yang kemudian disimpan ke local storage / cookies browser di sisi user. Tanpa token yang valid, user tidak dapat mengakses halaman-halaman tertentu pada aplikasi.

Catatan : JWT dapat diimplementasikan di bahasa pemrograman apa saja yang mendukung REST API.

Pembuatan Token di Sisi Backend / Lumen

Project Lumen (beserta database-nya) menggunakan instalasi dari contoh materi sebelumnya Membuat Single Page Application dengan Vue.js Lumen – source code project Lumen untuk latihan ini tersedia di Google Drive.

Langkah 1 – Siapkan Tabel Database Users

Dari root project Lumen di /Users/username_macos_anda/Sites (macOS) atau /home/linuxbrew/.linuxbrew/var/www (Linux), jalankan perintah :

php artisan make:migration create_users_table

Rubah isi file database migration untuk tabel users menjadi :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration {

    public function up() {

        Schema::create('users', function (Blueprint $table) {

            $table->increments('id');
            $table->string('nama');
            $table->string('username')->unique();
            $table->string('password')->nullable();
            $table->timestamps();
            
        });

    }

    public function down() {

        Schema::dropIfExists('users');

    }

}

Setelah perubahan pada file migration di atas tersimpan, lanjutkan dengan perintah :

php artisan migrate

Langkah 2 – Buat Eloquent Model Untuk Tabel Database Users

Perbarui file User.php yang ada di dalam folder app/, copy paste kode berikut :

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;

class User extends Model implements AuthenticatableContract, AuthorizableContract {
    
    use Authenticatable, Authorizable;

    protected $table = 'users';

    protected $guarded = ['id'];

    protected $hidden = ['password', 'created_at', 'updated_at'];

}

Langkah 3 – Siapkan Router

Buang router yang tidak dibutuhkan pada file web.php, ganti dengan dua router baru get('/random-key') dan post('/auth/login').

<?php

use Illuminate\Support\Str;

/*
|--------------------------------------------------------------------------
| Generate Random Key
|--------------------------------------------------------------------------
|
*/

$router->get('/random-key', function () {

    return Str::random(64);

});

/*
|--------------------------------------------------------------------------
| Autentikasi
|--------------------------------------------------------------------------
|
*/

$router->post('/auth/login', 'AuthController@authenticate');

Langkah 4 – Copy Paste Random Key ke File .env

Buka URL http://localhost/random-key di browser, copy string acak yang tertampil.

autentikasi vue.js dengan jwt lumen

Paste string acak di atas ke baris APP_KEY pada file .env.

autentikasi vue.js dengan jwt lumen

Package Firebase/Php-JWT digunakan untuk men-generate JSON Web Token. Untuk menginstallnya, ketik perintah di bawah pada root project Lumen :

composer require firebase/php-jwt

Langkah 5 – Buat Controller Autentikasi

Buat file baru dengan nama AuthController.php, lalu copy paste kode berikut :

<?php

namespace App\Http\Controllers;

use DB;

use App\User;

use Firebase\JWT\JWT;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

use Laravel\Lumen\Routing\Controller as BaseController;

class AuthController extends BaseController {
    
    private function jwt(User $user) {
        
        $payload = [
            'iss' => "lumen-jwt",
            'sub' => $user->id,
            'iat' => time(),
            'exp' => time() + 60*60 // token kadaluwarsa setelah 3600 detik
        ];
        
        return JWT::encode($payload, env('APP_KEY'));
    
    }
    
    public function authenticate(Request $request) {
        
        $username = $request->input('username');
    	$password = $request->input('password');
        
        $selectedUser = User::where('username', '=', $username)->first();
        
        if ($selectedUser && Hash::check($password, $selectedUser->password)) {
            
            $id = $selectedUser->id;
            $nama = $selectedUser->nama;
            
            $username = $selectedUser->username;
            
            $token = $this->jwt($selectedUser);
            
            $data = ['token' => $token];
            
            return response()->json($data, 200);
            
        } else {
            
            return response('', 422);
        
        }

    }

}

Penjelasan kode Controller di atas :

  1. Ketika request autentikasi diterima, router post('/auth/login) segera memanggil method authenticate.
  2. Perintah eloquent User::where('username', '=', $username)->first() mengecek apakah username ada di database.
  3. Jika username ditemukan if ($selectedUser dan hasil Hash password yang dimasukkan sama dengan password yang ada di database && Hash::check($password, $selectedUser->password)), lanjutkan ke proses berikutnya. Akan tetapi, jika kedua kondisi tidak terpenuhi, maka Lumen merespon request dengan HTTP error 422 return response('', 422).
  4. Controller memanggil private method jwt untuk men-generate token yang diberikan ke user menggunakan array payload dan string acak yang ada di env('APP_KEY') melalui baris return JWT::encode($payload, env('APP_KEY')).
  5. Terakhir, Lumen akan menanggapi request autentikasi dengan mengembalikan array data berisi ['token' => $token] dalam bentuk JSON return response()->json($data, 200).

Langkah 6 – Insert Data Dummy ke Tabel Users

Agar autentikasi dapat dilakukan, kita membutuhkan minimal satu row data user sebagai data dummy, dapat disisipkan melalui GUI DBMS atau dengan mengetikkan query SQL :

INSERT INTO users (nama, username, password) 
VALUES ('Varian Hermianto', 'variancode', '$2b$10$XxnpDTnpQ7/5v4oH.yik6uT6G7D7d7wwiEG3nrM6caShepoITg12a');

Nilai string $2b$10$XxnpDTnpQ7/5v4oH.yik6uT6G7D7d7wwiEG3nrM6caShepoITg12a untuk password di atas saya peroleh dari website yang menyediakan layanan BCrypt Hash Generator.

Request Token di Sisi Frontend / Vue.js

Project Vue dapat menggunakan hasil instalasi Vue-CLI dari contoh materi sebelumnya Membuat Single Page Application dengan Vue.js Lumen – source code project Vue untuk latihan ini tersedia di Google Drive.

Langkah 1 – Siapkan Router

Tambahkan router baru path: "/backend" ke file index.js.

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "@/views/Home.vue";
import Backend from "@/views/Backend.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  },
  {
    path: "/backend",
    name: "Backend",
    component: Backend
  }
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

export default router;

Langkah 2 – Modifikasi File Home.vue

Copy paste kode berikut untuk menggantikan isi file Home.vue sebelumnya.

<template>
  <div class="home">
    <div class="jumbotron text-center">
      <h2>Autentikasi Vue.js dengan JWT Lumen</h2>
      <p>Anda Berada di {{ currentRouteName }}</p>
    </div>
    <div class="container">
      <div class="row">
        <div class="col-md-4 offset-md-4">
          <div id="nav">
            <router-link to="/">Home</router-link> |
            <router-link to="/backend">Backend</router-link>
          </div>
          <div class="card">
            <div class="card-body">
              <h4 class="card-title text-center">Sign in</h4>
              <hr />
              <form @submit.prevent="onSubmit()">
                <div class="form-group">
                  <label for="username">Username</label>
                  <input type="text" class="form-control" v-model="username" />
                </div>
                <div class="form-group">
                  <label for="password">Password</label>
                  <input type="text" class="form-control" v-model="password" />
                </div>
                <div class="row">
                  <div class="col">
                    <div class="float-right">
                      <button type="submit" class="btn btn-primary">
                        Login
                      </button>
                    </div>
                  </div>
                </div>
              </form>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "Home",
  components: {},
  data() {
    return {
      username: "",
      password: "",
      currentRouteName: ""
    };
  },
  created() {
    this.currentRouteName = this.$route.name;
  },
  methods: {
    onSubmit() {
      if (this.username.trim() && this.password.trim()) {
        let formData = new FormData();
        formData.append("username", this.username.trim());
        formData.append("password", this.password);

        const options = {
          url: "http://localhost/auth/login",
          method: "post",
          headers: {
            "Content-Type": "multipart/form-data"
          },
          data: formData
        };

        axios(options)
          .then(response => {
            const token = response.data.token;

            if (token) {
              this.$router.push({
                name: "Backend",
                params: {
                  token: token
                }
              });
            }
          })
          .catch(e => {
            alert(e + "\n" + "Username / password yang dimasukkan salah.");
          });
      }
    }
  }
};
</script>

Tahapan membaca alur kode file Home.vue di atas :

  1. Masuk ke lifecycle created(), Vue mengeset nilai currentRouteName dengan nama route yang sedang aktif pada baris kode this.currentRouteName = this.$route.name.
  2. Method onSubmit dipanggil ketika button Login di-klik, kemudian Axios mengirim data ke URL http://localhost/auth/login menggunakan HTTP Request POST. Sebelum dikirim, property username dan password dibungkus / append ke dalam objek FormData. Option headers HTTP Request POST diisi dengan nilai "Content-Type": "multipart/form-data"
  3. Jika login tidak berhasil dilakukan karena username / password salah, Axios akan masuk ke blok .catch(errors), pesan error ditampilkan melalui alert(e + "\n" + "Username / password yang dimasukkan salah.").
  4. Nilai currentRouteName ditampilkan pada elemen <p>Anda Berada di {{ currentRouteName }}</p> saat halaman di-render.

Langkah 3 – Buat File Backend.vue

Buat file baru bernama Backend.vue lalu copy paste kode di bawah.

<template>
  <div class="backend">
    <div class="jumbotron text-center">
      <h2>Autentikasi Vue.js dengan JWT Lumen</h2>
      <p>Anda Berada di {{ currentRouteName }}</p>
    </div>
    <div class="container">
      <div class="row">
        <div class="col-md-6 offset-md-3">
          <div id="nav">
            <router-link to="/">Frontend</router-link> |
            <router-link to="/backend">Backend</router-link>
          </div>
          <p class="text-success" v-if="token">
            Halaman ini diakses DENGAN autentikasi login dan token !!
          </p>
          <p class="text-danger" v-else>
            Halaman ini diakses TANPA autentikasi login dan token !!
          </p>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Backend",
  data() {
    return {
      token: "",
      currentRouteName: ""
    };
  },
  created() {
    this.currentRouteName = this.$route.name;
    const token = this.$route.params.token;
    if (token) {
      this.token = token;
      alert("Anda mengakses halaman Backend dengan token : " + "\n\n" + token);
    }
  }
};
</script>

<style scoped>
p {
  text-align: center;
}
</style>

Tahapan membaca alur kode file Backend.vue di atas :

  1. Ketika memasuki lifecycle created(), Vue melakukan dua hal. Pertama, mengeset nilai currentRouteName dengan nama route aktif pada baris kode this.currentRouteName = this.$route.name. Selanjutnya yang kedua, mengecek apakah ada token yang dilewatkan ke parameter router di baris kode const token = this.$route.params.token dan if (token) { } .
  2. Masih di lifecycle created(), nilai token akan ditampilkan melalui alert (jika ada) sebelum halaman di-render.
  3. Keluar dari lifecycle created(), directive v-if="token" akan memilih elemen <p> yang di-render, apakah DENGAN autentikasi login dan token atau TANPA autentikasi login dan token. Sebelum itu, nilai currentRouteName telah lebih dulu ditampilkan di dalam elemen <p>Anda Berada di {{ currentRouteName }}</p>.

Langkah 4 – Tambahkan Style Navigasi

Style CSS digunakan oleh elemen <div id="nav"> pada file Home.vue dan Backend.vue, karena itu penulisan style harus dilakukan di atas level router Home dan Backend, yaitu file App.vue.

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<style>
#nav {
  text-align: center;
  padding: 0 0 50px 0;
}

#nav a {
  font-weight: bold;
  color: #42b983;
}

#nav a.router-link-exact-active {
  color: #2c3e50;
  text-decoration: none;
}
</style>

Langkah 5 – Jalankan Aplikasi Vue

Pada root project Vue, ketik perintah berikut untuk menjalankan aplikasi Vue :

npm run serve

Buka URL href="http://localhost:8080/ pada browser, login dengan username variancode dan password ehrahasiadong :

autentikasi vue.js dengan jwt lumen