mcpField
A
authjs-skills

authjs-skills

github.com/gocallum開発23793 回インストール40 週間前に更新
リポジトリ

authjs-skills について

authjs-skillsは、Auth.js v5の設定と利用方法をまとめたガイドです。Next.jsアプリに認証を実装する開発者向けに、プロバイダー、セッション、ルート保護、データベースアダプター、v4からの移行を扱います。

主な機能

  1. 01Auth.js v5の基本設定
  2. 02Googleと認証情報プロバイダー
  3. 03セッションとコールバックの管理
  4. 04ルート保護とロール確認
  5. 05データベースアダプター設定
  6. 06v4からの移行ガイド

活用シーン

  1. 01Next.jsアプリのサインイン設定
  2. 02OAuthまたはユーザー名とパスワード認証の追加
  3. 03ページとAPIルートの保護
  4. 04Auth.js v4からのアップグレード

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gocallum/nextjs16-agent-skills --skill authjs-skills

インストール後、Claude Code で/authjs-skillsを入力して読み込まれたか確認します。

詳細

リンク

インストール

pnpm add next-auth@beta

注:Auth.js v5 は現在ベータ版です。最新の v5 バージョンをインストールするには、next-auth@beta を使用してください。

Auth.js v5 の新機能

v4 からの主な変更点
  • 設定の簡素化:TypeScript のサポートを強化し、よりシンプルなセットアップを実現
  • ユニバーサルな auth() エクスポート:あらゆるコンテキストで認証を行う単一の関数
  • セキュリティの強化:CSRF 保護とセッション処理を改善
  • Edge Runtime のサポート:Edge Runtime およびミドルウェアとの完全な互換性
  • 型安全性の向上:全体を通して TypeScript の定義を改善

環境変数

必須の環境変数
# Auth.js Configuration
AUTH_SECRET=your_secret_key_here

# Google OAuth (if using Google provider)
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret

# For production deployments
AUTH_URL=https://yourdomain.com

# For development (optional, defaults to http://localhost:3000)
# AUTH_URL=http://localhost:3000
AUTH_SECRET の生成
# Generate a random secret (Unix/Linux/macOS)
openssl rand -base64 32

# Alternative using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

# Using pnpm
pnpm dlx auth secret

重要:AUTH_SECRET をバージョン管理にコミットしないでください。開発環境では .env.local を使用してください。

基本的なセットアップ(Next.js App Router)

1. auth.ts 設定ファイルを作成する

プロジェクトのルート(package.json と同じ階層)に auth.ts を作成します。

import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import Credentials from "next-auth/providers/credentials"

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Google({
      clientId: process.env.AUTH_GOOGLE_ID,
      clientSecret: process.env.AUTH_GOOGLE_SECRET,
    }),
    Credentials({
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      authorize: async (credentials) => {
        // TODO: Implement your authentication logic here
        // This is a basic example - see Credentials Provider section below for complete implementation
        if (!credentials?.email || !credentials?.password) {
          return null
        }

        // Example: validate against database (placeholder)
        // See "Credentials Provider" section for full implementation with bcrypt
        const user = { id: "1", email: credentials.email, name: "User" } // Replace with actual DB lookup
        
        if (!user) {
          return null
        }

        return {
          id: user.id,
          email: user.email,
          name: user.name,
        }
      },
    }),
  ],
  pages: {
    signIn: '/auth/signin',
  },
  callbacks: {
    authorized: async ({ auth }) => {
      // Return true if user is authenticated
      return !!auth
    },
  },
})

注:これは基本的なセットアップの例です。本番環境向けの Credentials 認証については、bcrypt による適切なパスワードハッシュ化とデータベース連携を含む、以下の「Credentials プロバイダー」セクションを参照してください。

2. API ルートハンドラーを作成する

app/api/auth/[...nextauth]/route.ts を作成します。

import { handlers } from "@/auth"

export const { GET, POST } = handlers
3. ミドルウェアを追加する(任意ですが推奨)

