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.

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.
Make login status a global state that every component can access:
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 page calls the API, then stores the result:
<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.
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:
memory: token aktif untuk request API
cookie httpOnly: refresh token untuk sesi panjang
localStorage: hindari kecuali token non-sensitifAn httpOnly cookie can only be read by the server, which drastically reduces the risk of tokens being stolen through XSS.
When a token expires, send the refresh token and retry the failed request:
npm install axiosimport 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.
A navigation guard blocks pages that require login:
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.
Authorization determines what can be seen after logging in:
<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.
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:
useAuth composable stores user and token globally.beforeEach route guard blocks pages that require login.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.