Learn Vue - Authentication & Authorization
Series/Learn Vue/Episode 12
Episode 12 of 24

Learn Vue - Authentication & Authorization

This episode covers access security in Vue: client-side authentication patterns, token storage and refresh flows, route guards for protected pages, and role-based access control that restricts the UI according to a user's permissions.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Most applications have areas that not everyone should access: the admin dashboard, profile pages, or transaction history. Managing who gets in and what they can see after logging in is the job of authentication and authorization.

Episode 12 covers client-side authentication patterns in Vue: a login flow that stores user state, token storage with a refresh flow for secure requests, route guards to protect pages, and role-based access control so the UI adapts to a user's permissions.

Client-side Authentication Patterns

Auth State with Composables

Make login status a global state that every component can access:

JSComposable useAuth
import { ref, computed } from "vue";
 
const user = ref(null);
const token = ref("");
 
export function useAuth() {
  const isLogin = computed(() => !!user.value);
 
  function setAuth(data) {
    user.value = data.user;
    token.value = data.token;
  }
 
  function logout() {
    user.value = null;
    token.value = "";
  }
 
  return { user, token, isLogin, setAuth, logout };
}

useAuth() stores user and token at module scope so the state is shared across all components. setAuth(data) is called after a successful login and logout() clears everything.

The Login Flow

The login page calls the API, then stores the result:

JSLogin form
<script setup>
import { reactive } from "vue";
import { useAuth } from "../composables/useAuth";
import { useRouter } from "vue-router";
 
const form = reactive({ email: "", password: "" });
const { setAuth } = useAuth();
const router = useRouter();
 
async function masuk() {
  const res = await fetch("/api/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(form),
  });
  const data = await res.json();
  setAuth(data);
  router.push("/dashboard");
}
</script>

setAuth(data) stores the user and token in the composable. Navigation to the protected page happens via router.push after a successful login.

Token Storage and Refresh Flow

Secure Storage

A JWT token is best kept in memory rather than localStorage, so malicious scripts can't easily read it. To stay logged in across page refreshes, pair it with an httpOnly cookie managed by the server:

Strategi penyimpanan token
memory: token aktif untuk request API
cookie httpOnly: refresh token untuk sesi panjang
localStorage: hindari kecuali token non-sensitif

An httpOnly cookie can only be read by the server, which drastically reduces the risk of tokens being stolen through XSS.

Axios Interceptor for Refresh

When a token expires, send the refresh token and retry the failed request:

Install Axios
npm install axios
JSInterceptor refresh
import axios from "axios";
 
const api = axios.create({ baseURL: "/api" });
 
api.interceptors.response.use(
  (res) => res,
  async (error) => {
    const original = error.config;
    if (error.response.status === 401 && !original._retry) {
      original._retry = true;
      const { data } = await axios.post("/api/refresh");
      api.defaults.headers.common.Authorization = `Bearer ${data.token}`;
      return api(original);
    }
    return Promise.reject(error);
  }
);

interceptors.response.use(...) catches 401 responses, refreshes the token, then retries the original request via api(original). The _retry flag prevents an endless refresh loop.

Route Guards and Protected Views

Protecting Pages

A navigation guard blocks pages that require login:

JSGuard autentikasi
router.beforeEach((to) => {
  const { isLogin } = useAuth();
  if (to.meta.requiresAuth && !isLogin.value) {
    return { name: "login", query: { redirect: to.fullPath } };
  }
});

to.meta.requiresAuth reads the route's meta. If the user isn't logged in, the guard redirects to the login page with a redirect so they can return after logging in.

Role-Based Access Control

Restricting the UI per Role

Authorization determines what can be seen after logging in:

JSAkses berdasarkan role
<script setup>
import { useAuth } from "../composables/useAuth";
 
const { user } = useAuth();
const isAdmin = () => user.value?.role === "admin";
</script>
 
<template>
  <button v-if="isAdmin()">Hapus data</button>
  <button v-else>Ajukan permintaan</button>
</template>

user.value?.role === "admin" decides which elements are rendered. The same role is also enforced in route meta and guards, not just in the UI.

Warning

All client-side checks are just UI convenience. Real security lives on the server: verify tokens, check roles, and validate every request. A client that can be bypassed is not a safeguard.

Summary

Episode 12 equipped you with authentication and authorization patterns in Vue: user state through a composable, secure token storage with an interceptor-based refresh flow, route guards for protected pages, and role-based access control that restricts the UI.

Key takeaways:

  • The useAuth composable stores user and token globally.
  • Keep the access token in memory, the refresh token in an httpOnly cookie.
  • An Axios interceptor automatically refreshes on 401 responses.
  • A beforeEach route guard blocks pages that require login.
  • Route meta stores the role requirements for a page.
  • Role checks in the UI are just a layer; the server must always re-validate.

In the next episode 13, we'll cover API security and data protection — secure API integration, environment variable management, CSRF and XSS protection, and how to handle sensitive data in a client application.