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

# Add Login to Your Nuxt.js Application

> This guide demonstrates how to integrate Auth0, add authentication, and display user profile information in a Single-Page Application (SPA) that uses Nuxt.js, using the Auth0 Nuxt.js SDK.

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

<HowToSchema />

<Callout icon="pencil" color="#FFC107" iconType="solid">
  This Quickstart is currently in **Beta**. We'd love to hear your feedback!
</Callout>

<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 https://github.com/auth0/agent-skills --skill auth0-nuxt
  ```

  **Then ask your AI assistant:**

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

  Your AI assistant will automatically create your Auth0 application, fetch credentials, install `@auth0/auth0-nuxt`, configure the module, and set up 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+
  * **[jq](https://jqlang.org/)** - Required for Auth0 CLI setup

  **Nuxt Version Compatibility:** This quickstart works with **Nuxt 3.x** out of the box. For **Nuxt 4.x**, ensure you're using Nuxt 4.2+.
</Note>

## Get Started

This quickstart demonstrates how to add Auth0 authentication to a Nuxt.js application. You'll build a secure single-page app with login, logout, and user profile features using the Auth0 Nuxt SDK.

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

    ```shellscript theme={null}
    npx nuxi@latest init auth0-nuxt-app
    ```

    Open the project

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

  <Step title="Install the Auth0 Nuxt SDK" stepNumber={2}>
    ```shellscript theme={null}
    npm add @auth0/auth0-nuxt && 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 set up your Auth0 app 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 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 regular --framework nuxt --port 3000 --name "My Nuxt App"
          ```

          ```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 regular --framework nuxt --port 3000 --name "My Nuxt App"
          ```
        </CodeGroup>

        <Note>
          This command will:

          1. Check if you're authenticated (and prompt for login if needed)
          2. Create an Auth0 Regular Web Application configured for `http://localhost:3000`
          3. Generate a `.env` file with `NUXT_AUTH0_DOMAIN`, `NUXT_AUTH0_CLIENT_ID`, `NUXT_AUTH0_CLIENT_SECRET`, `NUXT_AUTH0_SESSION_SECRET`, and `NUXT_AUTH0_APP_BASE_URL`
        </Note>
      </Tab>

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

        ```shellscript .env theme={null}
        NUXT_AUTH0_DOMAIN=YOUR_AUTH0_APP_DOMAIN
        NUXT_AUTH0_CLIENT_ID=YOUR_AUTH0_APP_CLIENT_ID
        NUXT_AUTH0_CLIENT_SECRET=YOUR_AUTH0_APP_CLIENT_SECRET
        NUXT_AUTH0_SESSION_SECRET=YOUR_SESSION_SECRET
        NUXT_AUTH0_APP_BASE_URL=http://localhost:3000
        ```

        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 `Regular Web Application` as the app type and click **Create**
        4. Switch to the **Settings** tab on the Application Details page
        5. Replace the values in the `.env` file with the **Domain**, **Client ID**, and **Client Secret** values from the dashboard
        6. Generate a session secret by running: `openssl rand -hex 64` and use it for `NUXT_AUTH0_SESSION_SECRET`

        Finally, on the **Settings** tab of your Application Details page, configure the following URLs:

        **Allowed Callback URLs:**

        ```
        http://localhost:3000/auth/callback
        ```

        **Allowed Logout URLs:**

        ```
        http://localhost:3000
        ```

        **Allowed Web Origins:**

        ```
        http://localhost:3000
        ```

        <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 Nuxt module" stepNumber={4}>
    ```typescript nuxt.config.ts {3,4,5,6,7,8,9,10,11,12,13} lines theme={null}
    export default defineNuxtConfig({
      devtools: { enabled: true },
      modules: ['@auth0/auth0-nuxt'],
      runtimeConfig: {
        auth0: {
          domain: process.env.NUXT_AUTH0_DOMAIN,
          clientId: process.env.NUXT_AUTH0_CLIENT_ID,
          clientSecret: process.env.NUXT_AUTH0_CLIENT_SECRET,
          sessionSecret: process.env.NUXT_AUTH0_SESSION_SECRET,
          appBaseUrl: process.env.NUXT_AUTH0_APP_BASE_URL,
        },
      },
    })
    ```
  </Step>

  <Step title="Create Login, Logout and Profile Components" stepNumber={5}>
    Create files

    ```shellscript theme={null}
    mkdir -p app/components && touch app/components/LoginButton.vue && touch app/components/LogoutButton.vue && touch app/components/UserProfile.vue
    ```

    And add the following code snippets

    <CodeGroup>
      ```vue app/components/LoginButton.vue expandable lines theme={null}
      <template>
       <a 
          href="/auth/login" 
          class="button login"
        >
          Log In
        </a>
      </template>

      <style scoped>
      .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.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:disabled {
        opacity: 0.7;
        cursor: not-allowed;
        transform: none;
      }
      </style>
      ```

      ```vue app/components/LogoutButton.vue expandable lines theme={null}
      <template>
       <a
          href="/auth/logout"
          class="button logout"
        >
          Log Out
        </a>
      </template>

      <style scoped>
      .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.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);
      }

      .button:disabled {
        opacity: 0.7;
        cursor: not-allowed;
        transform: none;
      }
      </style>
      ```

      ```vue app/components/UserProfile.vue expandable lines theme={null}
      <template>
        <div v-if="pending" class="loading-text">
          Loading profile...
        </div>
        <div v-else-if="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">
      const user = useUser()
      const pending = ref(false)

      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
      }

      // Simulate loading state for better UX
      onMounted(() => {
        pending.value = true
        nextTick(() => {
          pending.value = false
        })
      })
      </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;
      }

      .loading-text {
        font-size: 1.8rem;
        font-weight: 500;
        color: #a0aec0;
        animation: pulse 1.5s infinite ease-in-out;
      }

      @keyframes pulse {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.6; }
      }
      </style>
      ```

      ```vue app.vue expandable lines theme={null}
      <template>
        <div class="app-container">
          <div 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"
            />
            <h1 class="main-title">Welcome to Auth0 Nuxt</h1>

            <div v-if="user" 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">
      const user = useUser()
      </script>

      <style>
      @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');

      body {
        margin: 0;
        font-family: 'Inter', sans-serif;
        background-color: #1a1e27;
        min-height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        color: #e2e8f0;
        overflow: hidden;
      }

      .app-container {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
        width: 100%;
        padding: 1rem;
        box-sizing: border-box;
      }

      .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;
      }

      .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;
        }

        .auth0-logo {
          width: 120px;
        }
      }
      </style>
      ```
    </CodeGroup>
  </Step>

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

<Check>
  **Checkpoint**

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