brand logo

ドキュメント

セッションと認証

認証はウェブアプリで非常に一般的な要件です。このレシピでは、Nuxtアプリで基本的なユーザー登録と認証を実装する方法を紹介します。

はじめに

このレシピでは、Nuxt Auth Utilsを使用してフルスタックのNuxtアプリで認証を設定します。このモジュールは、クライアントサイドとサーバーサイドのセッションデータを管理するための便利なユーティリティを提供します。

このモジュールはセッションデータを保存するためにセキュアでシールドされたクッキーを使用するため、セッションデータを保存するためのデータベースを設定する必要はありません。

nuxt-auth-utilsのインストール

nuxt CLIを使用してnuxt-auth-utilsモジュールをインストールします。

Terminal
npx nuxt module add auth-utils

このコマンドはnuxt-auth-utilsを依存関係としてインストールし、nuxt.config.tsmodulesセクションに追加します。

クッキー暗号化キー

nuxt-auth-utilsはシールドされたクッキーを使用してセッションデータを保存するため、セッションクッキーはNUXT_SESSION_PASSWORD環境変数からの秘密キーを使用して暗号化されます。

設定されていない場合、この環境変数は開発モードで実行中に自動的に.envに追加されます。

.env
NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters

デプロイ前にこの環境変数を本番環境に追加する必要があります。

ログインAPIルート

このレシピでは、静的データに基づいてユーザーをサインインするためのシンプルなAPIルートを作成します。

メールアドレスとパスワードをリクエストボディに含むPOSTリクエストを受け入れる/api/login APIルートを作成しましょう。

server/api/login.post.ts
import { z } from 'zod'

const bodySchema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
})

export default defineEventHandler(async (event) => {
  const { email, password } = await readValidatedBody(event, bodySchema.parse)

  if (email === 'admin@admin.com' && password === 'iamtheadmin') {
    // クッキーにユーザーセッションを設定
    // このサーバーユーティリティはauth-utilsモジュールによって自動インポートされます
    await setUserSession(event, {
      user: {
        name: 'John Doe'
      }
    })
    return {}
  }
  throw createError({
    statusCode: 401,
    message: 'Bad credentials'
  })
})

プロジェクトにzod依存関係をインストールすることを忘れないでください(npm i zod)。

nuxt-auth-utilsによって公開されているsetUserSessionサーバーヘルパーについてもっと読む。

ログインページ

このモジュールは、アプリケーション内でユーザーが認証されているかどうかを知るためのVueコンポーザブルを公開しています。

const { loggedIn, session, user, clear, fetch } = useUserSession()

/api/loginルートにログインデータを送信するフォームを持つログインページを作成しましょう。

pages/login.vue
<script setup lang="ts">
const { loggedIn, user, fetch: refreshSession } = useUserSession()
const credentials = reactive({
  email: '',
  password: '',
})
async function login() {
  $fetch('/api/login', {
    method: 'POST',
    body: credentials
  })
  .then(async () => {
    // クライアントサイドでセッションを更新し、ホームページにリダイレクト
    await refreshSession()
    await navigateTo('/')
  })
  .catch(() => alert('Bad credentials'))
}
</script>

<template>
  <form @submit.prevent="login">
    <input v-model="credentials.email" type="email" placeholder="Email" />
    <input v-model="credentials.password" type="password" placeholder="Password" />
    <button type="submit">Login</button>
  </form>
</template>

APIルートの保護

サーバールートを保護することは、データを安全に保つための鍵です。クライアントサイドのミドルウェアはユーザーにとって便利ですが、サーバーサイドの保護がなければデータにアクセスされる可能性があります。機密データを含むルートを保護することは重要であり、ユーザーがログインしていない場合は401エラーを返すべきです。

auth-utilsモジュールは、ユーザーがログインしてアクティブなセッションを持っていることを確認するためのrequireUserSessionユーティリティ関数を提供します。

認証されたユーザーのみがアクセスできる/api/user/statsルートの例を作成しましょう。

