Vue + Auth0: Clean Setup and Storage Insights

A practical guide to integrating Auth0 in a Vue 3 + Vite app, with UI polish and localStorage diagnostics.

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

  1. Project setup
  • npm install
  • npm run dev
  1. 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
  1. Environment variables
  • Create .env at project root:
    • VITE_AUTH0_DOMAIN=<your-auth0-domain>
    • VITE_AUTH0_CLIENT_ID=<your-client-id>
    • VITE_REDIRECT_URI=http://localhost:5173
    • VITE_AUTH0_ORGANIZATION=<your-org-id-or-slug> (optional)
  1. Follow Dashboard Quickstart

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.