Secure Authentication Patterns
Token storage (memory vs localStorage vs httpOnly cookie), silent refresh, logout on tab close.
Secure Authentication Patterns is a free Vue Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Vue Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Tokens and Where They Live
SPAs authenticate with tokens, but where you store them decides your XSS and CSRF exposure. The modern pattern splits a short-lived access token from a long-lived refresh token, each stored differently.
The localStorage Problem
Storing tokens in localStorage is convenient but dangerous: any XSS-injected script can read it and exfiltrate the token. Avoid putting credentials there.
// AVOID
localStorage.setItem("token", accessToken);Access Token in Memory
Keep the access token in a JavaScript variable or a reactive ref. It vanishes on reload, so a successful XSS has a much smaller window and nothing persists on disk.
import { ref } from "vue";
export const accessToken = ref(null);Refresh Token in an httpOnly Cookie
The refresh token lives in a HttpOnly, Secure, SameSite cookie. JavaScript cannot read it, so XSS cannot steal it, and the browser sends it only to your auth endpoint.
Set-Cookie: refresh=...; HttpOnly; Secure; SameSite=Strict; Path=/authAttaching the Access Token
An Axios request interceptor adds the in-memory access token to the Authorization header on every call.
api.interceptors.request.use((config) => {
if (accessToken.value) {
config.headers.Authorization = "Bearer " + accessToken.value;
}
return config;
});Detecting Expiry: the 401
When the access token expires, the server responds with 401 Unauthorized. A response interceptor catches this to trigger a refresh.
api.interceptors.response.use(
(res) => res,
async (error) => {
if (error.response?.status === 401) {
// attempt refresh
}
return Promise.reject(error);
}
);Refreshing the Token
On a 401, call the refresh endpoint. The browser sends the httpOnly refresh cookie automatically (withCredentials), and the server returns a fresh access token.
const { data } = await axios.post("/auth/refresh", null, {
withCredentials: true
});
accessToken.value = data.accessToken;Retrying the Original Request
After refreshing, replay the original failed request with the new token so the user never notices.
async (error) => {
const original = error.config;
if (error.response?.status === 401 && !original._retry) {
original._retry = true;
const { data } = await axios.post("/auth/refresh", null, { withCredentials: true });
accessToken.value = data.accessToken;
original.headers.Authorization = "Bearer " + data.accessToken;
return api(original);
}
return Promise.reject(error);
}Avoiding Refresh Stampedes
If many requests 401 at once, queue them behind a single in-flight refresh so you do not fire the refresh endpoint repeatedly.
let refreshing = null;
function refresh() {
if (!refreshing) {
refreshing = axios.post("/auth/refresh", null, { withCredentials: true })
.finally(() => { refreshing = null; });
}
return refreshing;
}Logging Out
On logout, clear the in-memory token and call an endpoint that clears the refresh cookie server-side.
async function logout() {
accessToken.value = null;
await axios.post("/auth/logout", null, { withCredentials: true });
}Combine with CSRF Defense
Because the refresh cookie is sent automatically, protect that endpoint with SameSite and CSRF tokens just like other cookie-authenticated routes.
Quick Check
Where should the access token and refresh token each be stored?
Recap
Store the access token in an in-memory ref (never localStorage) and the refresh token in an HttpOnly cookie. A request interceptor attaches the bearer token; a response interceptor catches 401, calls the refresh endpoint, updates the token, and retries the original request - deduplicating concurrent refreshes and pairing with CSRF defenses.
Frequently asked questions
Is the “Secure Authentication Patterns” lesson free?
Yes — the full text of “Secure Authentication Patterns” is free to read here on the web, and the Vue Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Vue Academy course, upgrade to CoddyKit PRO.
What will I learn in “Secure Authentication Patterns”?
Token storage (memory vs localStorage vs httpOnly cookie), silent refresh, logout on tab close. You practise Vue Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Vue Academy?
No prior experience is required. Vue Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Secure Authentication Patterns” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Vue Academy lesson?
Yes. Every Vue Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- XSS Prevention in Vue
- Content Security Policy (CSP) with Vue
- CSRF Protection in Vue SPAs
- Secure Authentication Patterns