Vue + Auth0: Clean Setup and Storage Insights
If you’re spinning up a Vue SPA and want sign-in running in minutes—not days—this post is for you. We’ll integrate Auth0 cleanly, keep styles minimal and consistent, and add a small but powerful dev tool: a storage inspector right inside the profile view.
Goal: A production-ready Auth0 setup with a clean UI, plus practical diagnostics for localStorage and cache entries.
What We Built
- Auth0 integration in a Vue 3 + Vite SPA.
- Cleaned design tokens and standardized buttons (primary, secondary, error) for a consistent look.
- User Profile with an accordion that summarizes localStorage usage and pretty-prints JSON entries.
Setup
- Project setup
npm installnpm run dev
- Auth0 dashboard
- Create an Application as SPA.
- Add Allowed Callback URLs:
http://localhost:5173 - Add Allowed Logout URLs:
http://localhost:5173 - Add Allowed Web Origins:
http://localhost:5173
- Environment variables
- Create
.envat project root:VITE_AUTH0_DOMAIN=<your-auth0-domain>VITE_AUTH0_CLIENT_ID=<your-client-id>VITE_REDIRECT_URI=http://localhost:5173VITE_AUTH0_ORGANIZATION=<your-org-id-or-slug>(optional)
- Follow Dashboard Quickstart
- Keep the Auth0 Quickstart open while creating credentials and configuring the application. Or just clone the repository I already have set up: https://github.com/yagiz-aydin/auth0-vue
Components
src/components/LoginButton.vue
<template>
<button
@click="handleLogin"
class="button primary"
:disabled="isLoading"
>
{{ isLoading ? 'Loading...' : 'Log In' }}
</button>
</template>
<script setup lang="ts">
import { useAuth0 } from '@auth0/auth0-vue'
const { loginWithRedirect, isLoading } = useAuth0()
const handleLogin = () => {
loginWithRedirect()
}
</script>src/components/LogoutButton.vue
<template>
<button
@click="handleLogout"
class="button error"
:disabled="isLoading"
>
{{ isLoading ? 'Loading...' : 'Log Out' }}
</button>
</template>
<script setup lang="ts">
import { useAuth0 } from '@auth0/auth0-vue'
const { logout, isLoading } = useAuth0()
const handleLogout = () => {
logout({ logoutParams: { returnTo: window.location.origin } })
}
</script>src/components/UserProfile.vue (Stored Data accordion)
<ul class="entry-list">
<li v-for="e in summary.topEntries" :key="e.key" class="entry-item">
<span class="entry-key" :title="e.key">{{ shortenMiddle(e.key, 42) }}</span>
<span class="entry-size">{{ formatBytes(e.length) }}</span>
</li>
</ul>
<div class="data-section">
<div class="data-title">Stored Data</div>
<ul class="data-list">
<li v-for="d in entries" :key="d.key" class="data-item">
<div class="data-row">
<span class="data-key" :title="d.key">{{ shortenMiddle(d.key, 42) }}</span>
<span class="data-meta">{{ d.type.toUpperCase() }} · {{ formatBytes(d.length) }}</span>
<button class="button data-toggle" @click="openData[d.key] = !openData[d.key]">
{{ openData[d.key] ? 'Hide' : 'View' }}
</button>
</div>
<pre v-if="openData[d.key] && d.type === 'json'" class="json-view">{{ JSON.stringify(d.parsed, null, 2) }}</pre>
<div v-else-if="openData[d.key]" class="text-view">{{ d.preview }}</div>
</li>
</ul>
</div>src/utils/helpers.ts
export function shortenMiddle(s: string, max = 36): string {
if (!s) return ''
if (s.length <= max) return s
const keep = Math.floor((max - 1) / 2)
return s.slice(0, keep) + '…' + s.slice(s.length - keep)
}
export function readLocalEntries(max = 20) {
const out = [] as { key: string; length: number; type: 'json' | 'text'; preview: string; parsed?: unknown }[]
for (let i = 0; i < localStorage.length; i++) {
const k = localStorage.key(i)
if (!k) continue
const v = localStorage.getItem(k) || ''
let type: 'json' | 'text' = 'text'
let parsed: unknown = undefined
if (v && (v.startsWith('{') || v.startsWith('['))) {
try {
const obj = JSON.parse(v)
if (typeof obj === 'object') {
type = 'json'
parsed = obj
}
} catch {}
}
const preview = type === 'json' ? shortenMiddle(v, 120) : shortenMiddle(v, 120)
out.push({ key: k, length: v.length + k.length, type, preview, parsed })
}
out.sort((a, b) => b.length - a.length)
return out.slice(0, max)
}Auth0 Plugin (main.ts)
src/main.ts
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createAuth0 } from '@auth0/auth0-vue'
import App from './App.vue'
import router from './router/landing'
const app = createApp(App)
app.use(createAuth0({
domain: import.meta.env.VITE_AUTH0_DOMAIN,
clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
cacheLocation: 'localstorage',
useRefreshTokens: true,
authorizationParams: {
redirect_uri: import.meta.env.VITE_REDIRECT_URI || window.location.origin,
scope: 'openid profile email offline_access',
organization: import.meta.env.VITE_AUTH0_ORGANIZATION
}
}))
app.use(createPinia())
app.use(router)
app.mount('#app')Quickstart
For the latest, framework-specific instructions, follow Auth0 Quickstart for Vue SPA
This dashboard page includes guided steps to create credentials, configure callback/logout URLs, and integrate the SDK in React, Vue, or other SPA frameworks.