🍺
Next.js App Router + NextAuth 環境の API Routes で Session が null になる場合の対処
前提
- 以下
src
ディレクトリ有効な前提です。src
無効にしている場合は都度読み替えてください -
next-auth@beta
はあくまで beta なので利用しておらず、 v4 における内容となります - 仕事の納期的な理由で懇切丁寧に実装全体を記載する余裕が現状ないので書き殴り気味なことをお許しください
課題: getServerSession が null になる
/api
を使わずに直接 page.tsx
で DB とのやり取りなどを実装する場合には発生しませんが、/api
を BFF として使いたいケースで発生します
// src/app/(auth)/page.tsx
fetch('http://localhost:3000/api/hoge').then(res => res.json()).then(console.log)
// src/app/api/hoge/route.ts
const session = await getServerSession(authOptions)
console.log(session) // null
ログイン済みのブラウザで直接 /api/hoge
にアクセスすると session 情報が console.log に反映されるが、 app/page.tsx
側から fetch すると null になる。
原因
NextAuth に認証情報の管理を任せきりで忘れそうになるが、単に Cookie で管理されている。
/src/app/**/*/page.tsx
から /src/app/api/**/*/route.ts
側にこの Cookie が連動しないのが原因。
つまり headers から Cookie を取り出して fetch なりに渡してあげれば解決する。
対処方法
/// src/app/(auth)/page.tsx
import {headers} from 'next/headers'
fetch('http://localhost:3000/api/hoge', {
headers: await headers(); // ここでは全部まとめて headers に詰め込んでいるが、 Cookie など必要なものに絞った方が better
})
hono + hc を使っているケース
API Routes をそのまま使うとバックエンドの実装から型推論してくれないので hono + hc でいい感じにしたくなりますよね。なったんです。
まず前提となる実装は以下のようなものとする。
// src/app/api/[[...routes]]/route.ts
// こうしておくとフロントエンドとバックエンドが共存するキモさが軽減される気がする。
export * from '@/server'
// src/server/index.ts
import { Hono } from "hono";
import { handle } from "hono/vercel";
import { hogeApi } from "./handlers/hoge";
export const runtime = "edge";
const app = new Hono().basePath("/api").route("/hoge", hogeApi);
export { app };
export const GET = handle(app);
export const POST = handle(app);
export const PUT = handle(app);
export const DELETE = handle(app);
// src/server/handlers/hoge.ts
import { Hono } from "hono";
import { getServerSession } from 'next-auth';
import { nextAuthOptions } from '@/auth';
const api = new Hono();
const routes = api.get("/", async (ctx) => {
console.log(await getServerSession(nextAuthOptions));
return ctx.json({ msg: "hoge" });
});
export { routes as hogeApi };
// src/server/types.ts
import { app } from ".";
export type AppType = typeof app;
以上のような形で api/*
を hono で受け止めるようにしているとして、
普通に hc<AppType>('http://localhost:3000')
するとまた Cookie が伝達されない。なぜなら Cookie がセットされてないから。
hc
には init
オプションが用意されているのでここで headers を指定すると解消できる。
そのため以下のようにした。
// src/server/client.ts
export function $hc() {
return hc<AppType>('http://localhost:3000', {
init: {
headers: await header(),
}
})
}
これを使って page.tsx
を実装する。以下のようなイメージ。
/// src/app/(auth)/page.tsx
export default function HogePage() {
const cli = $hc();
const res = cli.api.hoge();
return <div>Hoge</div>
}
これで src/server/handlers
側で session が null にならなくなったはず。
Discussion