プロジェクトのルートに middleware.ts を作成します。

export { auth as middleware } from "@/auth"

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}

さらに細かく制御する場合:

import { auth } from "@/auth"

export default auth((req) => {
  const isLoggedIn = !!req.auth
  const isOnDashboard = req.nextUrl.pathname.startsWith('/dashboard')
  
  if (isOnDashboard && !isLoggedIn) {
    return Response.redirect(new URL('/auth/signin', req.url))
  }
})

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*'],
}

Google OAuth プロバイダー

1. Google Cloud Console のセットアップ
  1. Google Cloud Console にアクセスします
  2. 新しいプロジェクトを作成するか、既存のプロジェクトを選択します
  3. Google+ API を有効にします
  4. OAuth 2.0 認証情報を作成します:
    • アプリケーションの種類:ウェブアプリケーション
    • 承認済みのリダイレクト URI:
      • 開発環境:http://localhost:3000/api/auth/callback/google
      • 本番環境:https://yourdomain.com/api/auth/callback/google
  5. クライアント ID とクライアントシークレットを .env.local にコピーします
2. 設定
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Google({
      clientId: process.env.AUTH_GOOGLE_ID,
      clientSecret: process.env.AUTH_GOOGLE_SECRET,
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code"
        }
      }
    }),
  ],
})
3. Google プロバイダーのオプション
Google({
  clientId: process.env.AUTH_GOOGLE_ID,
  clientSecret: process.env.AUTH_GOOGLE_SECRET,
  // Request additional scopes
  authorization: {
    params: {
      scope: "openid email profile",
      prompt: "select_account", // Force account selection
    }
  },
  // Allow specific domains only
  allowDangerousEmailAccountLinking: false,
})

Credentials プロバイダー(ユーザー名/パスワード)

必須の依存関係
# Install required packages for credentials provider
pnpm add bcryptjs zod
pnpm add -D @types/bcryptjs
1. 基本設定
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import { z } from "zod"
import bcrypt from "bcryptjs"
import { prisma } from "@/lib/prisma"

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

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      credentials: {
        email: { label: "Email", type: "email", placeholder: "[email protected]" },
        password: { label: "Password", type: "password" },
      },
      authorize: async (credentials) => {
        try {
          const { email, password } = credentialsSchema.parse(credentials)
          
          // Fetch user from database
          const user = await prisma.user.findUnique({
            where: { email },
          })

          if (!user) {
            throw new Error("User not found")
          }

          // Verify password
          const isValidPassword = await bcrypt.compare(password, user.hashedPassword)
          
          if (!isValidPassword) {
            throw new Error("Invalid password")
          }

          // Return user object (must include id)
          return {
            id: user.id,
            email: user.email,
            name: user.name,
            image: user.image,
          }
        } catch (error) {
          console.error("Authentication error:", error)
          return null
        }
      },
    }),
  ],
  session: {
    strategy: "jwt", // Required for credentials provider
  },
})
2. ユーザー登録の例
// app/api/auth/register/route.ts
import { NextResponse } from "next/server"
import bcrypt from "bcryptjs"
import { z } from "zod"
import { prisma } from "@/lib/prisma"

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

export async function POST(req: Request) {
  try {
    const body = await req.json()
    const { email, password, name } = registerSchema.parse(body)

    // Check if user exists
    const existingUser = await prisma.user.findUnique({
      where: { email },
    })

    if (existingUser) {
      return NextResponse.json(
        { error: "User already exists" },
        { status: 400 }
      )
    }

    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10)

    // Create user
    const user = await prisma.user.create({
      data: {
        email,
        name,
        hashedPassword,
      },
    })

    return NextResponse.json(
      { message: "User created successfully", userId: user.id },
      { status: 201 }
    )
  } catch (error) {
    console.error("Registration error:", error)
    return NextResponse.json(
      { error: "Failed to register user" },
      { status: 500 }
    )
  }
}

コンポーネントでの Auth の使用

サーバーコンポーネント
import { auth } from "@/auth"

