Prisma × NextAuth.js:知っておくべき3つのポイント
Next.js アプリケーションで認証機能を実装する際、NextAuth.js は強力な選択肢です。そして、データベースとの連携には Prisma が非常に役立ちます。この2つを組み合わせることで、安全で効率的な認証システムを構築できますが、スムーズに進めるためにはいくつかの重要なポイントがあります。
1. 認証情報を保存する「必須テーブル」の定義
NextAuth.js は、ユーザーの認証情報をデータベースに保存するために、特定の構造を持つテーブルを必要とします。Prisma をデータレイヤーとして使う場合、prisma/schema.prisma ファイルに以下の3つのモデルを定義することが不可欠です。
・Account: ユーザーがどのプロバイダー(Googleなど)でログインしたかの情報。
・Session: ユーザーのセッション情報(ログイン状態を維持するための情報)。
・VerificationToken: メールアドレス認証などに使われる一時的なトークン情報。
これらのモデルが正しく定義されていないと、NextAuth.js は認証情報をデータベースに保存できず、ログインが完了しません。
prisma/schema.prisma
での定義例:
// 既存の User モデルの下に追加
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
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)
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
2. User モデルへの「追加フィールド」と「リレーション」
OAuth プロバイダー(Googleなど)からの認証情報には、ユーザー名やメールアドレスだけでなく、プロフィール画像(image
)やメールアドレスの認証状況(emailVerified
)といったデータも含まれています。これらの情報をデータベースに保存したい場合は、あなたのUser
モデルにこれらのフィールドを追加する必要があります。
また、Prisma はデータベースのリレーションシップ(関連性)を厳密に管理します。Account
やSession
モデルがUser
モデルを参照するように定義されているなら、その逆も必要です。つまり、User
モデルにも、自身に関連するAccount
やSession
のリスト(例: accounts Account[]
, sessions Session[]
)をリレーションとして追加しなければなりません。これが欠けていると、スキーマのバリデーションエラーが発生します。
prisma/schema.prisma
での定義例(Userモデル):
model User {
id String @id @default(cuid())
email String @unique
name String?
image String? // プロフィール画像用に追加
emailVerified DateTime? @map("email_verified") // メール認証日時用に追加(Null許容)
xp Int @default(0)
level Int @default(1)
habits Habit[]
checkIns CheckIn[]
memos DailyMemo[]
createdAt DateTime @default(now())
// Account と Session からのリレーションを受け取るため、逆リレーションを追加
accounts Account[]
sessions Session[]
}
3. PrismaClient の「適切な初期化」
NextAuth.js がPrisma アダプターを通じてデータベース操作を行う際、PrismaClient
のインスタンスを使用します。特に Next.js の開発環境では、ホットリロードの仕組み上、PrismaClient
のインスタンスが複数生成されてしまうと問題が起こることがあります。
これを避けるために、src/app/api/auth/[...nextauth]/route.ts
のような NextAuth.js の設定ファイル内で、PrismaClient のインスタンスをグローバルな変数として一度だけ初期化するパターンを採用することが推奨されます。これにより、アダプターに常に単一で有効な PrismaClient
のインスタンスが渡され、findUnique
などのデータベース操作が正しく実行されるようになります。
src/app/api/auth/[...nextauth]/route.ts
での初期化例:
// src/app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
// --- PrismaClientのグローバル初期化 ---
declare global {
var prisma: PrismaClient | undefined;
}
const prisma = global.prisma || new PrismaClient();
if (process.env.NODE_ENV === "development") global.prisma = prisma;
// --- 初期化ここまで ---
export const authOptions = {
// ここで初期化したprismaインスタンスをアダプターに渡す
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}),
],
// ... その他の設定(session, pages, callbacksなど)
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
まとめ
Prisma と NextAuth.js の連携でつまずきやすいのは、主にスキーマ定義とPrismaClient
の初期化方法です。この3つのポイントを理解し、上記のコード例を参考に適切に設定することで、エラーを回避し、認証システムをスムーズに構築できると思います。
Discussion