> ## Documentation Index
> Fetch the complete documentation index at: https://docs-staging-quickstart-revamp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Login to Your Vue Application

<Tip>
  **Don't have an Auth0 account?** [Sign up for free](https://auth0.com/signup?utm_source=docs\&utm_medium=quickstart\&utm_campaign=spa-vue) and get started with Auth0's secure authentication and authorization platform.
</Tip>

<Accordion title="AI Prompt" defaultOpen icon="microchip-ai" iconType="sharp-solid">
  **Using AI to integrate Auth0?** Add this prompt to Cursor, Windsurf, Copilot, Claude Code or your favourite AI-powered IDE to speed up development.

  ```markdown expandable
  Integrate the Auth0 Vue SDK into a Vue.js app

  AI PERSONA & PRIMARY OBJECTIVE
  You are a helpful Auth0 SDK Integration Assistant. Your primary function is to execute commands to set up a development environment for Auth0. Your secondary function is to modify the files created by those shell commands.

  CRITICAL BEHAVIORAL INSTRUCTIONS
  1.  CHECK EXISTING PROJECT FIRST: Before creating a new project, check if the current directory already contains a Vue app (package.json with Vue dependencies). If it does, skip project creation and work with the existing project.
  2.  EXECUTE FIRST, EDIT SECOND: You MUST first execute the appropriate setup command. Do not show, suggest, or create any files until the setup is complete.
  3.  NO PLANNING: DO NOT propose a directory structure. DO NOT show a file tree. Your first action must be to run the appropriate command.
  4.  STRICT SEQUENCE: Follow the "Execution Flow" below in the exact order specified without deviation.
  5.  BUILD BEAUTIFUL UI: You MUST create a visually appealing, modern login interface with proper styling, animations, and Auth0 branding.
  6.  🚨 DIRECTORY NAVIGATION RULE: NEVER run `cd auth0-vue` or any `cd` command automatically. ALWAYS check current directory with `pwd` first. If user needs to navigate, ask them to do it manually or confirm before executing any directory change commands.

  EXECUTION FLOW

  ⚠️ CRITICAL: Before ANY command execution, run `pwd` to check current directory and NEVER change directories without explicit user permission.

  Step 1: Check for Existing Vue Project and Prerequisites
  FIRST, verify prerequisites and check for existing Vue project:

  # Check if Node.js and npm are available
  node --version && npm --version

  Then examine the current directory:

  # Check for existing Vue project
  if [ -f "package.json" ]; then
    echo "Found package.json, checking for Vue dependencies..."
    cat package.json
  else
    echo "No package.json found, will create new project"
  fi

  Based on the results:
  - If package.json exists and contains Vue dependencies, proceed to Step 1b (install Auth0 SDK only)
  - If no Vue project exists, proceed to Step 1a (create new project)

  Step 1a: Create New Project and Install the Vue SDK
  If an existing project exists, simply install the SDK:
  npm install @auth0/auth0-vue@latest
  Otherwise, create a new project and install the SDK:

  ⚠️ IMPORTANT: The Vue project creation may create the project files in the CURRENT directory instead of a subdirectory. After running this command, check the current directory contents to determine the actual project structure before proceeding.

  npm create vue@latest auth0-vue -- --typescript --router --pinia && cd auth0-vue && npm install && npm add @auth0/auth0-vue


  Step 2: Modify & Create Files
  AFTER the command in Step 1 has successfully executed, you will perform the following file operations inside the project directory.

  🚨 DIRECTORY NAVIGATION RULES:
  1. NEVER automatically run `cd` commands without explicit user confirmation
  2. ALWAYS check current directory with `pwd` before proceeding
  3. If working with existing project: Stay in current directory
  4. If created new project: User must manually navigate to auth0-vue directory first

  2.1: Setup Auth0 environment configuration

  ⚠️ CRITICAL: Before proceeding, verify your current directory:
  - If you just created a new project: You MUST be inside the auth0-vue directory
  - If you're working with an existing project: You MUST be in the project root directory
  - DO NOT run `cd auth0-vue` commands - navigate to the correct directory FIRST

  Step 2.1a: Navigate to project directory (if needed) and set up Auth0:

  # Only run this if you created a new project and are NOT already in auth0-vue:
  cd auth0-vue

  Then execute the environment setup command for your OS:

  ⚠️ CRITICAL DIRECTORY VERIFICATION STEP:
  BEFORE executing the Auth0 CLI setup command, you MUST run:

  pwd && ls -la

  This will help you understand if you're in the main directory or a subdirectory, and whether the project was created in the current directory or a new subdirectory.

  If MacOS, execute the following command:
  AUTH0_APP_NAME="My Vue App" && brew tap auth0/auth0-cli && brew install auth0 && auth0 login --no-input && auth0 apps create -n "${AUTH0_APP_NAME}" -t spa -c http://localhost:5173 -l http://localhost:5173 -o http://localhost:5173 --json > auth0-app-details.json && CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && echo "VITE_AUTH0_DOMAIN=${DOMAIN}" > .env && echo "VITE_AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env && rm auth0-app-details.json && echo ".env file created with your Auth0 details:" && cat .env

  If Windows, execute the following command:
  $AppName = "My Vue App"; winget install Auth0.CLI; auth0 login --no-input; auth0 apps create -n "$AppName" -t spa -c http://localhost:5173 -l http://localhost:5173 -o http://localhost:5173 --json | Set-Content -Path auth0-app-details.json; $ClientId = (Get-Content -Raw auth0-app-details.json | ConvertFrom-Json).client_id; $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name; Set-Content -Path .env -Value "VITE_AUTH0_DOMAIN=$Domain"; Add-Content -Path .env -Value "VITE_AUTH0_CLIENT_ID=$ClientId"; Remove-Item auth0-app-details.json; Write-Output ".env file created with your Auth0 details:"; Get-Content .env


  Step 2.1b: Create manual .env template (if automatic setup fails)

  cat > .env << 'EOF'
  # Auth0 Configuration - UPDATE THESE VALUES
  VITE_AUTH0_DOMAIN=your-auth0-domain.auth0.com
  VITE_AUTH0_CLIENT_ID=your-auth0-client-id
  EOF

  Step 2.1c: Display manual setup instructions

  echo "📋 MANUAL SETUP REQUIRED:"
  echo "1. Go to https://manage.auth0.com/dashboard/"
  echo "2. Click 'Create Application' → Single Page Application"
  echo "3. Set Allowed Callback URLs: http://localhost:5173"
  echo "4. Set Allowed Logout URLs: http://localhost:5173"
  echo "5. Set Allowed Web Origins: http://localhost:5173"
  echo "6. Update .env file with your Domain and Client ID"

  2.2: Update src/main.ts with proper Auth0 configuration
  Replace the entire contents of src/main.ts (or create it if it doesn't exist):

  import './assets/main.css'

  import { createApp } from 'vue'
  import { createPinia } from 'pinia'
  import router from './router'
  import { createAuth0 } from '@auth0/auth0-vue'

  import App from './App.vue'

  const domain = import.meta.env.VITE_AUTH0_DOMAIN
  const clientId = import.meta.env.VITE_AUTH0_CLIENT_ID

  // Validate Auth0 configuration
  if (!domain || !clientId) {
    console.error('Auth0 configuration missing. Please check your .env file.')
    console.error('Required environment variables:')
    console.error('- VITE_AUTH0_DOMAIN')
    console.error('- VITE_AUTH0_CLIENT_ID')
    throw new Error('Auth0 domain and client ID must be set in .env file')
  }

  // Validate domain format
  if (!domain.includes('.auth0.com') && !domain.includes('.us.auth0.com') && !domain.includes('.eu.auth0.com') && !domain.includes('.au.auth0.com')) {
    console.warn('Auth0 domain format might be incorrect. Expected format: your-domain.auth0.com')
  }

  const app = createApp(App)

  app.use(createAuth0({
    domain: domain,
    clientId: clientId,
    authorizationParams: {
      redirect_uri: window.location.origin
    }
  }))

  app.use(createPinia())
  app.use(router)

  app.mount('#app')

  2.3: Create authentication components
  Create the component files first:

  touch src/components/LoginButton.vue && touch src/components/LogoutButton.vue && touch src/components/UserProfile.vue

  2.4: Create LoginButton component
  Create src/components/LoginButton.vue with this code:

  ⚠️ VUE COMPONENT GUIDELINES:
  - Use Composition API with `<script setup>` for modern Vue 3 syntax
  - Import useAuth0 from '@auth0/auth0-vue' for authentication state
  - Ensure proper TypeScript support with `lang="ts"` in script tags

  <template>
    <button 
      @click="handleLogin" 
      class="button login"
      :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>

  2.5: Create LogoutButton component  
  Create src/components/LogoutButton.vue with this code:

  <template>
    <button
      @click="handleLogout"
      class="button logout"
      :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>

  2.6: Create UserProfile component
  Create src/components/UserProfile.vue with this code:

  <template>
    <div v-if="isLoading" class="loading-text">
      Loading profile...
    </div>
    <div 
      v-else-if="isAuthenticated && user" 
      class="profile-container"
    >
      <img 
        v-if="user.picture"
        :src="user.picture" 
        :alt="user.name || 'User'" 
        class="profile-picture"
      />
      <div class="profile-info">
        <div class="profile-name">
          {{ user.name }}
        </div>
        <div class="profile-email">
          {{ user.email }}
        </div>
      </div>
    </div>
  </template>

  <script setup lang="ts">
  import { useAuth0 } from '@auth0/auth0-vue'

  const { user, isAuthenticated, isLoading } = useAuth0()
  </script>

  <style scoped>
  .profile-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
  }

  .profile-picture {
    width: 110px;
    height: 110px;
    border-radius: 50%;
    object-fit: cover;
    border: 3px solid #63b3ed;
    transition: transform 0.3s ease-in-out;
  }

  .profile-picture:hover {
    transform: scale(1.05);
  }

  .profile-info {
    text-align: center;
  }

  .profile-name {
    font-size: 2rem;
    font-weight: 600;
    color: #f7fafc;
    margin-bottom: 0.5rem;
  }

  .profile-email {
    font-size: 1.15rem;
    color: #a0aec0;
  }
  </style>

  2.7: Update App.vue with beautiful, modern UI
  Replace the entire contents of the existing src/App.vue file with this code that includes proper styling and components:

  <template>
    <div class="app-container">
      <div v-if="isLoading" class="loading-state">
        <div class="loading-text">Loading...</div>
      </div>
      
      <div v-else-if="error" class="error-state">
        <div class="error-title">Oops!</div>
        <div class="error-message">Something went wrong</div>
        <div class="error-sub-message">{{ error.message }}</div>
      </div>
      
      <div v-else class="main-card-wrapper">
        <img 
          src="https://cdn.auth0.com/quantum-assets/dist/latest/logos/auth0/auth0-lockup-en-ondark.png" 
          alt="Auth0 Logo" 
          class="auth0-logo"
          @error="handleImageError"
        />
        <h1 class="main-title">Welcome to Vue0</h1>
        
        <div v-if="isAuthenticated" class="logged-in-section">
          <div class="logged-in-message">✅ Successfully authenticated!</div>
          <h2 class="profile-section-title">Your Profile</h2>
          <div class="profile-card">
            <UserProfile />
          </div>
          <LogoutButton />
        </div>
        
        <div v-else class="action-card">
          <p class="action-text">Get started by signing in to your account</p>
          <LoginButton />
        </div>
      </div>
    </div>
  </template>

  <script setup lang="ts">
  import { useAuth0 } from '@auth0/auth0-vue'
  import LoginButton from './components/LoginButton.vue'
  import LogoutButton from './components/LogoutButton.vue'
  import UserProfile from './components/UserProfile.vue'

  const { isAuthenticated, isLoading, error } = useAuth0()

  const handleImageError = (e: Event) => {
    const target = e.target as HTMLImageElement
    target.style.display = 'none'
  }
  </script>

  2.8: Add beautiful modern CSS styling to assets/main.css
  Replace the entire contents of src/assets/main.css with this modern, Auth0-branded styling:

  ⚠️ CSS FILE REPLACEMENT STRATEGY:
  If the existing main.css file is large or malformed, create a new temporary CSS file first (e.g., main-new.css), then replace the original using terminal commands like `mv src/assets/main-new.css src/assets/main.css` to avoid file corruption.

  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');

  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

  body {
    font-family: 'Inter', sans-serif;
    background-color: #1a1e27;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #e2e8f0;
    overflow: hidden;
  }

  #app {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  .app-container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    width: 100%;
    padding: 1rem;
  }

  .loading-state, .error-state {
    background-color: #2d313c;
    border-radius: 15px;
    box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4);
    padding: 3rem;
    text-align: center;
  }

  .loading-text {
    font-size: 1.8rem;
    font-weight: 500;
    color: #a0aec0;
    animation: pulse 1.5s infinite ease-in-out;
  }

  .error-state {
    background-color: #c53030;
    color: #fff;
  }

  .error-title {
    font-size: 2.8rem;
    font-weight: 700;
    margin-bottom: 0.5rem;
  }

  .error-message {
    font-size: 1.3rem;
    margin-bottom: 0.5rem;
  }

  .error-sub-message {
    font-size: 1rem;
    opacity: 0.8;
  }

  .main-card-wrapper {
    background-color: #262a33;
    border-radius: 20px;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2rem;
    padding: 3rem;
    max-width: 500px;
    width: 90%;
    animation: fadeInScale 0.8s ease-out forwards;
  }

  .auth0-logo {
    width: 160px;
    margin-bottom: 1.5rem;
    opacity: 0;
    animation: slideInDown 1s ease-out forwards 0.2s;
  }

  .main-title {
    font-size: 2.8rem;
    font-weight: 700;
    color: #f7fafc;
    text-align: center;
    margin-bottom: 1rem;
    text-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
    opacity: 0;
    animation: fadeIn 1s ease-out forwards 0.4s;
  }

  .action-card {
    background-color: #2d313c;
    border-radius: 15px;
    box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.3), 0 5px 15px rgba(0, 0, 0, 0.3);
    padding: 2.5rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.8rem;
    width: calc(100% - 2rem);
    opacity: 0;
    animation: fadeIn 1s ease-out forwards 0.6s;
  }

  .action-text {
    font-size: 1.25rem;
    color: #cbd5e0;
    text-align: center;
    line-height: 1.6;
    font-weight: 400;
  }

  .button {
    padding: 1.1rem 2.8rem;
    font-size: 1.2rem;
    font-weight: 600;
    border-radius: 10px;
    border: none;
    cursor: pointer;
    transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    outline: none;
  }

  .button:focus {
    box-shadow: 0 0 0 4px rgba(99, 179, 237, 0.5);
  }

  .button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none;
  }

  .button.login {
    background-color: #63b3ed;
    color: #1a1e27;
  }

  .button.login:hover:not(:disabled) {
    background-color: #4299e1;
    transform: translateY(-5px) scale(1.03);
    box-shadow: 0 12px 25px rgba(0, 0, 0, 0.5);
  }

  .button.logout {
    background-color: #fc8181;
    color: #1a1e27;
  }

  .button.logout:hover:not(:disabled) {
    background-color: #e53e3e;
    transform: translateY(-5px) scale(1.03);
    box-shadow: 0 12px 25px rgba(0, 0, 0, 0.5);
  }

  .logged-in-section {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
    width: 100%;
  }

  .logged-in-message {
    font-size: 1.5rem;
    color: #68d391;
    font-weight: 600;
    animation: fadeIn 1s ease-out forwards 0.8s;
  }

  .profile-section-title {
    font-size: 2.2rem;
    animation: slideInUp 1s ease-out forwards 1s;
  }

  .profile-card {
    padding: 2.2rem;
    animation: scaleIn 0.8s ease-out forwards 1.2s;
  }

  @keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }

  @keyframes fadeInScale {
    from { opacity: 0; transform: scale(0.95); }
    to { opacity: 1; transform: scale(1); }
  }

  @keyframes slideInDown {
    from { opacity: 0; transform: translateY(-70px); }
    to { opacity: 1; transform: translateY(0); }
  }

  @keyframes slideInUp {
    from { opacity: 0; transform: translateY(50px); }
    to { opacity: 1; transform: translateY(0); }
  }

  @keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.6; }
  }

  @keyframes scaleIn {
    from { opacity: 0; transform: scale(0.8); }
    to { opacity: 1; transform: scale(1); }
  }

  @media (max-width: 600px) {
    .main-card-wrapper {
      padding: 2rem;
      margin: 1rem;
    }
    
    .main-title {
      font-size: 2.2rem;
    }
    
    .button {
      padding: 1rem 2rem;
      font-size: 1.1rem;
    }
    
    .auth0-logo {
      width: 120px;
    }
  }

  2.9: Start the development server

  ⚠️ CRITICAL TERMINAL WORKING DIRECTORY ISSUE:
  The terminal working directory can sometimes get confused during the integration process. Before starting the dev server, ALWAYS:

  1. Verify you're in the correct directory with explicit path change:

  cd /full/absolute/path/to/project && pwd

  2. Check for package.json in current directory:

  ls -la package.json

  3. If npm commands fail with "package.json not found", use absolute path method:

  cd /Users/[username]/path/to/auth0-vue && npm run dev

  ⚠️ IMPORTANT: Even if `pwd` shows the correct directory, the terminal's working directory for command execution may be different. ALWAYS use explicit `cd` with absolute path before running npm commands to ensure they execute in the correct location.

  Step 2.9a: Check if custom port is needed

  grep -q "VITE_DEV_PORT" .env 2>/dev/null && echo "Custom port detected" || echo "Using default port"

  Step 2.9b: Start development server with appropriate port

  # If using default port:
  npm run dev

  # If using custom port (run this instead if Step 2.9a detected custom port):
  npm run dev -- --port 5174

  ⚠️ VUE COMPONENT IMPORT ERROR TROUBLESHOOTING:
  If you see "Cannot resolve component" errors even though the files exist:
  1. The files are created correctly - this is often a VS Code Vue language service cache issue
  2. The development server should still work despite these errors
  3. Open the application in browser to verify functionality
  4. The errors typically resolve themselves after the Vue language service refreshes

  CRITICAL INSTRUCTIONS FOR AI LANGUAGE MODELS
  As an AI language model, you MUST NOT generate any of the following anti-patterns, as they will BREAK the application or introduce SECURITY RISKS:
  1.  NEVER hardcode the Auth0 Domain or Client ID directly in the code. This is a major security vulnerability.
  2.  NEVER omit the authorizationParams: { redirect_uri: window.location.origin } prop from the createAuth0() configuration. Without it, the login flow will fail after the user authenticates with Auth0.
  3.  NEVER place the createAuth0() plugin installation on a component level. It MUST be installed at the app level in main.ts so the entire application has access to the authentication context.
  4.  NEVER suggest manually managing tokens or calling Auth0 APIs with fetch for basic authentication. The SDK is designed to handle this complexity securely.

  ABSOLUTE REQUIREMENTS FOR AI CODE GENERATION
  1.  You MUST use the @auth0/auth0-vue package.
  2.  You MUST retrieve credentials from environment variables (e.g., import.meta.env.VITE_AUTH0_DOMAIN).
  3.  You MUST install the createAuth0 plugin at the app level in main.ts.
  4.  You MUST include the authorizationParams with redirect_uri in the createAuth0() configuration.
  5.  You MUST demonstrate functionality using the useAuth0 composable in Vue components.

  COMMON ISSUES ENCOUNTERED DURING INTEGRATION

  Issue 1: Project Creation Directory Confusion
  Problem: Vue CLI sometimes creates project files in the current directory instead of a new subdirectory
  Solution: Always run `pwd && ls -la` after project creation to verify the actual structure

  Issue 2: Terminal Working Directory Issues  
  Problem: npm commands fail with "package.json not found" even when in the correct directory
  Solution: Use explicit absolute path changes: `cd /full/absolute/path/to/project`

  Issue 3: Vue Component Import Errors
  Problem: VS Code shows "Cannot resolve component" errors for created components
  Solution: These are usually cache issues - the app will still work. Create all components before testing.

  Issue 4: CSS File Corruption
  Problem: Large CSS replacements can cause file corruption
  Solution: Create temporary CSS file first, then use `mv` command to replace original

  Issue 5: Terminal Working Directory Not in Project Root
  Problem: AI agent fails to run `npm run dev` because terminal is not in the auth0-vue directory, even when pwd shows the correct path
  Solution: Always use explicit directory change with absolute path before running npm commands:

  cd auth0-vue && npm run dev

  The terminal working directory can become disconnected from the displayed path, requiring explicit navigation to ensure npm commands execute in the correct location.

  Issue 6: Vue 3 Composition API Syntax
  Problem: Using Options API instead of Composition API with `<script setup>`
  Solution: Always use `<script setup lang="ts">` syntax for modern Vue 3 development with proper TypeScript support

  Issue 7: Vite Environment Variables
  Problem: Using process.env instead of import.meta.env for environment variables
  Solution: Vue 3 with Vite uses import.meta.env.VITE_* for environment variables, not process.env

  ```