export default async function ProfilePage() {
  const session = await auth()

  if (!session?.user) {
    return <div>Not authenticated</div>
  }

  return (
    <div>
      <h1>Welcome, {session.user.name}!</h1>
      <p>Email: {session.user.email}</p>
    </div>
  )
}
サーバーアクション
"use server"

import { auth } from "@/auth"
import { revalidatePath } from "next/cache"
import { prisma } from "@/lib/prisma"

export async function updateProfile(formData: FormData) {
  const session = await auth()

  if (!session?.user) {
    throw new Error("Not authenticated")
  }

  const name = formData.get("name") as string

  // Update database
  await prisma.user.update({
    where: { id: session.user.id },
    data: { name },
  })

  revalidatePath("/profile")
}
クライアントコンポーネント(SessionProvider を使用)
// app/providers.tsx
"use client"

import { SessionProvider } from "next-auth/react"

export function Providers({ children }: { children: React.ReactNode }) {
  return <SessionProvider>{children}</SessionProvider>
}
// app/layout.tsx
import { Providers } from "./providers"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}
// app/components/user-profile.tsx
"use client"

import { useSession, signIn, signOut } from "next-auth/react"

export function UserProfile() {
  const { data: session, status } = useSession()

  if (status === "loading") {
    return <div>Loading...</div>
  }

  if (!session) {
    return (
      <button onClick={() => signIn()}>
        Sign In
      </button>
    )
  }

  return (
    <div>
      <p>Signed in as {session.user?.email}</p>
      <button onClick={() => signOut()}>
        Sign Out
      </button>
    </div>
  )
}

サインイン/サインアウトの操作

プログラムによるサインイン
import { signIn } from "@/auth"

// Server Action
export async function handleSignIn(provider: string) {
  "use server"
  await signIn(provider)
}

// With credentials
export async function handleCredentialsSignIn(formData: FormData) {
  "use server"
  await signIn("credentials", formData)
}

// With redirect
export async function handleGoogleSignIn() {
  "use server"
  await signIn("google", { redirectTo: "/dashboard" })
}
サインインフォームコンポーネント
// app/auth/signin/page.tsx
import { signIn } from "@/auth"

export default function SignInPage() {
  return (
    <div>
      <h1>Sign In</h1>
      
      {/* Google OAuth */}
      <form
        action={async () => {
          "use server"
          await signIn("google")
        }}
      >
        <button type="submit">Sign in with Google</button>
      </form>

      {/* Credentials */}
      <form
        action={async (formData) => {
          "use server"
          await signIn("credentials", formData)
        }}
      >
        <input name="email" type="email" placeholder="Email" required />
        <input name="password" type="password" placeholder="Password" required />
        <button type="submit">Sign In</button>
      </form>
    </div>
  )
}
サインアウト
import { signOut } from "@/auth"

export default function SignOutButton() {
  return (
    <form
      action={async () => {
        "use server"
        await signOut()
      }}
    >
      <button type="submit">Sign Out</button>
    </form>
  )
}

セッション管理

セッション戦略

Auth.js v5 は、次の 2 つのセッション戦略をサポートしています。

  1. JWT(デフォルト):セッションを暗号化された JWT トークンに保存します
  2. データベース:セッションをデータベースに保存します
export const { handlers, signIn, signOut, auth } = NextAuth({
  session: {
    strategy: "jwt", // or "database"
    maxAge: 30 * 24 * 60 * 60, // 30 days
    updateAge: 24 * 60 * 60, // 24 hours
  },
})
セッションの拡張
import NextAuth from "next-auth"
import type { DefaultSession } from "next-auth"

declare module "next-auth" {
  interface Session {
    user: {
      id: string
      role: string
    } & DefaultSession["user"]
  }
}

export const { handlers, signIn, signOut, auth } = NextAuth({
  callbacks: {
    jwt({ token, user }) {
      if (user) {
        token.id = user.id
        token.role = user.role
      }
      return token
    },
    session({ session, token }) {
      if (session.user) {
        session.user.id = token.id as string
        session.user.role = token.role as string
      }
      return session
    },
  },
})

