はじめに
この投稿は、Next.js学習者が書いています。Rails単体のアプリから、Next.jsをフロントに置き、APIはRailsのまま、という構成へ移したときに、セッションCookieの扱いを、自身の理解のために整理します。
Railsだけだったころは、ブラウザが同一オリジンへのリクエストにCookieを付ける、という流れで足りることが多いです。Next.jsを挟むと、ブラウザから見えるリクエストと、サーバコンポーネントなどサーバ側からRailsへ出すリクエストで、Cookieの運び方が分かれます。
1. 全体の役割分担
想定する構成は次です。
- ブラウザ … ユーザーの操作、ログイン後の画面
- Next.js … UIと、一部のサーバ側データ取得
- Rails … セッション発行とAPI
セッションCookieはRailsが発行し、ブラウザが保持します。以降で見るのは、誰がRailsへのリクエストにCookieを載せるかです。
2. ブラウザからのfetch
ブラウザ上のfetchでは、Cookieを手動でヘッダに書かず、次のようにします。
await fetch("/api/session", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
ブラウザがfetchで送る/api/*は、Next.jsのrewriteでRailsへ転送されます。ブラウザから見ると、いま開いているサイトへの同一オリジンのリクエストです。
この例はログインです。成功すると、レスポンスヘッダに含まれるSet-Cookieをブラウザが保存します。ログイン後のAPI呼び出しでも、同じcredentials: "include"により、保持しているセッションCookieがリクエストに付きます。
3. サーバ側からのfetch
サーバコンポーネントやサーバ専用モジュールからRailsへfetchする場合、呼び出すのはブラウザではありません。ブラウザが自動で付けるCookieは、そのままでは付きません。
サーバ側では、届いているリクエストのCookieを読み、Rails向けのCookieヘッダとして付け直します。_app_sessionは、Railsのsession_storeで設定するCookie名の例で、Next.js側でも同じ文字列を使います。
import { cookies } from "next/headers";
const SESSION_COOKIE_NAME = "_app_session";
async function sessionCookieHeader(): Promise<
{ Cookie: string } | undefined
> {
const cookieStore = await cookies();
const session = cookieStore.get(SESSION_COOKIE_NAME);
if (!session) {
return undefined;
}
return {
Cookie: `${SESSION_COOKIE_NAME}=${encodeURIComponent(session.value)}`,
};
}
async function fetchTasks() {
const res = await fetch("http://api.example/api/tasks", {
headers: await sessionCookieHeader(),
cache: "no-store",
});
return res.json();
}
http://api.exampleは説明用のURLです。
ポイントは次です。
-
cookies()で、Next.jsに届いたリクエスト上のセッションを読む - 値があるときだけ
Cookieヘッダを組み立て、Railsへ付ける - 値は
encodeURIComponentで整え、記号を含んでもヘッダとして送れる形にする - ないときはヘッダを付けない(未ログインとしてAPI側の応答に任せる)
ブラウザのcredentials: "include"と、サーバのCookieヘッダ付け直しは、同じセッションを運ぶ別経路です。
4. ルート保護での確認
画面遷移の入口で、セッションCookieの有無だけを見ることもあります。次の例は、プロジェクト直下のproxy.tsに置く処理です。
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
const SESSION_COOKIE_NAME = "_app_session";
export function proxy(request: NextRequest) {
if (request.nextUrl.pathname === "/login") {
return NextResponse.next();
}
if (!request.cookies.has(SESSION_COOKIE_NAME)) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
ここでも読んでいるのは、ブラウザからNext.jsへ来たリクエストにCookieがあるかどうかです。サーバ側でCookieヘッダを付け直す処理とは別の層ですが、同じCookie名を共有します。
まとめ
- RailsがセッションCookieを発行し、ブラウザが保持する、という点は従来と同様です。
- ブラウザからのAPI呼び出しでは
credentials: "include"でCookieを付けます。 - Next.jsのサーバ側からRailsへ出す呼び出しでは、
cookies()の値をCookieヘッダとして付け直します。 - 自動付与に頼れるのはブラウザ起点のリクエストであり、サーバ起点では明示的な付け直しが必要です。