server/api/user/stats.get.ts
export default defineEventHandler(async (event) => {
  // ユーザーがログインしていることを確認
  // 有効なユーザーセッションからのリクエストでない場合、401エラーをスローします
  const { user } = await requireUserSession(event)

  // TODO: ユーザーに基づいていくつかの統計を取得

  return {}
});

アプリルートの保護

サーバーサイドルートがあることでデータは安全ですが、何もしなければ、認証されていないユーザーが/usersページにアクセスしようとすると奇妙なデータを取得する可能性があります。クライアントサイドでルートを保護し、ユーザーをログインページにリダイレクトするためのクライアントサイドミドルウェアを作成する必要があります。

nuxt-auth-utilsは、ユーザーがログインしているかどうかを確認し、ログインしていない場合にリダイレクトするための便利なuseUserSessionコンポーザブルを提供します。

/middlewareディレクトリにミドルウェアを作成します。サーバーとは異なり、クライアントサイドのミドルウェアはすべてのエンドポイントに自動的に適用されるわけではなく、適用したい場所を指定する必要があります。

middleware/authenticated.ts
export default defineNuxtRouteMiddleware(() => {
  const { loggedIn } = useUserSession()

  // 認証されていない場合、ユーザーをログイン画面にリダイレクト
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
})

ホームページ

ルートを保護するためのアプリミドルウェアができたので、認証されたユーザー情報を表示するホームページで使用できます。ユーザーが認証されていない場合、ログインページにリダイレクトされます。

保護したいルートにミドルウェアを適用するためにdefinePageMetaを使用します。

pages/index.vue
<script setup lang="ts">
definePageMeta({
  middleware: ['authenticated'],
})
  
const { user, clear: clearSession } = useUserSession()

async function logout() {
  await clearSession()
  await navigateTo('/login')
}
</script>

<template>
  <div>
    <h1>Welcome {{ user.name }}</h1>
    <button @click="logout">Logout</button>
  </div>
</template>

また、セッションをクリアしてログインページにリダイレクトするためのログアウトボタンも追加しました。

結論

Nuxtアプリで非常に基本的なユーザー認証とセッション管理を設定することに成功しました。また、認証されたユーザーのみがアクセスできるように、サーバーとクライアントサイドで機密ルートを保護しました。

次のステップとして、以下を行うことができます:

OAuth認証、データベース、CRUD操作を備えたNuxtアプリの完全な例については、オープンソースのatidoneリポジトリをチェックしてください。

tips

このセクションは公式ドキュメントの翻訳ではなく、本サイト独自の補足記事です。

はじめに:Nuxtで認証とセッション管理を行うメリット

ウェブアプリケーションにおいて、ユーザー認証はほぼ必須の機能です。Nuxtはサーバーサイドレンダリング(SSR)とクライアントサイドレンダリング(CSR)の両方をシームレスに扱えるため、認証やセッション管理の実装には特有の課題があります。

本記事では、Nuxtでの認証とセッション管理の基本的な仕組みを理解し、実務でよくあるユースケースや注意点を補足的に解説します。これにより、セキュアでユーザーフレンドリーな認証機能を効率的に構築できるようになります。


まず結論:Nuxt認証のポイント

  • セッション管理はサーバーとクライアントの両面で行う必要がある
    NuxtはSSRを行うため、サーバー側でのセッション検証とクライアント側での状態管理を両立させることが重要です。

  • セキュアなクッキーを使ったセッション保存が推奨される
    セッション情報は暗号化されたHTTPOnlyクッキーに保存し、XSSやCSRF攻撃から守るべきです。

  • APIルートの保護はサーバー側で必須
    クライアント側のミドルウェアだけでは不十分で、サーバー側APIで認証チェックを必ず行う必要があります。

  • クライアントサイドミドルウェアでルート保護とリダイレクトを実装
    認証されていないユーザーをログインページに誘導するために、クライアント側でもルートガードを設定します。

  • Nuxtのコンポーザブルを活用してセッション状態を管理
    useUserSessionのようなコンポーザブルを使うと、認証状態の取得や更新が簡単になります。


いつ使うべきか、使わない方がよいケース

