NextAuth / Auth.js で accessToken を session に載せてはいけない理由

中級 | 11分 で読める | 2026.06.14

公式ドキュメント

最初に結論

Auth.js / NextAuth で、外部API用の accessTokensession() callback の返り値に載せてはいけません。

次のようなコードは危険です。

callbacks: {
  async session({ session, token }) {
    session.accessToken = token.accessToken;
    return session;
  },
}

なぜなら、session() callback の返り値はクライアントから見えるからです。

session はクライアントに見える

session() callback の返り値は、次の経路でブラウザ側に露出します。

  • useSession()
  • /api/auth/session
  • クライアント側の getSession()

たとえば、Client Componentで次のように読めます。

"use client";

import { useSession } from "next-auth/react";

export function DebugSession() {
  const { data } = useSession();

  console.log(data?.accessToken);
  return null;
}

accessTokensession に入れると、ブラウザのJavaScriptから読める状態になります。これは、ブラウザにaccess tokenを渡さないというBFF構成の考え方と矛盾します。

注意: HttpOnly Cookie に保存しているつもりでも、session callback で accessToken を返すと、/api/auth/session 経由でブラウザに公開してしまいます。

jwt callback と session callback の違い

Auth.js / NextAuth では、jwt() callback と session() callback は役割が違います。

callback役割クライアントから見えるか
jwt()トークン内部に情報を保存する直接は見えない
session()クライアントに返すsessionの形を作る見える

jwt()tokenaccessToken を保持することと、session()sessionaccessToken を載せることは別です。

悪い例

次のコードは、外部API用の accessToken をクライアントに公開します。

callbacks: {
  async jwt({ token, account }) {
    if (account) {
      token.accessToken = account.access_token;
    }
    return token;
  },

  async session({ session, token }) {
    session.accessToken = token.accessToken;
    return session;
  },
}

jwt() で保持した値を、session() で公開コピーしているのが問題です。

よい例

session() には、UI表示に必要な最小限の情報だけを載せます。

callbacks: {
  async jwt({ token, account }) {
    if (account) {
      token.accessToken = account.access_token;
      token.refreshToken = account.refresh_token;
      token.expiresAt = account.expires_at;
    }
    return token;
  },

  async session({ session, token }) {
    if (token.sub) {
      session.user.id = token.sub;
    }
    return session;
  },
}

この構成では、accessToken はクライアントのsessionには出ません。

access token はサーバ専用ヘルパで取り出す

外部APIを呼ぶときは、Server Component、Server Action、Route Handlerなどのサーバ側から取り出します。

import "server-only";
import { getToken } from "next-auth/jwt";
import { headers } from "next/headers";

export async function getAccessToken() {
  const reqHeaders = await headers();

  const token = await getToken({
    req: { headers: reqHeaders } as any,
    secret: process.env.AUTH_SECRET,
  });

  return token?.accessToken;
}

import "server-only" を入れることで、クライアント側から誤ってimportした場合に検出しやすくなります。

Server Component で使う

import { auth } from "@/auth";
import { getAccessToken } from "@/lib/server/access-token";
import { redirect } from "next/navigation";

export default async function OrdersPage() {
  const session = await auth();
  if (!session) redirect("/login");

  const accessToken = await getAccessToken();

  const res = await fetch("https://api.example.com/orders", {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  const orders = await res.json();
  return <OrderList orders={orders} />;
}

この accessToken はサーバ側のローカル変数です。Client Componentへpropsで渡さない限り、ブラウザには露出しません。

session に載せてよいもの

session() に載せてよいのは、UIに必要な最小限の情報です。

載せてもよい候補理由
user.idUIや権限制御に必要な場合がある
user.name表示名
user.email表示や問い合わせに必要な場合
roleUI出し分けに必要な場合

載せてはいけないものは、外部APIを呼べるトークンや長期秘密です。

載せないもの理由
accessTokenAPIを呼べる
refreshToken長期的に強い権限を持つ
provider の秘密情報漏洩リスクが高い

まとめ

Auth.js / NextAuth では、jwt()session() の違いを理解することが重要です。

  • jwt() の token はサーバ側で扱う内部情報
  • session() の session はクライアントに返す情報
  • session.accessToken = token.accessToken は公開コピーになる
  • access token はサーバ側ヘルパで取り出す
  • Client Componentや /api/auth/session に秘密を出さない

参考リソース

  • 公式ドキュメント - NextAuth / Auth.js で accessToken を session に載せてはいけない理由 を確認するための一次情報

次に読む記事

← 一覧に戻る
PR
PR
PR
PR