このクイックスタートは現在 Beta 段階です。ぜひフィードバックをお寄せください。
AI を使って Auth0 を統合する
AI を使って Auth0 を統合する
Claude Code、Cursor、GitHub Copilot などの AI コーディングアシスタントを使用している場合は、agent skills を使うことで、数分で Auth0 認証を自動的に追加できます。インストール:次に、AI アシスタントに次のように依頼します。AI アシスタントが、Auth0 アプリケーションの作成、認証情報の取得、
npx skills add https://github.com/auth0/agent-skills --skill auth0-nuxt
Add Auth0 authentication to my Nuxt app
@auth0/auth0-nuxt のインストール、モジュールの設定、ルートのセットアップを自動的に行います。agent skills の完全なドキュメント →はじめに
1
新規プロジェクトを作成する
このクイックスタート用に新しいNuxtプロジェクトを作成するプロジェクトを開く
npx nuxi@latest init auth0-nuxt-app
cd auth0-nuxt-app
2
Auth0 Nuxt SDKをインストールする
npm add @auth0/auth0-nuxt && npm install
3
Auth0 アプリを設定する
次に、Auth0テナントで新しいアプリを作成し、プロジェクトに環境変数を追加します。CLIコマンドを実行して自動的に行う方法と、Dashboardから手動で行う方法のいずれかを選択できます。
- CLI
- ダッシュボード
Auth0 アプリを作成し、
.env ファイルを生成するには、プロジェクトのルートディレクトリで次のシェルコマンドを実行します。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
始める前に、プロジェクトのルートディレクトリに 許可されているログアウト URL:許可された Web オリジン:
.env ファイルを作成してください.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
- Auth0 Dashboard に移動します
- Applications > Applications > Create Application をクリックします
- ポップアップでアプリの名前を入力し、アプリケーションタイプとして
Regular Web Applicationを選択して Create をクリックします - Application Details ページで Settings タブに切り替えます
.envファイルの値を、ダッシュボードに表示される ドメイン、クライアントID、クライアントシークレット の値に置き換えますopenssl rand -hex 64を実行してセッションシークレットを生成し、それをNUXT_AUTH0_SESSION_SECRETに使用します
http://localhost:3000/auth/callback
http://localhost:3000
http://localhost:3000
Allowed Callback URLs は、認証後にユーザーを安全にアプリケーションへ戻すための重要なセキュリティ対策です。一致する URL がない場合、ログイン処理は失敗し、ユーザーはアプリにアクセスできず、代わりに Auth0 のエラーページが表示されます。Allowed Logout URLs は、サインアウト時にシームレスなユーザー体験を提供するうえで不可欠です。一致する URL がない場合、ログアウト後にユーザーはアプリケーションへリダイレクトされず、代わりに汎用的な Auth0 ページに留まることになります。Allowed Web Origins は、サイレント認証に不可欠です。これがないと、ユーザーはページを更新したときや、後でアプリに戻ったときにログアウトされます。
4
Auth0 Nuxt モジュールを設定する
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
ログイン、ログアウト、ユーザープロファイル用のコンポーネントを作成する
ファイルを作成する次のコードスニペットを追加してください
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
}
// 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
アプリを実行する
npm run dev
チェックポイントこれで、localhost で Auth0 のログインページが問題なく動作しているはずです