Skip to main content
This Quickstart is currently in Beta. We’d love to hear your feedback!

Use AI to integrate Auth0

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.Install:
npx skills add https://github.com/auth0/agent-skills --skill auth0-nuxt
Then ask your AI assistant:
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 →
Prerequisites: Before you begin, ensure you have the following installed: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+.

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

Create a new project

Create a new Nuxt project for this Quickstart
npx nuxi@latest init auth0-nuxt-app
Open the project
cd auth0-nuxt-app
2

Install the Auth0 Nuxt SDK

npm add @auth0/auth0-nuxt && npm install
3

Setup your Auth0 App

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:
Run the following shell command on your project’s root directory to create an Auth0 app and generate a .env file:
# 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"
# 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"
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
4

Configure the Auth0 Nuxt module

nuxt.config.ts
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,
    },
  },
})
5

Create Login, Logout and Profile Components

Create files
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
<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>
<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>
<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>
<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>
6

Run your app

npm run dev
CheckpointYou should now have a fully functional Auth0 login page running on your localhost