使うべきケース

  • ユーザーごとに異なるコンテンツや機能を提供したい場合
    会員限定ページやユーザー固有のダッシュボードを作る際に必須です。

  • セキュリティが重要なアプリケーション
    個人情報や課金情報を扱う場合は、認証とセッション管理を厳密に行う必要があります。

  • SSRを活用してSEOや初期表示速度を向上させたい場合
    NuxtのSSR機能を活かしつつ、認証状態をサーバー側で判定したいとき。

使わない方がよいケース

  • 完全にパブリックなサイトで認証が不要な場合
    認証機能を実装すると複雑さが増すため、不要なら避けたほうがシンプルです。

  • 認証が外部サービスに完全に委託されている場合
    例えばFirebase Authenticationなどを使い、Nuxt側でセッション管理を行わないケース。


実務でよくあるユースケースとサンプルコード

1. シンプルなログインAPIの実装

サーバー側でメールアドレスとパスワードを検証し、成功したらセッションをクッキーに保存します。

import { z } from 'zod'

const bodySchema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
})

export default defineEventHandler(async (event) => {
  const { email, password } = await readValidatedBody(event, bodySchema.parse)

  if (email === 'admin@admin.com' && password === 'securepassword') {
    await setUserSession(event, {
      user: { name: 'Admin User' }
    })
    return { message: 'ログイン成功' }
  }
  throw createError({ statusCode: 401, message: '認証情報が正しくありません' })
})

2. クライアントサイドでのログインフォームとセッション更新

ログイン成功後にセッション情報を更新し、ホームページへリダイレクトします。

<script setup lang="ts">
const { fetch: refreshSession } = useUserSession()
const credentials = reactive({ email: '', password: '' })

async function login() {
  try {
    await $fetch('/api/login', { method: 'POST', body: credentials })
    await refreshSession()
    await navigateTo('/')
  } catch {
    alert('ログインに失敗しました。メールアドレスとパスワードを確認してください。')
  }
}
</script>

<template>
  <form @submit.prevent="login">
    <input v-model="credentials.email" type="email" placeholder="メールアドレス" required />
    <input v-model="credentials.password" type="password" placeholder="パスワード" required />
    <button type="submit">ログイン</button>
  </form>
</template>

3. APIルートの認証保護

認証済みユーザーのみがアクセスできるAPIエンドポイントの例です。

export default defineEventHandler(async (event) => {
  const { user } = await requireUserSession(event)
  // ユーザー固有のデータを返す処理
  return { message: `ようこそ、${user.name}さん` }
})

よくある落とし穴・注意点

SSRとCSRの認証状態の同期

NuxtはSSRで初期レンダリングを行うため、サーバー側で認証状態を正しく判定しないと、クライアントとサーバーで表示がずれる「ハイドレーションエラー」が発生します。
サーバー側でセッションを検証し、クライアント側でも同じ状態を反映させることが重要です。

セキュリティ面の注意

  • HTTPOnlyかつSecureなクッキーを使う
    JavaScriptからアクセスできないクッキーにセッション情報を保存し、XSS攻撃を防ぎます。

  • CSRF対策を忘れない
    POSTリクエストなどの重要操作にはCSRFトークンを利用しましょう。

パフォーマンスへの影響

認証チェックをAPIリクエストごとに行うとサーバー負荷が増えるため、必要なルートだけに限定して保護をかけることが望ましいです。
また、クライアント側での状態管理は軽量にし、不要な再レンダリングを避ける工夫も必要です。


まとめ

Nuxtでの認証とセッション管理は、SSRとCSRの両方を考慮した設計が求められます。
セキュアなクッキーを使ったセッション保存、サーバー側APIの認証保護、クライアントサイドのルートガードを組み合わせることで、安全かつユーザーフレンドリーな認証機能を実装できます。

本記事のポイントを押さえ、実務でのユースケースに応じて適切に使い分けることで、Nuxtアプリの信頼性とユーザー体験を向上させましょう。

認証機能はセキュリティに直結するため、実装後は必ず動作検証とセキュリティ監査を行うことをおすすめします。