Skip to content

Panduan Integrasi Vue.js ​

Panduan ini membahas integrasi autentikasi DUMEG Identity pada aplikasi frontend berbasis Vue 3 (Composition API), pengelolaan status dengan Pinia, perlindungan rute via Vue Router, dan pola komunikasi yang aman.

Status Kode Referensi (Starter Template)

Contoh kode di bawah ini adalah kode referensi arsitektural (starter kit) untuk membantu pengembang membangun integrasi frontend. Kode ini belum diuji dalam aplikasi integrasi nyata maupun server Keycloak nyata, dan BUKAN paket siap-production secara langsung. Setiap tim wajib menyesuaikan dengan kebutuhan manajemen state, library HTTP client, dan arsitektur backend spesifik sebelum diaplikasikan ke lingkungan production.


1. Rekomendasi Pola Arsitektur: Backend-for-Frontend (BFF) ​

Dalam pengembangan aplikasi web modern yang memiliki API backend (seperti Laravel), pola Backend-for-Frontend (BFF) adalah standar arsitektur yang sangat direkomendasikan untuk integrasi DUMEG Identity.

mermaid
flowchart LR
    subgraph Browser["Browser Pengguna"]
        Vue["Vue 3 SPA\n(Pinia + Vue Router)"]
    end

    subgraph Backend["Server Backend (BFF)"]
        Laravel["Laravel Backend / Sanctum\n(Confidential Client)"]
        Session[(Encrypted Session\nHttpOnly Cookie)]
    end

    subgraph IdP["DUMEG Identity"]
        Keycloak["Keycloak Server\nlogin.dumeg.com"]
    end

    Vue <-->|"1. Cookie Session (HttpOnly, Secure)"| Laravel
    Laravel <-->|"2. OIDC PKCE + Client Secret"| Keycloak
    Laravel --- Session

Mengapa Memilih Pola BFF? ​

KriteriaPola Backend-for-Frontend (BFF)Token di Browser (localStorage)
Penyimpanan TokenTersimpan aman di serverDi localStorage browser
Resiko Serangan XSSTerlindungi dari Eksfiltrasi Token (Cookie HttpOnly tak terbaca JS)Sangat Rentan (Token mudah dicuri script pihak ketiga)
Kerahasiaan Secretclient_secret aman di backendTidak bisa menggunakan client secret
Kompleksitas FrontendSederhana (hanya memanggil endpoint API /api/user)Rumit (parsing JWT, cek expiry, refresh loop di JS)

Jangan Gunakan `localStorage` untuk Token Sensitif

Menyimpan access_token atau id_token di window.localStorage membuka celah fatal bagi serangan Cross-Site Scripting (XSS). Seluruh data identitas dapat diekstraksi oleh skrip pihak ketiga.


2. Pengelolaan State Autentikasi dengan Pinia ​

Buat Pinia store stores/auth.ts untuk melacak status login, profil pengguna, dan memicu alur login/logout:

typescript
// src/stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import axios from 'axios'

export interface UserProfile {
  id: number
  keycloak_id: string
  name: string
  email: string
  roles?: string[]
}

export const useAuthStore = defineStore('auth', () => {
  const user = ref<UserProfile | null>(null)
  const isLoading = ref<boolean>(false)
  const isInitialized = ref<boolean>(false)

  const isAuthenticated = computed(() => !!user.value)

  /**
   * Mengambil data profil user aktif dari backend Laravel (BFF)
   */
  async function fetchUser(): Promise<UserProfile | null> {
    isLoading.value = true
    try {
      const response = await axios.get<UserProfile>('/api/user', {
        headers: { Accept: 'application/json' },
        withCredentials: true, // Wajib menyertakan cookie sesi
      })
      user.value = response.data
      return user.value
    } catch (error) {
      user.value = null
      return null
    } finally {
      isLoading.value = false
      isInitialized.value = true
    }
  }

  /**
   * Mengarahkan browser ke rute inisiasi login SSO Laravel
   */
  function loginWithDumeg() {
    // Redirect langsung ke route inisiasi SSO di Laravel
    window.location.href = '/auth/dumeg/login'
  }

  /**
   * Logout dari backend dan Keycloak SSO
   */
  async function logout() {
    try {
      // POST ke endpoint logout Laravel
      await axios.post('/logout', {}, { withCredentials: true })
    } finally {
      user.value = null
      window.location.href = '/login'
    }
  }

  return {
    user,
    isLoading,
    isInitialized,
    isAuthenticated,
    fetchUser,
    loginWithDumeg,
    logout,
  }
})

