やりたいこと
POST で API を叩くことで、ログイン中のユーザー ID を外部キーとして持つデータを永続化したい場面がありました。NextAuth.js の認証プロバイダーには Email (マジックリンク方式) を用いており、DB アダプターである Prisma と連携しています。
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import { PrismaClient } from '@prisma/client';
import EmailProvider from 'next-auth/providers/email';
import type { AuthOptions } from 'next-auth';
const prisma = new PrismaClient();
export const authOptions: AuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],
};
import { authOptions } from '@/lib/next-auth';
import { PrismaClient } from '@prisma/client';
import { getServerSession } from 'next-auth';
import type { Prisma } from '@prisma/client';
import type { NextApiRequest, NextApiResponse } from 'next';
const prisma = new PrismaClient();
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
// セッションオブジェクトを取得
const session = await getServerSession(req, res, authOptions);
if (!session || !session.user) {
res.status(401).send({ error: 'Not signed in' });
return;
}
const { title } = req.body as { title: string };
// ユーザーオブジェクトから ID を取得
// session.user に id プロパティは存在しないためエラーとなる
const { id: authorId } = session.user;
// ^^^^^ Property 'id' does not exist on type'{ name?: string | null | undefined; email?: string | null | undefined; image?: string | null | undefined; } | undefined'
const data = {
title,
authorId,
} satisfies Prisma.PostUncheckedCreateInput;
// DB にデータを保存
await prisma.post.create({ data });
res.status(204).end();
}
};
export default handler;
セッションの型定義を見ると、次のようになっていました。これを拡張する必要がありそうです。
export interface DefaultSession {
user?: {
name?: string | null
email?: string | null
image?: string | null
}
expires: ISODateString
}
セッションオブジェクトにユーザー ID を含める
調査の結果、セッションオブジェクトを拡張するには 2 つの手順が必要でした。
- Session callback を追加して、セッション取得のための API (
getServerSession
など) が返すオブジェクトを変更する - セッションオブジェクトの型定義を上書きする
Session callback を追加する
認証プロバイダー等を設定している authOptions
オブジェクトに callbacks.session
を追加します。関数内では spread syntax で作り直したセッションオブジェクトを返すことで、session.user
が id
プロパティを持つようにしています。
export const authOptions: AuthOptions = {
adapter: PrismaAdapter(prisma),
+ callbacks: {
+ session: ({ session, user }) => ({
+ ...session,
+ user: {
+ ...user,
+ id: user.id,
+ },
+ }),
+ },
providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
今回の様に認証プロバイダーと DB を連動している場合は、session callback の引数で受け取った user
オブジェクトは User 型になるため id
を取得できます。
セッションオブジェクトの型定義を上書きする
types/next-auth.d.ts
を追加して、セッションオブジェクトの型定義を上書きします。DefaultSession
を拡張する形で型をマージしているため、既存の情報を損ねることもありません。
import type { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
id: string;
} & DefaultSession['user'];
}
}
これで API Route のハンドラー内で発生していたエラーも消えて、きちんと動作するようになりました。
おわりに
NextAuth.js はドキュメントもしっかり整備されており、今回の様なケースまで網羅されていました。この記事が何かの参考になれば幸いです。
参考
Callbacks | NextAuth.jsCallbacks are asynchronous functions you can use to control what happens when an action is performed.next-auth.js.org
TypeScript | NextAuth.jsNextAuth.js has its own type definitions to use in your TypeScript projects safely. Even if you don't use TypeScript, IDEs like VSCode will pick this up to provide you with a better developer experience. While you are typing, you will get suggestions about what certain objects/functions look like, and sometimes links to documentation, examples, and other valuable resources.next-auth.js.org