> ## Documentation Index
> Fetch the complete documentation index at: https://docs-dev-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Login to Your Vue Application

> This guide demonstrates how to integrate Auth0, add authentication, and display user profile information in any Vue application using the Auth0 Vue SDK.

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

export const CreateInteractiveApp = ({placeholderText = 'Auth0', appType = 'regular_web', allowedCallbackUrls = ['localhost:3000'], allowedLogoutUrls = ['localhost:3000'], allowedOriginUrls = ['localhost:3000']}) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  const [displayForm, setDisplayForm] = useState(true);
  useEffect(() => {
    const init = () => setStoreReady(true);
    if (window.rootStore) {
      window.rootStore.clientStore.setSelectedClient(null);
      window.rootStore.clientStore.setSelectedClientSecret(undefined);
      init();
    } else {
      window.addEventListener('adu:storeReady', init);
    }
    return () => {
      window.removeEventListener('adu:storeReady', init);
    };
  }, []);
  useEffect(() => {
    if (!storeReady) return;
    const disposer = autorun(() => {
      const rootStore = window.rootStore;
      setIsAuthenticated(rootStore.sessionStore.isAuthenticated);
    });
    return () => {
      disposer();
    };
  }, [storeReady]);
  if (!storeReady || typeof window === 'undefined' || !displayForm) {
    return <></>;
  }
  const login = () => {
    const baseUrl = window.rootStore.config.apiBaseUrl;
    const returnTo = encodeURIComponent(window.location.href);
    window.location.href = `${baseUrl}/auth/user/login?returnTo=${returnTo}`;
  };
  const Card = ({className = '', children}) => {
    return <div className={`
          flex border rounded-2xl
          border-gray-950/10 dark:border-white/10
          py-3.5 px-4 gap-2
          text-sm text-gray-900 dark:text-gray-200
          ${className}
        `}>
        {children}
      </div>;
  };
  const Button = ({children, ...props}) => {
    return <button className="bg-[--button-primary] text-[--foreground-inverse] px-[1.125rem] py-1.5 rounded-lg font-medium" {...props}>
        {children}
      </button>;
  };
  const CreateApplicationForm = () => {
    const [name, setName] = useState('');
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState('');
    const handleSubmit = async () => {
      if (!name.trim()) {
        setError('Application name is required');
        return;
      }
      setIsLoading(true);
      setError(null);
      try {
        await window.rootStore.clientStore.createClient({
          name: name.trim(),
          app_type: appType,
          callbacks: allowedCallbackUrls,
          allowed_logout_urls: allowedLogoutUrls,
          web_origins: allowedOriginUrls,
          client_metadata: {
            created_by: 'quickstart-docs-app-creation-component'
          }
        });
        setDisplayForm(false);
      } catch (err) {
        console.error('Error creating client:', err);
        const errorMessage = err instanceof Error ? err.message : 'Failed to create application';
        setError(errorMessage);
      } finally {
        setIsLoading(false);
      }
    };
    return <Card className="flex-col items-start p-4 gap-3.75">
        <span className="font-medium text-gray-900 dark:text-gray-200">
          Create Auth0 App
        </span>
        <div className="w-full flex gap-2">
          <input id="app-name" name={name} className="
              w-full max-w-[448px] h-11 py-2 px-4 
              border rounded-lg border-gray-950/10 dark:border-white/10 
              text-gray-900 dark:text-gray-200
              focus:outline-none dark:focus:outline-none
            " placeholder={`My ${placeholderText} App`} value={name} onChange={e => setName(e.target.value)} />
          <Button onClick={handleSubmit}>
            {isLoading ? 'Creating...' : 'Create'}
          </Button>
        </div>
        {error && <p className="text-red-500">{error}</p>}
      </Card>;
  };
  const SignInForm = () => {
    return <Card className="items-center">
        <Button onClick={login}>Log in</Button> <span>to create the app</span>
      </Card>;
  };
  return isAuthenticated ? <CreateApplicationForm /> : <SignInForm />;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

<HowToSchema />

