This Quickstart is currently in Beta. We’d love to hear your feedback!
Use AI to integrate Auth0
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:Then ask your AI assistant:Your AI assistant will automatically create your Auth0 application, fetch credentials, install
npx skills add https://github.com/auth0/agent-skills --skill auth0-nuxt
Add Auth0 authentication to my Nuxt app
@auth0/auth0-nuxt, configure the module, and set up your routes. Full agent skills documentation →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.Create a new project
Create a new Nuxt project for this QuickstartOpen the project
npx nuxi@latest init auth0-nuxt-app
cd auth0-nuxt-app
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 do this automatically by running a CLI command or do it manually via the Dashboard:
- CLI
- Dashboard
Run the following shell command on your project’s root directory to create an Auth0 app and generate a
.env file:AUTH0_APP_NAME="My Nuxt App" && brew tap auth0/auth0-cli && brew install auth0 && auth0 login --no-input && auth0 apps create -n "${AUTH0_APP_NAME}" -t regular -c http://localhost:3000/auth/callback -l http://localhost:3000 -o http://localhost:3000 --reveal-secrets --json > auth0-app-details.json && CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) && CLIENT_SECRET=$(jq -r '.client_secret' auth0-app-details.json) && DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') && echo "NUXT_AUTH0_DOMAIN=${DOMAIN}" > .env && echo "NUXT_AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env && echo "NUXT_AUTH0_CLIENT_SECRET=${CLIENT_SECRET}" >> .env && echo "NUXT_AUTH0_SESSION_SECRET=$(openssl rand -hex 64)" >> .env && echo "NUXT_AUTH0_APP_BASE_URL=http://localhost:3000" >> .env && rm auth0-app-details.json && echo ".env file created with your Auth0 details:" && cat .env
$AppName = "My Nuxt App"; winget install Auth0.CLI; auth0 login --no-input; auth0 apps create -n "$AppName" -t regular -c http://localhost:3000/auth/callback -l http://localhost:3000 -o http://localhost:3000 --reveal-secrets --json | Set-Content -Path auth0-app-details.json; $ClientId = (Get-Content -Raw auth0-app-details.json | ConvertFrom-Json).client_id; $ClientSecret = (Get-Content -Raw auth0-app-details.json | ConvertFrom-Json).client_secret; $Domain = (auth0 tenants list --json | ConvertFrom-Json | Where-Object { $_.active -eq $true }).name; Set-Content -Path .env -Value "NUXT_AUTH0_DOMAIN=$Domain"; Add-Content -Path .env -Value "NUXT_AUTH0_CLIENT_ID=$ClientId"; Add-Content -Path .env -Value "NUXT_AUTH0_CLIENT_SECRET=$ClientSecret"; $SessionSecret = -join ((1..128) | ForEach {'{0:X}' -f (Get-Random -Max 16)}); Add-Content -Path .env -Value "NUXT_AUTH0_SESSION_SECRET=$SessionSecret"; Add-Content -Path .env -Value "NUXT_AUTH0_APP_BASE_URL=http://localhost:3000"; Remove-Item auth0-app-details.json; Write-Output ".env file created with your Auth0 details:"; Get-Content .env
Before you start, create a Allowed Logout URLs:Allowed Web Origins:
.env file on your project’s root directory.env
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
- Head to the Auth0 Dashboard
- Click on Applications > Applications > Create Application
- In the popup, enter a name for your app, select
Regular Web Applicationas the app type and click Create - Switch to the Settings tab on the Application Details page
- Replace the values in the
.envfile with the Domain, Client ID, and Client Secret values from the dashboard - Generate a session secret by running:
openssl rand -hex 64and use it forNUXT_AUTH0_SESSION_SECRET
http://localhost:3000/auth/callback
http://localhost:3000
http://localhost:3000
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.
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.
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,
},
},
})
Create Login, Logout and Profile Components
Create filesAnd add the following code snippets
mkdir -p app/components && touch app/components/LoginButton.vue && touch app/components/LogoutButton.vue && touch app/components/UserProfile.vue
<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>
CheckpointYou should now have a fully functional Auth0 login page running on your localhost