コールバック

必須のコールバック
export const { handlers, signIn, signOut, auth } = NextAuth({
  callbacks: {
    // Called when user signs in
    async signIn({ user, account, profile }) {
      // Return true to allow sign in, false to deny
      // Example: Check if email is verified
      if (account?.provider === "google") {
        return profile?.email_verified === true
      }
      return true
    },

    // Called whenever a JWT is created or updated
    async jwt({ token, user, account }) {
      if (user) {
        token.id = user.id
      }
      if (account) {
        token.accessToken = account.access_token
      }
      return token
    },

    // Called whenever a session is checked
    async session({ session, token }) {
      session.user.id = token.id as string
      session.accessToken = token.accessToken as string
      return session
    },

    // Called on middleware and server-side auth checks
    async authorized({ auth, request }) {
      const isLoggedIn = !!auth?.user
      const isOnDashboard = request.nextUrl.pathname.startsWith("/dashboard")
      
      if (isOnDashboard) {
        return isLoggedIn
      }
      
      return true
    },

    // Called when user is redirected
    async redirect({ url, baseUrl }) {
      // Allows relative callback URLs
      if (url.startsWith("/")) return `${baseUrl}${url}`
      // Allows callback URLs on the same origin
      else if (new URL(url).origin === baseUrl) return url
      return baseUrl
    },
  },
})

データベースアダプター(任意)

ユーザー、アカウント、セッションをデータベースに永続化するには、Prisma アダプターをインストールします。

pnpm add @auth/prisma-adapter

次に、auth.ts で設定します。

import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/lib/prisma"

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: {
    strategy: "database",
  },
  providers: [
    // ... providers
  ],
})

必須の Prisma スキーマ:

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String    @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?
  user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

API ルート

カスタム API エンドポイント
// app/api/user/route.ts
import { auth } from "@/auth"
import { NextResponse } from "next/server"

export async function GET() {
  const session = await auth()

  if (!session?.user) {
    return NextResponse.json(
      { error: "Unauthorized" },
      { status: 401 }
    )
  }

  return NextResponse.json({
    user: session.user,
  })
}
保護されたルートのヘルパー
// lib/auth-helpers.ts
import { auth } from "@/auth"
import { NextResponse } from "next/server"
import type { Session } from "next-auth"

export async function withAuth(
  handler: (session: Session) => Promise<NextResponse>
) {
  const session = await auth()

  if (!session?.user) {
    return NextResponse.json(
      { error: "Unauthorized" },
      { status: 401 }
    )
  }

  return handler(session)
}

// Usage
export async function GET() {
  return withAuth(async (session) => {
    return NextResponse.json({ userId: session.user.id })
  })
}

ベストプラクティス

セキュリティ
  • 必ずパスワードをハッシュ化する:bcrypt、argon2 などを使用します
  • 本番環境では HTTPS を使用する:Cookie を安全に送信するために必要です
  • 環境変数を検証する:AUTH_SECRET とプロバイダーの認証情報を確認します
  • 安全な Cookie オプションを設定する:
    cookies: {
      sessionToken: {
        name: `__Secure-next-auth.session-token`,
        options: {
          httpOnly: true,
          sameSite: 'lax',
          path: '/',
          secure: process.env.NODE_ENV === 'production',
        },
      },
    }
    
  • レート制限を実装する:サインインエンドポイントを保護します
  • CSRF 保護を使用する:v5 ではデフォルトで有効になっています
  • リダイレクトを検証する:オープンリダイレクトを防ぐために redirect コールバックを使用します
セッション管理
  • 適切なmaxAgeを使用する:デフォルトは30日です。セキュリティ要件に応じて調整してください
  • セッションを定期的に更新する:updateAgeを使用してセッションデータを更新します
  • セッションの有効期限切れに適切に対処する:分かりやすいUIフィードバックを表示します
  • セッションストレージを保護する:機密性の高いアプリケーションではデータベースストラテジーを使用します
