0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NextAuth v5のセッションはJWTなのかDBなのか、いつも曖昧だったので本番構成で整理した

0
Posted at

NextAuth(Auth.js)v5を個人開発2本(Web + iOSアプリ)で運用しています。動いてはいるものの、セッションがJWTなのかDBなのか聞かれたら即答できない時期が長くありました。

調べて整理したら、押さえるべきルールは実質2つでした。

  • セッション戦略は「adapterを設定したかどうか」で暗黙に切り替わる(なし=jwt、あり=database)
  • Credentialsプロバイダはdatabase戦略ではセッションが作られないので、strategy: "jwt"の明示が必要

この記事では、この分岐がどこで効いてくるのかを、実際に運用している設定を貼りながら整理します。

よくある症状

NextAuthを使っていて、こういう状態になったことはないでしょうか。

  • チュートリアル通りに書いたら動いたが、セッションがどこに保存されているのか答えられない
  • sessionコールバックの引数にtokenを書いている記事とuserを書いている記事があり、どちらが正しいのか分からない
  • Credentialsプロバイダを追加したら、ログインできたはずなのにセッションが取れない

全部、冒頭の2ルールから説明がつきます。

前提: セッション戦略は2つある

jwt戦略 database戦略
セッションの実体 暗号化JWT(JWE)そのものがCookie DBのsessionsテーブルの行
Cookieの中身 クレームを含む暗号化トークン ランダムなセッショントークン(DBへの参照キー)
サーバー側の照会 不要(復号だけ) 毎回DBを引く
即時失効(強制ログアウト) 難しい DBの行を消すだけ
sessionコールバックの引数 { session, token } { session, user }

重要なのはデフォルト値の決まり方です。明示しなければ、adapterを渡していないときはjwt、渡しているときはdatabaseになります。

つまりDrizzleAdapterを繋いだ瞬間に、セッションの保存場所はCookieからDBへ静かに切り替わります。ここに気付かないまま古い記事からコールバックだけコピペすると、tokenがundefinedで首をかしげることになります。私はなりました。

Credentialsだけは例外

もう1つのハマりどころがCredentialsプロバイダです。Credentialsによるログインではdatabaseセッションが作成されません。adapterを設定した状態(デフォルトがdatabase戦略)でCredentialsログインすると、認証自体は通るのにセッションが保存されず、「ログインしたのにされていない」状態になります。

対処はsession: { strategy: "jwt" }を明示することです。ただしこれは全プロバイダに効くグローバル設定なので、「OAuthはDBセッション、Credentialsも併用」という構成は素直にはできません。

本番でどう使い分けているか

私のプロダクト(squadnote)では、この制約を逆手に取ってこう運用しています。

  • 本番: Google / Apple OAuthのみ。database戦略(デフォルトのまま)
  • E2Eテストと開発環境のみ: テストログイン用Credentialsを有効化して、jwt戦略に切り替え

実際の設定の抜粋です。

export function getAuthConfig(env?: { ENABLE_TEST_LOGIN?: string /* ... */ }): NextAuthConfig {
  const enableTestLogin = env?.ENABLE_TEST_LOGIN === "true";

  const providers: NextAuthConfig["providers"] = [
    Google({ /* ... */ }),
    Apple({ /* ... */ }),
  ];

  if (enableTestLogin) {
    providers.push(
      Credentials({
        id: "test-login",
        async authorize(credentials) { /* テストユーザーを返す */ },
      }),
    );
  }

  return {
    providers,
    adapter: drizzleAdapter, // これがある時点でデフォルトはdatabase戦略
    // Credentials ProviderはJWTセッション戦略が必要
    session: enableTestLogin ? { strategy: "jwt" } : undefined,
    callbacks: {
      // 戦略によってsessionコールバックの「相方」が変わる
      jwt: enableTestLogin
        ? ({ token, user }) => {
            if (user) token.id = user.id; // 初回ログイン時のみuserが渡ってくる
            return token;
          }
        : undefined,
      session: enableTestLogin
        ? ({ session, token }) => ({
            // jwt戦略: DBを引かないのでtokenから復元する
            ...session,
            user: { ...session.user, id: (token as { id?: string }).id ?? "" },
          })
        : ({ session, user }) => ({
            // database戦略: sessionsとJOINされたuserがそのまま渡ってくる
            ...session,
            user: { ...session.user, id: user.id },
          }),
    },
  };
}

この構成で押さえている点を挙げます。

sessionにundefinedを渡すと、デフォルトの分岐(adapterあり=database)にそのまま任せられます。条件付きで戦略を切り替えたいときに便利です。

jwt戦略のときだけjwtコールバックが必要になります。注意したいのは、userが渡ってくるのはサインイン直後の1回だけということです。以降のリクエストでは、tokenに自分で書き込んだ値だけが頼りになります。

そしてsessionコールバックは戦略ごとに引数が違うので、両対応するなら2系統書くしかありません。型を見るとtokenuserも生えているように見えるのが罠で、実行時にどちらが入っているかは戦略次第です。

いまどちらの戦略で動いているか確認する方法

自分のアプリがどちらで動いているかは、次の2点を見れば分かります。

  1. DBのsessions(相当の)テーブルに行が増えるか。増えるならdatabase戦略
  2. セッションCookie(authjs.session-token)の中身。eyJ...形式の長いトークン(JWE)ならjwt戦略、短いランダム文字列ならdatabase戦略の参照トークン

Drizzle + D1で運用している場合はwrangler d1 execute <db> --command "select * from session"あたりで確認できます。

まとめ

状況 戦略 やること
adapterなし jwt(デフォルト) jwt/sessionコールバックでtokenを扱う
adapterあり・OAuthのみ database(デフォルト) session({ session, user })でuserを使う
Credentials使用 jwt必須 strategy: "jwt"を明示。DBセッションとの併用は不可
OAuth=DB、Credentialsはテストのみ 環境で切替 この記事の構成(環境変数でstrategyとコールバックを出し分け)

「adapterを入れた瞬間に戦略が切り替わる」「Credentialsはjwt必須」。この2つを押さえてから、セッション周りで首をかしげる回数は目に見えて減りました。

環境

  • next-auth 5.0.0-beta.25 / @auth/drizzle-adapter 1.7.x
  • Next.js 15(App Router)+ Drizzle ORM + Cloudflare D1(@opennextjs/cloudflare)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?