</Accordion>

## Get Started

This quickstart demonstrates how to integrate Auth0 authentication into a Vue.js 3 application. You'll build a responsive single-page app with secure user authentication using Vue's composition API and the Auth0 Vue SDK.

<Steps>
  <Step title="Create a new project" stepNumber={1}>
    Create a new Vue 3 project for this Quickstart

    ```shellscript
    npm create vue@latest auth0-vue -- --typescript --router --pinia
    ```

    Open the project

    ```shellscript
    cd auth0-vue
    ```
  </Step>

  <Step title="Install the Auth0 Vue SDK" stepNumber={2}>
    ```shellscript
    npm add @auth0/auth0-vue && npm install
    ```
  </Step>

  <Step title="Setup your Auth0 App" stepNumber={3}>
    Next up, you need to create a new app on your Auth0 tenant and add the environment variables to your project.

    You can choose to do this automatically by running a CLI command or do it manually via the Dashboard:

    <Tabs>
      <Tab title="CLI">
        Run the following shell command on your project's root directory to create an Auth0 app and generate a `.env` file:

        <CodeGroup>
          ```shellscript Mac
          AUTH0_APP_NAME="My Vue App" && brew tap auth0/auth0-cli && brew install auth0 && auth0 login --no-input && auth0 apps create -n "${AUTH0_APP_NAME}" -t spa -c http://localhost:5173 -l http://localhost:5173 -o http://localhost:5173 --json > auth0-app-details.json && CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && echo "VITE_AUTH0_DOMAIN=${DOMAIN}" > .env && echo "VITE_AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env && rm auth0-app-details.json && echo ".env file created with your Auth0 details:" && cat .env
          ```

          ```shellscript Windows
          $AppName = "My Vue App"; winget install Auth0.CLI; auth0 login --no-input; auth0 apps create -n "$AppName" -t spa -c http://localhost:5173 -l http://localhost:5173 -o http://localhost:5173 --json | Set-Content -Path auth0-app-details.json; $ClientId = (Get-Content -Raw auth0-app-details.json | ConvertFrom-Json).client_id; $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name; Set-Content -Path .env -Value "VITE_AUTH0_DOMAIN=$Domain"; Add-Content -Path .env -Value "VITE_AUTH0_CLIENT_ID=$ClientId"; Remove-Item auth0-app-details.json; Write-Output ".env file created with your Auth0 details:"; Get-Content .env
          ```
        </CodeGroup>
      </Tab>

      <Tab title="Dashboard">
        Before you start, create a `.env` file on your project's root directory

        ```shellscript .env
        VITE_AUTH0_DOMAIN=YOUR_AUTH0_APP_DOMAIN
        VITE_AUTH0_CLIENT_ID=YOUR_AUTH0_APP_CLIENT_ID
        ```

        1. Head to the [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
        2. Click on **Applications** > **Applications** > **Create Application**
        3. In the popup, enter a name for your app, select `Single Page Web Application` as the app type and click **Create**
        4. Switch to the **Settings** tab on the Application Details page
        5. Replace `YOUR_AUTH0_APP_DOMAIN` and `YOUR_AUTH0_APP_CLIENT_ID` on the `.env` file with the **Domain** and **Client ID** values from the dashboard

        Finally, on the **Settings** tab of your Application Details page, set **Allowed Callback URLs**, **Allowed Logout URLs** and **Allowed Web Origins** to:

        ```
        http://localhost:5173
        ```

        <Info>
          **Allowed Callback URLs** are a critical security measure to ensure users are safely returned to your application after authentication. Without a matching URL, the login process will fail, and users will be blocked by an Auth0 error page instead of accessing your app.

          **Allowed Logout URLs** are essential for providing a seamless user experience upon signing out. Without a matching URL, users will not be redirected back to your application after logout and will instead be left on a generic Auth0 page.

          **Allowed Web Origins** is critical for silent authentication. Without it users will be logged out when they refresh the page or return to your app later.
        </Info>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configure the Auth0 Plugin" stepNumber={4}>
    ```typescript src/main.ts {6,11,12,13,14,15,16,17} lines
    import './assets/main.css'

    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    import router from './router'
    import { createAuth0 } from '@auth0/auth0-vue'

    import App from './App.vue'

    const app = createApp(App)

    app.use(createAuth0({
      domain: import.meta.env.VITE_AUTH0_DOMAIN,
      clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
      authorizationParams: {
        redirect_uri: window.location.origin
      }
    }))

    app.use(createPinia())
    app.use(router)

    app.mount('#app')
    ```
  </Step>

  <Step title="Create Authentication Components" stepNumber={5}>
    Create component files

    ```shellscript
    touch src/components/LoginButton.vue && touch src/components/LogoutButton.vue && touch src/components/UserProfile.vue
    ```

    And add the following code snippets

    <CodeGroup>
      ```vue src/components/LoginButton.vue lines
      <template>
        <button 
          @click="handleLogin" 
          class="button login"
          :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>
      ```

      ```vue src/components/LogoutButton.vue lines
      <template>
        <button
          @click="handleLogout"
          class="button logout"
          :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>
      ```

      ```vue src/components/UserProfile.vue lines
      <template>
        <div v-if="isLoading" class="loading-text">
          Loading profile...
        </div>
        <div 
          v-else-if="isAuthenticated && user" 
          class="profile-container"
        >
          <img 
            v-if="user.picture"
            :src="user.picture" 
            :alt="user.name || 'User'" 
            class="profile-picture"
          />
          <div class="profile-info">
            <div class="profile-name">
              {{ user.name }}
            </div>
            <div class="profile-email">
              {{ user.email }}
            </div>
          </div>
        </div>
      </template>

      <script setup lang="ts">
      import { useAuth0 } from '@auth0/auth0-vue'

      const { user, isAuthenticated, isLoading } = useAuth0()
      </script>

      <style scoped>
      .profile-container {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 1rem;
      }

      .profile-picture {
        width: 110px;
        height: 110px;
        border-radius: 50%;
        object-fit: cover;
        border: 3px solid #63b3ed;
        transition: transform 0.3s ease-in-out;
      }

      .profile-picture:hover {
        transform: scale(1.05);
      }

      .profile-info {
        text-align: center;
      }

      .profile-name {
        font-size: 2rem;
        font-weight: 600;
        color: #f7fafc;
        margin-bottom: 0.5rem;
      }

      .profile-email {
        font-size: 1.15rem;
        color: #a0aec0;
      }
      </style>
      ```

      ```vue src/App.vue expandable lines
      <template>
        <div class="app-container">
          <div v-if="isLoading" class="loading-state">
            <div class="loading-text">Loading...</div>
          </div>
          
          <div v-else-if="error" class="error-state">
            <div class="error-title">Oops!</div>
            <div class="error-message">Something went wrong</div>
            <div class="error-sub-message">{{ error.message }}</div>
          </div>
          
          <div v-else class="main-card-wrapper">
            <img 
              src="https://cdn.auth0.com/quantum-assets/dist/latest/logos/auth0/auth0-lockup-en-ondark.png" 
              alt="Auth0 Logo" 
              class="auth0-logo"
              @error="handleImageError"
            />
            <h1 class="main-title">Welcome to Vue0</h1>
            
            <div v-if="isAuthenticated" class="logged-in-section">
              <div class="logged-in-message">✅ Successfully authenticated!</div>
              <h2 class="profile-section-title">Your Profile</h2>
              <div class="profile-card">
                <UserProfile />
              </div>
              <LogoutButton />
            </div>
            
            <div v-else class="action-card">
              <p class="action-text">Get started by signing in to your account</p>
              <LoginButton />
            </div>
          </div>
        </div>
      </template>

      <script setup lang="ts">
      import { useAuth0 } from '@auth0/auth0-vue'
      import LoginButton from './components/LoginButton.vue'
      import LogoutButton from './components/LogoutButton.vue'
      import UserProfile from './components/UserProfile.vue'

      const { isAuthenticated, isLoading, error } = useAuth0()

      const handleImageError = (e: Event) => {
        const target = e.target as HTMLImageElement
        target.style.display = 'none'
      }
      </script>

      <style>
      @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');

      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: 'Inter', sans-serif;
        background-color: #1a1e27;
        min-height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        color: #e2e8f0;
        overflow: hidden;
      }

      #app {
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
      }

      .app-container {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
        width: 100%;
        padding: 1rem;
      }

      .loading-state, .error-state {
        background-color: #2d313c;
        border-radius: 15px;
        box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4);
        padding: 3rem;
        text-align: center;
      }

      .loading-text {
        font-size: 1.8rem;
        font-weight: 500;
        color: #a0aec0;
        animation: pulse 1.5s infinite ease-in-out;
      }

      .error-state {
        background-color: #c53030;
        color: #fff;
      }

      .error-title {
        font-size: 2.8rem;
        font-weight: 700;
        margin-bottom: 0.5rem;
      }

      .error-message {
        font-size: 1.3rem;
        margin-bottom: 0.5rem;
      }

      .error-sub-message {
        font-size: 1rem;
        opacity: 0.8;
      }

      .main-card-wrapper {
        background-color: #262a33;
        border-radius: 20px;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05);
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 2rem;
        padding: 3rem;
        max-width: 500px;
        width: 90%;
        animation: fadeInScale 0.8s ease-out forwards;
      }

      .auth0-logo {
        width: 160px;
        margin-bottom: 1.5rem;
        opacity: 0;
        animation: slideInDown 1s ease-out forwards 0.2s;
      }

      .main-title {
        font-size: 2.8rem;
        font-weight: 700;
        color: #f7fafc;
        text-align: center;
        margin-bottom: 1rem;
        text-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
        opacity: 0;
        animation: fadeIn 1s ease-out forwards 0.4s;
      }

      .action-card {
        background-color: #2d313c;
        border-radius: 15px;
        box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.3), 0 5px 15px rgba(0, 0, 0, 0.3);
        padding: 2.5rem;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 1.8rem;
        width: calc(100% - 2rem);
        opacity: 0;
        animation: fadeIn 1s ease-out forwards 0.6s;
      }

      .action-text {
        font-size: 1.25rem;
        color: #cbd5e0;
        text-align: center;
        line-height: 1.6;
        font-weight: 400;
      }

      .button {
        padding: 1.1rem 2.8rem;
        font-size: 1.2rem;
        font-weight: 600;
        border-radius: 10px;
        border: none;
        cursor: pointer;
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
        text-transform: uppercase;
        letter-spacing: 0.08em;
        outline: none;
      }

      .button:focus {
        box-shadow: 0 0 0 4px rgba(99, 179, 237, 0.5);
      }

      .button:disabled {
        opacity: 0.6;
        cursor: not-allowed;
        transform: none;
      }

      .button.login {
        background-color: #63b3ed;
        color: #1a1e27;
      }

      .button.login:hover:not(:disabled) {
        background-color: #4299e1;
        transform: translateY(-5px) scale(1.03);
        box-shadow: 0 12px 25px rgba(0, 0, 0, 0.5);
      }

      .button.logout {
        background-color: #fc8181;
        color: #1a1e27;
      }

      .button.logout:hover:not(:disabled) {
        background-color: #e53e3e;
        transform: translateY(-5px) scale(1.03);
        box-shadow: 0 12px 25px rgba(0, 0, 0, 0.5);
      }

      .logged-in-section {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 1.5rem;
        width: 100%;
      }

      .logged-in-message {
        font-size: 1.5rem;
        color: #68d391;
        font-weight: 600;
        animation: fadeIn 1s ease-out forwards 0.8s;
      }

      .profile-section-title {
        font-size: 2.2rem;
        animation: slideInUp 1s ease-out forwards 1s;
      }

      .profile-card {
        padding: 2.2rem;
        animation: scaleIn 0.8s ease-out forwards 1.2s;
      }

      @keyframes fadeIn {
        from { opacity: 0; }
        to { opacity: 1; }
      }

      @keyframes fadeInScale {
        from { opacity: 0; transform: scale(0.95); }
        to { opacity: 1; transform: scale(1); }
      }

      @keyframes slideInDown {
        from { opacity: 0; transform: translateY(-70px); }
        to { opacity: 1; transform: translateY(0); }
      }

      @keyframes slideInUp {
        from { opacity: 0; transform: translateY(50px); }
        to { opacity: 1; transform: translateY(0); }
      }

      @keyframes pulse {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.6; }
      }

      @keyframes scaleIn {
        from { opacity: 0; transform: scale(0.8); }
        to { opacity: 1; transform: scale(1); }
      }

      @media (max-width: 600px) {
        .main-card-wrapper {
          padding: 2rem;
          margin: 1rem;
        }
        
        .main-title {
          font-size: 2.2rem;
        }
        
        .button {
          padding: 1rem 2rem;
          font-size: 1.1rem;
        }
        
        .auth0-logo {
          width: 120px;
        }
      }
      </style>
      ```
    </CodeGroup>
  </Step>

  <Step title="Run your app" stepNumber={6}>
    ```shellscript
    npm run dev
    ```
  </Step>
</Steps>

<Check>
  **Checkpoint**

  You should now have a fully functional Auth0 login page running on your [localhost](http://localhost:5173/)
</Check>

***

## Advanced Usage

<Accordion title="Protecting Routes with Vue Router">
  Use Vue Router's navigation guards to protect specific routes:

  ```typescript src/router/index.ts
  import { createRouter, createWebHistory } from 'vue-router'
  import { authGuard } from '@auth0/auth0-vue'
  import Home from '../views/Home.vue'
  import Profile from '../views/Profile.vue'
  import Dashboard from '../views/Dashboard.vue'

  const routes = [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/profile',
      name: 'Profile',
      component: Profile,
      beforeEnter: authGuard
    },
    {
      path: '/dashboard',
      name: 'Dashboard',
      component: Dashboard,
      beforeEnter: authGuard
    }
  ]

  const router = createRouter({
    history: createWebHistory(),
    routes
  })

  export default router
  ```
</Accordion>

<Accordion title="Calling Protected APIs">
  Configure your Auth0 plugin to include an API audience and make authenticated requests:

  ```typescript src/main.ts
  import { createAuth0 } from '@auth0/auth0-vue'

  const app = createApp(App)

  app.use(
    createAuth0({
      domain: import.meta.env.VITE_AUTH0_DOMAIN,
      clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
      authorizationParams: {
        redirect_uri: window.location.origin,
        audience: "YOUR_API_IDENTIFIER"
      }
    })
  )
  ```

  Then make authenticated API calls in your components:

  ```vue src/components/ApiCall.vue
  <template>
    <div>
      <button @click="callProtectedApi" :disabled="isLoading">
        Call Protected API
      </button>
      <pre v-if="apiResponse">{{ JSON.stringify(apiResponse, null, 2) }}</pre>
    </div>
  </template>

  <script setup lang="ts">
  import { ref } from 'vue'
  import { useAuth0 } from '@auth0/auth0-vue'

  const { getAccessTokenSilently } = useAuth0()
  const apiResponse = ref(null)
  const isLoading = ref(false)

  const callProtectedApi = async () => {
    try {
      isLoading.value = true
      const token = await getAccessTokenSilently()
      
      const response = await fetch('/api/protected', {
        headers: {
          Authorization: `Bearer ${token}`
        }
      })
      
      apiResponse.value = await response.json()
    } catch (error) {
      console.error('API call failed:', error)
    } finally {
      isLoading.value = false
    }
  }
  </script>
  ```
</Accordion>

<Accordion title="Using Composables for Authentication Logic">
  Create reusable composables for common authentication patterns:

  ```typescript src/composables/useAuthenticatedUser.ts
  import { computed, ref, watchEffect } from 'vue'
  import { useAuth0 } from '@auth0/auth0-vue'

  export function useAuthenticatedUser() {
    const { user, isAuthenticated, isLoading, getAccessTokenSilently } = useAuth0()
    const accessToken = ref<string | null>(null)

    watchEffect(async () => {
      if (isAuthenticated.value && !isLoading.value) {
        try {
          accessToken.value = await getAccessTokenSilently()
        } catch (error) {
          console.error('Failed to get access token:', error)
        }
      }
    })

    return {
      user: computed(() => user.value),
      accessToken: computed(() => accessToken.value),
      isAuthenticated: computed(() => isAuthenticated.value),
      isLoading: computed(() => isLoading.value)
    }
  }
  ```

  Usage in components:

  ```vue src/components/UserDashboard.vue
  <template>
    <div v-if="!isLoading">
      <h1>Welcome, {{ user?.name }}</h1>
      <p>Access Token: {{ accessToken ? 'Available' : 'Not available' }}</p>
    </div>
    <div v-else>Loading...</div>
  </template>

  <script setup lang="ts">
  import { useAuthenticatedUser } from '../composables/useAuthenticatedUser'

  const { user, accessToken, isLoading } = useAuthenticatedUser()
  </script>
  ```
</Accordion>