プロバイダーの設定
  • Google OAuth:必要最小限のスコープをリクエストします
  • Credentials:常にzodなどで入力を検証します
  • 複数のプロバイダー:アカウントの連携は慎重に許可します
  • プロバイダー固有のロジック:コールバックを使用してプロバイダーごとの差異に対処します
パフォーマンス
  • セッション確認をキャッシュする:ルート保護にはミドルウェアを使用します
  • データベース呼び出しを最小限にする:適切な場合はJWTストラテジーを使用します
  • データベースクエリを最適化する:頻繁にクエリされるフィールドにインデックスを追加します
  • Edge Runtimeを使用する:ミドルウェアでの認証確認を高速化します
型安全性
  • 型を適切に拡張する:カスタムセッションフィールドにはモジュール拡張を使用します
  • 入力を検証する:実行時の型チェックにはzodを使用します
  • TypeScriptのstrict mode:型安全性を高めるために有効にします

よく使われるパターン

ミドルウェアを使った保護されたページ
import { auth } from "@/auth"
import { NextResponse } from "next/server"

export default auth((req) => {
  const isLoggedIn = !!req.auth
  const { pathname } = req.nextUrl

  // Public routes
  const publicRoutes = ['/auth/signin', '/auth/register', '/']
  if (publicRoutes.includes(pathname)) {
    return NextResponse.next()
  }

  // Protected routes
  if (!isLoggedIn) {
    const signInUrl = new URL('/auth/signin', req.url)
    signInUrl.searchParams.set('callbackUrl', pathname)
    return NextResponse.redirect(signInUrl)
  }

  // Role-based access
  const adminRoutes = ['/admin']
  if (adminRoutes.some(route => pathname.startsWith(route))) {
    if (req.auth.user.role !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', req.url))
    }
  }

  return NextResponse.next()
})

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
複数プロバイダーの設定
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import GitHub from "next-auth/providers/github"
import Credentials from "next-auth/providers/credentials"
import { prisma } from "@/lib/prisma"

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Google({
      clientId: process.env.AUTH_GOOGLE_ID,
      clientSecret: process.env.AUTH_GOOGLE_SECRET,
    }),
    GitHub({
      clientId: process.env.AUTH_GITHUB_ID,
      clientSecret: process.env.AUTH_GITHUB_SECRET,
    }),
    Credentials({
      // ... credentials config
    }),
  ],
  callbacks: {
    async signIn({ user, account, profile }) {
      // Link accounts with same email
      if (account?.provider !== "credentials") {
        const existingUser = await prisma.user.findUnique({
          where: { email: user.email },
        })
        
        if (existingUser) {
          // Link account to existing user
          await prisma.account.create({
            data: {
              userId: existingUser.id,
              type: account.type,
              provider: account.provider,
              providerAccountId: account.providerAccountId,
              access_token: account.access_token,
              refresh_token: account.refresh_token,
            },
          })
        }
      }
      return true
    },
  },
})
カスタムサインインページ
// app/auth/signin/page.tsx
import { signIn } from "@/auth"
import { redirect } from "next/navigation"