<Accordion title="Use AI to integrate Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using [agent skills](https://agentskills.io/home).

  **Install:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-vue
  ```

  **Then ask your AI assistant:**

  ```text theme={null}
  Add Auth0 authentication to my Vue app
  ```

  Your AI assistant will automatically create your Auth0 application, fetch credentials, install `@auth0/auth0-vue`, create the authentication plugin, and configure your routes. [Full agent skills documentation →](/docs/quickstart/agent-skills)
</Accordion>

<Note>
  **Prerequisites:** Before you begin, ensure you have the following installed:

  * **[Node.js](https://nodejs.org/en/download)** 20 LTS or newer
  * **[npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)** 10+ or **[yarn](https://classic.yarnpkg.com/lang/en/docs/install/)** 1.22+ or **[pnpm](https://pnpm.io/installation)** 8+

  Verify installation: `node --version && npm --version`

  **Vue Version Compatibility:** This quickstart works with **Vue 3.x** and newer.
</Note>

## 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.

export const localEnvSnippet = `VITE_AUTH0_DOMAIN={yourDomain}
VITE_AUTH0_CLIENT_ID={yourClientId}`;

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

    ```shellscript theme={null}
    npm create vue@latest auth0-vue -- --typescript --router --pinia
    ```

    Open the project

    ```shellscript theme={null}
    cd auth0-vue
    ```
  </Step>

  <Step title="Install the Auth0 Vue SDK" stepNumber={2}>
    ```shellscript theme={null}
    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 have three options to set up your Auth0 app: use the Quick Setup tool (recommended), run a CLI command, or configure manually via the Dashboard:

    <Tabs>
      <Tab title="Quick Setup (recommended)">
        Create an Auth0 App and copy the pre-filled `.env` file with the right configuration values.

        <CreateInteractiveApp placeholderText="Vue" appType="spa" allowedCallbackUrls={["http://localhost:5173"]} allowedLogoutUrls={["http://localhost:5173"]} allowedOriginUrls={["http://localhost:5173"]} />

        <AuthCodeBlock children={localEnvSnippet} language="shellscript" filename=".env" />
      </Tab>

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

        <CodeGroup>
          ```shellscript Mac theme={null}
          # Install Auth0 CLI (if not already installed)
          brew tap auth0/auth0-cli && brew install auth0

          # Set up Auth0 app and generate .env file
          auth0 qs setup --app --type spa --framework vue --build-tool vite --name "My App" --port 5173
          ```

          ```powershell Windows theme={null}
          # Install Auth0 CLI (if not already installed)
          scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
          scoop install auth0

          # Set up Auth0 app and generate .env file
          auth0 qs setup --app --type spa --framework vue --build-tool vite --name "My App" --port 5173
          ```
        </CodeGroup>

        <Note>
          This command will:

          1. Check if you're authenticated (and prompt for login if needed)
          2. Create an Auth0 Single Page Application configured for `http://localhost:5173`
          3. Generate a `.env` file with `VITE_AUTH0_DOMAIN` and `VITE_AUTH0_CLIENT_ID`
        </Note>
      </Tab>

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

        ```shellscript .env theme={null}
        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, configure the following URLs:

        **Allowed Callback URLs:**

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

        **Allowed Logout URLs:**

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

        **Allowed Web Origins:**

        ```
        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>

    <Tip>
      Verify your `.env` file exists: `cat .env` (Mac/Linux) or `type .env` (Windows)
    </Tip>
  </Step>

  <Step title="Configure the Auth0 Plugin" stepNumber={4}>
    ```typescript src/main.ts {6,11,12,13,14,15,16,17} lines theme={null}
    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

    <CodeGroup>
      ```shellscript Mac/Linux theme={null}
      touch src/components/LoginButton.vue && touch src/components/LogoutButton.vue && touch src/components/UserProfile.vue
      ```

      ```powershell Windows theme={null}
      New-Item -ItemType File -Path src/components/LoginButton.vue
      New-Item -ItemType File -Path src/components/LogoutButton.vue
      New-Item -ItemType File -Path src/components/UserProfile.vue
      ```
    </CodeGroup>

    And add the following code snippets

    <AuthCodeGroup>
      ```vue src/components/LoginButton.vue lines theme={null}
      <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 theme={null}
      <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 expandable lines theme={null}
      <template>
        <div v-if="isLoading" class="loading-text">
          Loading profile...
        </div>
        <div 
          v-else-if="isAuthenticated && user" 
          class="profile-container"
        >
          <img 
            :src="user.picture || placeholderImage" 
            :alt="user.name || 'User'" 
            class="profile-picture"
            @error="handleImageError"
          />
          <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()

      const placeholderImage = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='50' fill='%2363b3ed'/%3E%3Cpath d='M50 45c7.5 0 13.64-6.14 13.64-13.64S57.5 17.72 50 17.72s-13.64 6.14-13.64 13.64S42.5 45 50 45zm0 6.82c-9.09 0-27.28 4.56-27.28 13.64v3.41c0 1.88 1.53 3.41 3.41 3.41h47.74c1.88 0 3.41-1.53 3.41-3.41v-3.41c0-9.08-18.19-13.64-27.28-13.64z' fill='%23fff'/%3E%3C/svg%3E`

      function handleImageError(e: Event) {
        const target = e.target as HTMLImageElement
        target.src = placeholderImage
      }
      </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 theme={null}
      <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>
      ```
    </AuthCodeGroup>
  </Step>

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

    <Info>
      If port 5173 is in use, run: `npm run dev -- --port 5174` and update your Auth0 app's callback URLs to `http://localhost:5174`
    </Info>
  </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 theme={null}
  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 theme={null}
  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 theme={null}
  <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 theme={null}
  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 theme={null}
  <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>