3. Proteksi Rute dengan Vue Router ​

Terapkan navigasi terproteksi (Navigation Guards) pada router/index.ts untuk memastikan hanya pengguna yang telah terotentikasi yang dapat membuka halaman tertentu:

typescript
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/HomeView.vue'),
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/LoginView.vue'),
    meta: { guestOnly: true },
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('@/views/DashboardView.vue'),
    meta: { requiresAuth: true },
  },
  {
    path: '/profile',
    name: 'Profile',
    component: () => import('@/views/ProfileView.vue'),
    meta: { requiresAuth: true },
  },
]

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

// Global Navigation Guard
router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore()

  // Inisialisasi status user jika aplikasi baru dibuka
  if (!authStore.isInitialized) {
    await authStore.fetchUser()
  }

  // Jika rute butuh autentikasi dan user belum login
  if (to.meta.requiresAuth && !authStore.isAuthenticated) {
    return next({
      name: 'Login',
      query: { redirect: to.fullPath },
    })
  }

  // Jika user sudah login mencoba akses halaman login
  if (to.meta.guestOnly && authStore.isAuthenticated) {
    return next({ name: 'Dashboard' })
  }

  next()
})

export default router

4. Penanganan Sesi Kedaluwarsa (Axios Interceptors) ​

Jika sesi backend kedaluwarsa atau tidak valid, tangkap respons 401 Unauthorized secara terpusat:

typescript
// src/plugins/axios.ts
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'
import router from '@/router'

axios.defaults.baseURL = import.meta.env.VITE_API_BASE_URL || ''
axios.defaults.withCredentials = true

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response && error.response.status === 401) {
      const authStore = useAuthStore()
      authStore.user = null
      
      if (router.currentRoute.value.meta.requiresAuth) {
        router.push({
          name: 'Login',
          query: { session_expired: '1' }
        })
      }
    }
    return Promise.reject(error)
  }
)

5. Contoh Komponen Tampilan (Vue 3) ​

Komponen Tombol Login SSO (DumegLoginButton.vue) ​

vue
<!-- src/components/DumegLoginButton.vue -->
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth'

const authStore = useAuthStore()

function handleLogin() {
  authStore.loginWithDumeg()
}
</script>

<template>
  <button @click="handleLogin" class="dumeg-sso-btn" type="button">
    <span class="icon">🔐</span>
    <span class="label">Masuk dengan DUMEG Identity (SSO)</span>
  </button>
</template>

<style scoped>
.dumeg-sso-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 12px 18px;
  background-color: #0875c9;
  color: #ffffff;
  border: none;
  border-radius: 8px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.1s ease;
}

.dumeg-sso-btn:hover {
  background-color: #0284c7;
  transform: translateY(-1px);
}

.dumeg-sso-btn:active {
  transform: translateY(0);
}
</style>

6. Skenario Khusus: Standalone SPA (Tanpa Backend) ​

Apabila aplikasi frontend Vue.js Anda adalah Single Page Application murni yang tidak memiliki backend server sendiri:

  1. Daftarkan client di Keycloak sebagai Public Client.
  2. Wajib aktifkan PKCE S256 pada Keycloak Client Settings.
  3. Gunakan library OIDC tersertifikasi seperti oidc-client-ts yang mengelola perputaran token di memori aplikasi.
  4. Hindari menyimpan refresh token di localStorage: Jika halaman di-refresh, manfaatkan silent SSO session check via hidden iframe atau redirect cepat ke Keycloak yang memanfaatkan session cookie KEYCLOAK_IDENTITY aktif pengguna.

Developer Portal Single Sign-On (SSO) Ekosistem DUMEG