export default function SignInPage({
  searchParams,
}: {
  searchParams: { callbackUrl?: string }
}) {
  const callbackUrl = searchParams.callbackUrl || "/dashboard"

  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="w-full max-w-md space-y-8 p-8">
        <h1 className="text-2xl font-bold text-center">Sign In</h1>
        
        {/* OAuth Providers */}
        <div className="space-y-4">
          <form
            action={async () => {
              "use server"
              await signIn("google", { redirectTo: callbackUrl })
            }}
          >
            <button 
              type="submit"
              className="w-full bg-white border border-gray-300 text-gray-700 py-2 px-4 rounded hover:bg-gray-50"
            >
              Continue with Google
            </button>
          </form>
        </div>

        <div className="relative">
          <div className="absolute inset-0 flex items-center">
            <div className="w-full border-t border-gray-300" />
          </div>
          <div className="relative flex justify-center text-sm">
            <span className="bg-white px-2 text-gray-500">Or</span>
          </div>
        </div>

        {/* Credentials Form */}
        <form
          action={async (formData) => {
            "use server"
            try {
              await signIn("credentials", {
                email: formData.get("email"),
                password: formData.get("password"),
                redirectTo: callbackUrl,
              })
            } catch (error) {
              redirect(`/auth/signin?error=CredentialsSignin&callbackUrl=${callbackUrl}`)
            }
          }}
          className="space-y-4"
        >
          <div>
            <label htmlFor="email" className="block text-sm font-medium text-gray-700">
              Email
            </label>
            <input
              id="email"
              name="email"
              type="email"
              required
              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"
            />
          </div>
          <div>
            <label htmlFor="password" className="block text-sm font-medium text-gray-700">
              Password
            </label>
            <input
              id="password"
              name="password"
              type="password"
              required
              className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md"
            />
          </div>
          <button
            type="submit"
            className="w-full bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700"
          >
            Sign In
          </button>
        </form>
      </div>
    </div>
  )
}
ロールベースのアクセス制御(RBAC)
// lib/auth-rbac.ts
import { auth } from "@/auth"

export type Role = "admin" | "user" | "guest"

export async function checkRole(allowedRoles: Role[]) {
  const session = await auth()
  
  if (!session?.user) {
    return false
  }

  const userRole = session.user.role as Role
  return allowedRoles.includes(userRole)
}

// Usage in Server Component
export default async function AdminPage() {
  const hasAccess = await checkRole(["admin"])
  
  if (!hasAccess) {
    redirect("/unauthorized")
  }

  return <div>Admin Dashboard</div>
}

// Usage in Server Action
export async function deleteUser(userId: string) {
  "use server"
  
  const hasAccess = await checkRole(["admin"])
  
  if (!hasAccess) {
    throw new Error("Unauthorized")
  }

  const { prisma } = await import("@/lib/prisma")
  await prisma.user.delete({ where: { id: userId } })
}

v4からv5への移行

主な違い
  1. インポートの変更:next-authパッケージはそのままですが、インポートが簡潔になりました
  2. 汎用的なauth():getServerSessionをauth()に置き換えます
  3. ミドルウェア:authをミドルウェアとして直接使用します
  4. 設定:より簡潔になり、必要なオプションが減りました
移行手順
// v4 (old)
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"

export async function GET() {
  const session = await getServerSession(authOptions)
}

// v5 (new)
import { auth } from "@/auth"

export async function GET() {
  const session = await auth()
}
// v4 middleware (old)
import { withAuth } from "next-auth/middleware"

export default withAuth({
  callbacks: {
    authorized: ({ token }) => !!token,
  },
})

// v5 middleware (new)
export { auth as middleware } from "@/auth"

トラブルシューティング

よくある問題

AUTH_SECRETが設定されていない場合:

Error: AUTH_SECRET environment variable is not set

.env.localでAUTH_SECRETを生成して設定してください

Google OAuthのリダイレクトが一致しない場合:

Error: redirect_uri_mismatch

Google ConsoleのリダイレクトURIが次の値と一致していることを確認してください:http://localhost:3000/api/auth/callback/google

セッションが保持されない場合:

  • AUTH_URLが正しく設定されていることを確認します
  • Cookieがブロックされていないことを確認します
  • sessionToken Cookieが設定されていることを確認します(ブラウザーのDevToolsで確認)

セッションに関するTypeScriptエラー:

  • モジュール拡張を使用してSession型とJWT型を拡張します
  • pnpm tsc --noEmitを実行して型エラーを確認します

Credentialsプロバイダーが動作しない場合:

  • session.strategyが"jwt"に設定されていることを確認します
  • authorize関数がidフィールドを含む正しいユーザーオブジェクトを返すことを確認します
  • パスワードのハッシュ化/比較ロジックを確認します

リソース

コメント