@dmytro_shved Doing authorisation checks on the client is never secure. This is the entire reason Sanctum and its cookie-based authentication was created.
VueJS 3: Storing Token & User. Code Analyzing
Hey everyone
I am analyzing the code of RealWorld Example (Vue 3 + Pinia) and I'm trying to understand the author's logic for storing the user and token.
Below is a summary of what I observed.
1. User storage in localStorage
The app uses a custom Storage class to store the user object (from what I understood including the token) in localStorage:
// src/utils/storage.ts
export const userStorage = new Storage<User>('user')
2. Global access via Pinia
The user is exposed through a Pinia store:
// src/store/user.ts
const user = ref(userStorage.get())
const isAuthorized = computed(() => !!user.value)
function updateUser(userData?: User | null) {
if (userData) {
userStorage.set(userData)
api.setSecurityData(userData.token)
user.value = userData
} else {
userStorage.remove()
api.setSecurityData(null)
user.value = null
}
}
Notes:
-
user.valueprovides reactive access to the user data. -
isAuthorizedis a computed property based on whether a user exists (boolean true/false). -
updateUserupdates both Pinia andlocalStoragewith edited user data.
3. Token storage in JavaScript memory
The HttpClient class, used for API requests, has a private securedData property that holds the token.
// src/services/api.ts
export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = "https://api.realworld.show/api";
private securityData: SecurityDataType | null = null; // this line
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
private abortControllers = new Map<CancelToken, AbortController>();
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
fetch(...fetchParams);
//...
public setSecurityData = (data: SecurityDataType | null) => {
this.securityData = data;
};
Source code of api.ts (the file is pretty big (800+ lines of code), so you can find it by: Ctrl + F and typing HttpClient.)
During login, the token is stored both in localStorage (via userStorage) and in JavaScript memory (securityData):
// src/store/user.ts
updateUser(result.data.user) // sets token in both places
4. Token recovery after reload
On application start, a plugin (set-authorization-token.ts) reads the token from localStorage and writes it to HttpClient.securityData
// src/plugins/set-authorization-token.ts
import { api } from 'src/services'
import { userStorage } from 'src/store/user'
export default function setAuthorizationToken(): void {
const token = userStorage.get()?.token
if (token !== undefined)
api.setSecurityData(token)
}
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import registerGlobalComponents from './plugins/global-components'
import setAuthorizationToken from './plugins/set-authorization-token'
import { router } from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
setAuthorizationToken()
registerGlobalComponents(app)
app.mount('#app')
Authentication flow:
-
The user visits
Login.vueand logs in viaapi.users.login, which returns a user object (with token?). -
updateUser(user)saves user inuser.tsanduserStorage, and copies token intoHttpClient.securityData. -
On page reload,
setAuthorizationToken()reads the token fromuserStorageand restores it toHttpClient.securityData. -
API requests use
HttpClient.securityDatain the Authorization header:
// src/services/index.ts
import { CONFIG } from 'src/config'
import type { GenericErrorModel, HttpResponse } from 'src/services/api'
import { Api, ContentType } from 'src/services/api'
export const limit = 10
export const api = new Api({
baseUrl: `${CONFIG.API_HOST}/api`,
securityWorker: token => token ? { headers: { Authorization: `Token ${String(token)}` } } : {},
baseApiParams: {
headers: {
'content-type': ContentType.Json,
},
format: 'json',
},
})
// ...
My questions
- If the token is already in
userStorage, why does the author also store it in JavaScript memory (securityData)? - Is this approach secure? I understand that storing tokens in
userStorageis vulnerable to XSS attacks, but since this repo has many stars I still don't understand the logic. - How is the token actually protected here, given that it persists in
userStorageand only copied into JavaScript memory on initialization?
I'm trying to understand the rationale and security implications of this design. Is this a common pattern? Are there better practices for SPA token management?
Would be grateful for your help
Please or to participate in this conversation.