[GH-ISSUE #7008] auth.api.getSession() returns null in Next.js App Router server components with cookie cache enabled #10701

Open
opened 2026-04-13 06:59:35 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @Ben52 on GitHub (Dec 26, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7008

Originally assigned to: @bytaesu on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Enable cookie cache in Better Auth config
  2. Sign in successfully (cookies are set, client-side useSession works)
  3. Call auth.api.getSession({ headers }) from a Next.js server component
  4. Session returns null despite valid cookies being present

Minimal reproduction:

  import { betterAuth } from 'better-auth';
  import { drizzleAdapter } from 'better-auth/adapters/drizzle';
  
  export const auth = betterAuth({
    secret: process.env.BETTER_AUTH_SECRET,
    database: drizzleAdapter(db, {
      provider: 'sqlite',
      schema: { /* ... */ },
    }),
    emailAndPassword: { enabled: true },
    session: {
      cookieCache: {
        enabled: true,
        maxAge: 5 * 60, // 5 minutes
      },
    },
  });
import { auth } from '@/auth';
import { headers } from 'next/headers';

export async function getSession() {
  const h = await headers();
  console.log('cookie header:', h.get('cookie')); // ✅ Shows valid cookies
  
  const result = await auth.api.getSession({ headers: h });
  console.log('result:', result); // ❌ Returns null
  
  return result;
}
export default async function DashboardPage() {
  const session = await getSession();
  if (!session) redirect('/login'); // Always redirects despite valid session
  
  return <div>Welcome {session.user.name}</div>;
}

Current vs. Expected behavior

Current:

  • auth.api.getSession() returns null in server components
  • The cookie header is present and contains both better-auth.session_token and better-auth.session_data
  • No errors are thrown

Expected:

  • auth.api.getSession() should parse the cookie cache and return the session without hitting the database

What works:

  • Calling /api/auth/get-session API endpoint directly returns the session correctly (~10ms, using cache)
  • Client-side useSession() works correctly
  • Disabling cookieCache makes auth.api.getSession() work (but hits DB every time)

Debug output

[getSession] cookie header: better-auth.session_token=Y2SAOhI9dCQyNCwtwD5Co0giUXIr6qj0...; better-auth.session_data=eyJzZXNzaW9uIjp7...
[getSession] result: null

The session_data cookie contains valid base64-encoded session data with user info, expiry, and signature.

1.4.9 (also reproduced on 1.4.8-beta.6)

Works correctly on 1.2.7.

What version of Better Auth are you using?

1.4.9

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:45 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6000",
    "release": "25.0.0",
    "cpuCount": 10,
    "cpuModel": "Apple M1 Max",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.30 GB"
  },
  "node": {
    "version": "v25.2.1",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.2"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.1.0"
    },
    {
      "name": "react",
      "version": "19.2.3"
    }
  ],
  "databases": [
    {
      "name": "@libsql/client",
      "version": "^0.15.15"
    },
    {
      "name": "drizzle",
      "version": "^0.45.1"
    }
  ],
  "betterAuth": {
    "version": "Unknown",
    "config": null,
    "error": "URL_INVALID: The URL 'undefined' is not in a valid format"
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { db } from '@/db';
import * as schema from '@/db/schema';

export const auth = betterAuth({
  secret: process.env.BETTER_AUTH_SECRET,
  debug: true,
  database: drizzleAdapter(db, {
    provider: 'sqlite',
    schema: {
      user: schema.users,
      session: schema.sessions,
      account: schema.accounts,
      verification: schema.verifications,
    },
  }),

  // Enable email/password authentication
  emailAndPassword: {
    enabled: true,
    // Use bcrypt with same config as next-auth for hash compatibility
    password: {
      hash: async (password: string) => {
        const bcrypt = await import('bcryptjs');
        return bcrypt.default.hash(password, 10);
      },
      verify: async (data: { password: string; hash: string }) => {
        const bcrypt = await import('bcryptjs');
        return bcrypt.default.compare(data.password, data.hash);
      },
    },
  },

  // Use numeric IDs for users (let DB auto-increment)
  advanced: {
    database: {
      generateId: (options) => {
        if (options.model === 'user') {
          return false; // Let SQLite auto-increment for users
        }
        return crypto.randomUUID();
      },
    },
  },
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // 5 minutes
    },
  },
});

Additional context

Works correctly on version 1.2.7

Originally created by @Ben52 on GitHub (Dec 26, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7008 Originally assigned to: @bytaesu on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Enable cookie cache in Better Auth config 2. Sign in successfully (cookies are set, client-side useSession works) 3. Call auth.api.getSession({ headers }) from a Next.js server component 4. Session returns null despite valid cookies being present Minimal reproduction: ``` import { betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; export const auth = betterAuth({ secret: process.env.BETTER_AUTH_SECRET, database: drizzleAdapter(db, { provider: 'sqlite', schema: { /* ... */ }, }), emailAndPassword: { enabled: true }, session: { cookieCache: { enabled: true, maxAge: 5 * 60, // 5 minutes }, }, }); ``` ``` import { auth } from '@/auth'; import { headers } from 'next/headers'; export async function getSession() { const h = await headers(); console.log('cookie header:', h.get('cookie')); // ✅ Shows valid cookies const result = await auth.api.getSession({ headers: h }); console.log('result:', result); // ❌ Returns null return result; } ``` ``` export default async function DashboardPage() { const session = await getSession(); if (!session) redirect('/login'); // Always redirects despite valid session return <div>Welcome {session.user.name}</div>; } ``` ### Current vs. Expected behavior **Current:** - `auth.api.getSession()` returns `null` in server components - The cookie header is present and contains both `better-auth.session_token` and `better-auth.session_data` - No errors are thrown **Expected:** - `auth.api.getSession()` should parse the cookie cache and return the session without hitting the database **What works:** - Calling `/api/auth/get-session` API endpoint directly returns the session correctly (~10ms, using cache) - Client-side `useSession()` works correctly - Disabling `cookieCache` makes `auth.api.getSession()` work (but hits DB every time) ### Debug output ``` [getSession] cookie header: better-auth.session_token=Y2SAOhI9dCQyNCwtwD5Co0giUXIr6qj0...; better-auth.session_data=eyJzZXNzaW9uIjp7... [getSession] result: null ``` The `session_data` cookie contains valid base64-encoded session data with user info, expiry, and signature. 1.4.9 (also reproduced on 1.4.8-beta.6) Works correctly on 1.2.7. ### What version of Better Auth are you using? 1.4.9 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:45 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6000", "release": "25.0.0", "cpuCount": 10, "cpuModel": "Apple M1 Max", "totalMemory": "32.00 GB", "freeMemory": "0.30 GB" }, "node": { "version": "v25.2.1", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.2" }, "frameworks": [ { "name": "next", "version": "16.1.0" }, { "name": "react", "version": "19.2.3" } ], "databases": [ { "name": "@libsql/client", "version": "^0.15.15" }, { "name": "drizzle", "version": "^0.45.1" } ], "betterAuth": { "version": "Unknown", "config": null, "error": "URL_INVALID: The URL 'undefined' is not in a valid format" } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import { db } from '@/db'; import * as schema from '@/db/schema'; export const auth = betterAuth({ secret: process.env.BETTER_AUTH_SECRET, debug: true, database: drizzleAdapter(db, { provider: 'sqlite', schema: { user: schema.users, session: schema.sessions, account: schema.accounts, verification: schema.verifications, }, }), // Enable email/password authentication emailAndPassword: { enabled: true, // Use bcrypt with same config as next-auth for hash compatibility password: { hash: async (password: string) => { const bcrypt = await import('bcryptjs'); return bcrypt.default.hash(password, 10); }, verify: async (data: { password: string; hash: string }) => { const bcrypt = await import('bcryptjs'); return bcrypt.default.compare(data.password, data.hash); }, }, }, // Use numeric IDs for users (let DB auto-increment) advanced: { database: { generateId: (options) => { if (options.model === 'user') { return false; // Let SQLite auto-increment for users } return crypto.randomUUID(); }, }, }, session: { cookieCache: { enabled: true, maxAge: 5 * 60, // 5 minutes }, }, }); ``` ### Additional context Works correctly on version 1.2.7
GiteaMirror added the platformcorebug labels 2026-04-13 06:59:35 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 26, 2025):

It looks like you're encountering an issue where auth.api.getSession() is returning null even though valid session cookies are set in your Next.js App Router server components. This seems to be a known issue with the Better Auth framework, particularly with the cookie caching mechanism under certain configurations.

Here are some steps you can take to troubleshoot this issue:

  1. Disable Cookie Cache: As you mentioned, disabling the cookieCache setting allows auth.api.getSession() to work, although it hits the database every time. This suggests that the cookie handling might specifically have issues.

  2. Ensure Cookies are Correctly Configured: Double-check the cookie configuration in your Better Auth setup. Make sure that they match what is expected in your application.

  3. Check for Updates: Ensure that you are using the latest stable version of Better Auth. You're currently on 1.4.9, which may have known issues that are resolved in later releases.

  4. Revisit Middleware Setup: Depending on the Next.js version, some middleware configurations may not support Node.js API calls directly. Ensure your setup is correct for the Next.js version you are using.

  5. Explore Open Issues: Look at other relevant issues in the Better Auth GitHub repository where similar problems may have been reported and see if there are suggested fixes.

  6. Alternative Session Retrieval: If the direct call to auth.api.getSession fails, consider if you can retrieve session information through an alternative API endpoint as a workaround.

If these suggestions do not resolve the issue, it might be beneficial to tag Beka and Max in your GitHub issue for further assistance. They can provide more definitive help regarding this specific situation.

Is there anything else specific you'd like to know or try out from here?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3693196158 --> @better-auth-agent[bot] commented on GitHub (Dec 26, 2025): It looks like you're encountering an issue where `auth.api.getSession()` is returning null even though valid session cookies are set in your Next.js App Router server components. This seems to be a known issue with the Better Auth framework, particularly with the cookie caching mechanism under certain configurations. Here are some steps you can take to troubleshoot this issue: 1. **Disable Cookie Cache**: As you mentioned, disabling the `cookieCache` setting allows `auth.api.getSession()` to work, although it hits the database every time. This suggests that the cookie handling might specifically have issues. 2. **Ensure Cookies are Correctly Configured**: Double-check the cookie configuration in your Better Auth setup. Make sure that they match what is expected in your application. 3. **Check for Updates**: Ensure that you are using the latest stable version of Better Auth. You're currently on 1.4.9, which may have known issues that are resolved in later releases. 4. **Revisit Middleware Setup**: Depending on the Next.js version, some middleware configurations may not support Node.js API calls directly. Ensure your setup is correct for the Next.js version you are using. 5. **Explore Open Issues**: Look at other relevant issues in the Better Auth GitHub repository where similar problems may have been reported and see if there are suggested fixes. 6. **Alternative Session Retrieval**: If the direct call to `auth.api.getSession` fails, consider if you can retrieve session information through an alternative API endpoint as a workaround. If these suggestions do not resolve the issue, it might be beneficial to tag Beka and Max in your GitHub issue for further assistance. They can provide more definitive help regarding this specific situation. Is there anything else specific you'd like to know or try out from here? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@dosubot[bot] commented on GitHub (Dec 26, 2025):

Hi @Ben52! I'm Dosu and I’m helping the better-auth team.

This is a known integration issue with Better Auth in Next.js server components when cookie cache is enabled. In recent versions (>=1.4.x), the default cookie cache strategy changed from legacy base64-hmac to encrypted JWE/JWT. If your cookies are still using the legacy format, you need to explicitly set session.cookieCache.strategy: 'base64-hmac' in your config to restore the previous behavior source.

Also, in Next.js 16, you must call cookies() or headers() outside of any cached function and pass the cookie header as an argument to your session retrieval logic. Calling them inside cached functions can cause getSession to return null source.

If you want to bypass the cookie cache and force a DB lookup, use auth.api.getSession({ headers, query: { disableCookieCache: true } }) source.

For cross-origin setups or remote auth servers, proxy session requests through a Next.js API route or server action that forwards the cookie header source.

If you still see null, check that your cookie settings (sameSite, secure, path, domain) match your environment, and ensure your secret is correct. Debug logging and inspecting actual cookie values can help pinpoint validation issues source.

Let me know if you need a migration snippet or want to troubleshoot cookie format/version issues further.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3693202207 --> @dosubot[bot] commented on GitHub (Dec 26, 2025): <!-- Greeting --> Hi @Ben52! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known integration issue with Better Auth in Next.js server components when cookie cache is enabled. In recent versions (>=1.4.x), the default cookie cache strategy changed from legacy base64-hmac to encrypted JWE/JWT. If your cookies are still using the legacy format, you need to explicitly set `session.cookieCache.strategy: 'base64-hmac'` in your config to restore the previous behavior [source](https://github.com/better-auth/better-auth/pull/5510). Also, in Next.js 16, you must call `cookies()` or `headers()` outside of any cached function and pass the cookie header as an argument to your session retrieval logic. Calling them inside cached functions can cause `getSession` to return null [source](https://github.com/better-auth/better-auth/issues/5584). If you want to bypass the cookie cache and force a DB lookup, use `auth.api.getSession({ headers, query: { disableCookieCache: true } })` [source](https://github.com/better-auth/better-auth/discussions/4231). For cross-origin setups or remote auth servers, proxy session requests through a Next.js API route or server action that forwards the cookie header [source](https://github.com/better-auth/better-auth/issues/4188). If you still see null, check that your cookie settings (`sameSite`, `secure`, `path`, `domain`) match your environment, and ensure your secret is correct. Debug logging and inspecting actual cookie values can help pinpoint validation issues [source](https://github.com/better-auth/better-auth/issues/6672). Let me know if you need a migration snippet or want to troubleshoot cookie format/version issues further. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/cba5b19d-36fb-45f5-bb1d-266477d22989?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/cba5b19d-36fb-45f5-bb1d-266477d22989?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/cba5b19d-36fb-45f5-bb1d-266477d22989?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/cba5b19d-36fb-45f5-bb1d-266477d22989?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/cba5b19d-36fb-45f5-bb1d-266477d22989?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/cba5b19d-36fb-45f5-bb1d-266477d22989?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/cba5b19d-36fb-45f5-bb1d-266477d22989?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20a%20known%20integration%20issue%20with%20Better%20Auth%20in%20Next.js%20server%20components%20when%20cookie%20cache%20is%20enabled.%20In%20recent%20versions%20%28%3E%3D1.4.x%29%2C%20the%20default%20cookie%20cache%20strategy%20changed%20from%20legacy%20base64-hmac%20to%20encrypted%20JWE/JWT.%20If%20your%20cookies%20are%20still%20using%20the%20legacy%20format%2C%20you%20need%20to%20explicitly%20set%20%60session.cookieCache.strategy%3A%20%27base64-hmac%27%60%20in%20your%20config%20to%20restore%20the%20previous%20behavior%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/pull/5510%29.%0A%0AAlso%2C%20in%20Next.js%2016%2C%20you%20must%20call%20%60cookies%28%29%60%20or%20%60headers%28%29%60%20outside%20of%20any%20cached%20function%20and%20pass%20the%20cookie%20header%20as%20an%20argument%20to%20your%20session%20retrieval%20logic.%20Calling%20them%20inside%20cached%20functions%20can%20cause%20%60getSession%60%20to%20return%20null%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/5584%29.%0A%0AIf%20you%20want%20to%20bypass%20the%20cookie%20cache%20and%20force%20a%20DB%20lookup%2C%20use%20%60auth.api.getSession%28%7B%20headers%2C%20query%3A%20%7B%20disableCookieCache%3A%20true%20%7D%20%7D%29%60%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/discussions/4231%29.%0A%0AFor%20cross-origin%20setups%20or%20remote%20auth%20servers%2C%20proxy%20session%20requests%20through%20a%20Next.js%20API%20route%20or%20server%20action%20that%20forwards%20the%20cookie%20header%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/4188%29.%0A%0AIf%20you%20still%20see%20null%2C%20check%20that%20your%20cookie%20settings%20%28%60sameSite%60%2C%20%60secure%60%2C%20%60path%60%2C%20%60domain%60%29%20match%20your%20environment%2C%20and%20ensure%20your%20secret%20is%20correct.%20Debug%20logging%20and%20inspecting%20actual%20cookie%20values%20can%20help%20pinpoint%20validation%20issues%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/6672%29.%0A%0ALet%20me%20know%20if%20you%20need%20a%20migration%20snippet%20or%20want%20to%20troubleshoot%20cookie%20format/version%20issues%20further.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/7008)
Author
Owner

@bytaesu commented on GitHub (Dec 26, 2025):

Hi @Ben52, I'm looking into this

<!-- gh-comment-id:3693350657 --> @bytaesu commented on GitHub (Dec 26, 2025): Hi @Ben52, I'm looking into this
Author
Owner

@bytaesu commented on GitHub (Dec 26, 2025):

Hi @Ben52,

I tested it, but couldn't reproduce the issue 🤔 Is it still happening for you?


https://github.com/user-attachments/assets/c37058bd-6c25-4c34-bc5e-8758329a05c1


import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";

const getSession = async () => {
	const headersList = await headers();
	console.log("cookie header:", headersList.get("cookie"));

	const result = await auth.api.getSession({ headers: headersList });
	console.log("result:", result);

	return result;
};

const Page = async () => {
	const session = await getSession();
	if (!session) redirect("/login");

	return <div>Welcome {session.user.email}</div>;
};

export default Page;

import { stripe } from "@better-auth/stripe";
import { neon } from "@neondatabase/serverless";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { betterAuth } from "better-auth/minimal";
import { openAPI } from "better-auth/plugins";
import { drizzle } from "drizzle-orm/neon-http";
import Stripe from "stripe";
import * as authSchema from "./auth-schema";

const sql = neon(process.env.DATABASE_URL ?? "");
const db = drizzle({ client: sql });

const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY ?? "");

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema: authSchema,
  }),
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID || "",
      clientSecret: process.env.GITHUB_CLIENT_SECRET || "",
    },
  },
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60,
    },
  },
  advanced: {
    database: {
      generateId: "uuid",
    },
  },
  logger: {
    level: "debug",
  },

  // ... other configs
});
<!-- gh-comment-id:3693376525 --> @bytaesu commented on GitHub (Dec 26, 2025): Hi @Ben52, I tested it, but couldn't reproduce the issue 🤔 Is it still happening for you? --- https://github.com/user-attachments/assets/c37058bd-6c25-4c34-bc5e-8758329a05c1 --- ```ts import { headers } from "next/headers"; import { redirect } from "next/navigation"; import { auth } from "@/lib/auth"; const getSession = async () => { const headersList = await headers(); console.log("cookie header:", headersList.get("cookie")); const result = await auth.api.getSession({ headers: headersList }); console.log("result:", result); return result; }; const Page = async () => { const session = await getSession(); if (!session) redirect("/login"); return <div>Welcome {session.user.email}</div>; }; export default Page; ``` --- ```ts import { stripe } from "@better-auth/stripe"; import { neon } from "@neondatabase/serverless"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { betterAuth } from "better-auth/minimal"; import { openAPI } from "better-auth/plugins"; import { drizzle } from "drizzle-orm/neon-http"; import Stripe from "stripe"; import * as authSchema from "./auth-schema"; const sql = neon(process.env.DATABASE_URL ?? ""); const db = drizzle({ client: sql }); const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY ?? ""); export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema: authSchema, }), socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID || "", clientSecret: process.env.GITHUB_CLIENT_SECRET || "", }, }, session: { cookieCache: { enabled: true, maxAge: 5 * 60, }, }, advanced: { database: { generateId: "uuid", }, }, logger: { level: "debug", }, // ... other configs }); ```
Author
Owner

@Ben52 commented on GitHub (Dec 28, 2025):

using the strategy: 'jwt', fixes it for me...

<!-- gh-comment-id:3694912624 --> @Ben52 commented on GitHub (Dec 28, 2025): using the `strategy: 'jwt',` fixes it for me...
Author
Owner

@bytaesu commented on GitHub (Dec 28, 2025):

using the strategy: 'jwt', fixes it for me...

I just left a comment by mistake thinking it was a different issue 😅

I haven't seen this issue even without additional configuration..🤔

<!-- gh-comment-id:3694918068 --> @bytaesu commented on GitHub (Dec 28, 2025): > using the `strategy: 'jwt',` fixes it for me... I just left a comment by mistake thinking it was a different issue 😅 I haven't seen this issue even without additional configuration..🤔
Author
Owner

@bytaesu commented on GitHub (Dec 28, 2025):

Could you share a minimal reproducible public repo with me?

<!-- gh-comment-id:3694918599 --> @bytaesu commented on GitHub (Dec 28, 2025): Could you share a minimal reproducible public repo with me?
Author
Owner

@Ben52 commented on GitHub (Dec 28, 2025):

The repro isn't reproducing the issue... For whatever reason, in my app its not working properly unless I set the strategy to jwt.

<!-- gh-comment-id:3694945872 --> @Ben52 commented on GitHub (Dec 28, 2025): The repro isn't reproducing the issue... For whatever reason, in my app its not working properly unless I set the strategy to `jwt`.
Author
Owner

@mitesh9991 commented on GitHub (Dec 30, 2025):

@bytaesu Try deploying to Vercel and then test it. It works fine locally but not in production.

<!-- gh-comment-id:3700033249 --> @mitesh9991 commented on GitHub (Dec 30, 2025): @bytaesu Try deploying to Vercel and then test it. It works fine locally but not in production.
Author
Owner

@bytaesu commented on GitHub (Dec 30, 2025):

Hi @mitesh9991,

Could you share a reproducible repo for this issue? I'm not able to reproduce it.

<!-- gh-comment-id:3700122095 --> @bytaesu commented on GitHub (Dec 30, 2025): Hi @mitesh9991, Could you share a reproducible repo for this issue? I'm not able to reproduce it. - https://temp-better-auth-server-side.vercel.app - https://github.com/bytaesu/temp-better-auth-server-side
Author
Owner

@raychunghk commented on GitHub (Jan 8, 2026):

I also have the same issue. I am sorry I am too busy and please allow me to post my bug report draft by AI

Hello better-auth team,

I'm experiencing an issue where the server-side session becomes null approximately 5 minutes after a successful login, despite having a much longer session.expiresIn and cookieCache.maxAge configured. This is causing users to be logged out unexpectedly.

The Problem
After a user logs in, auth.api.getSession() correctly returns a session object. However, after about 5 minutes, calls to getSession() start returning null, even though the JWT session token itself has not expired.

I've added detailed logging in my Next.js middleware to track the exact moment this happens. The logs confirm that the session becomes invalid after around 300 seconds (5 minutes).

Log Output
Here is the log from my middleware showing the time of login and the time the session first became null. The difference is consistently around 5 minutes.

🔀[Middleware][1/8/2026, 11:33:11 AM] 🔴 BetterAuth session is NULL { "loginSuccessAt": "2026-01-08T03:28:06.393Z", "firstSessionNullAt": "2026-01-08T03:33:11.260Z", "timeSinceLoginToFirstNullMs": 304867 }
My Configuration
My setup uses the credentials provider to authenticate against a separate Nest.js backend, which returns a JWT. This JWT and user data are then used to create the better-auth session.

Here is my auth.ts configuration file:

`// /app/lib/auth.ts
import { config } from '@tcon360/config';
import { betterAuth } from 'better-auth';
import { credentials } from 'better-auth-credentials-plugin';
import { customSession, username } from 'better-auth/plugins';
import z from 'zod/v3';
import { customSignInResponsePlugin } from './plugin/customSignInResponsePlugin';

export const myCustomSchema = z.object({
username: z.string().min(1),
password: z.string().min(1),
email: z.string().min(1),
});

const _tokenExpiration = Number(process.env.TOKEN_MAX_AGE) || 30 * 24 * 60 * 60; // 30 days

export const auth = betterAuth({
database: null,
advanced: {
disableCSRFCheck: true,
},
secret: process.env.JWT_SECRET!,
user: {
additionalFields: {
role: { type: "string" },
staff: { type: "json" },
tkn: { type: "string" },
nestJwt: { type: "string" },
username: { type: "string" },
tokenMaxAge: { type: "number" },
},
},
emailAndPassword: {
enabled: false,
},
session: {
strategy: 'jwt',
expiresIn: _tokenExpiration, // 30 days
cookieCache: {
enabled: true,
maxAge: 60 * 60 // Set to 1 hour (3600 seconds)
}
},
cookies: {
sessionToken: {
name: 'session_token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
},
},
},
plugins: [
username({ minUsernameLength: 5 }),
customSignInResponsePlugin(), // Custom plugin to shape sign-in response
credentials({
autoSignUp: true,
path: "/sign-in/credentials",
inputSchema: myCustomSchema,
async callback(ctx, parsed) {
const { email, username, password } = parsed;
const identifier = email || username;

            if (!identifier || !password) return null;

            try {
                const backendLoginUrl = `http://localhost:${config.backendport}/api/user/login`;
                const nestResponse = await fetch(backendLoginUrl, {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({ identifier, password, excludeViewStaff: true }),
                });

                if (!nestResponse.ok) return null;

                const nestData = await nestResponse.json();
                const nestJwt = nestData.accessToken;
                if (!nestJwt) return null;

                const userFromNest = nestData.user;
                const tokenMaxAge = nestData.tokenMaxAge;

                return {
                    id: userFromNest.id,
                    email: userFromNest.email,
                    name: userFromNest.name,
                    image: userFromNest.image || null,
                    staff: userFromNest.staff || [],
                    tokenMaxAge,
                    nestJwt,
                };
            } catch (error) {
                console.error("[Credentials Callback] Error:", error);
                return null;
            }
        },
    }),
    customSession(async (session) => {
        return { ...session };
    }),
],
basePath: '/api/bauth',
debug: true,

});
`
Hypothesis
The session becomes null after exactly 5 minutes, which seems too specific to be a coincidence. It feels like there might be a hardcoded or default cache expiration of 300 seconds somewhere within better-auth's session management or cookieCache implementation that is overriding the maxAge value I have configured in auth.ts.

Thank you for looking into this. Let me know if you need any more information.

<!-- gh-comment-id:3721766646 --> @raychunghk commented on GitHub (Jan 8, 2026): I also have the same issue. I am sorry I am too busy and please allow me to post my bug report draft by AI Hello better-auth team, I'm experiencing an issue where the server-side session becomes null approximately 5 minutes after a successful login, despite having a much longer session.expiresIn and cookieCache.maxAge configured. This is causing users to be logged out unexpectedly. The Problem After a user logs in, auth.api.getSession() correctly returns a session object. However, after about 5 minutes, calls to getSession() start returning null, even though the JWT session token itself has not expired. I've added detailed logging in my Next.js middleware to track the exact moment this happens. The logs confirm that the session becomes invalid after around 300 seconds (5 minutes). Log Output Here is the log from my middleware showing the time of login and the time the session first became null. The difference is consistently around 5 minutes. `🔀[Middleware][1/8/2026, 11:33:11 AM] 🔴 BetterAuth session is NULL { "loginSuccessAt": "2026-01-08T03:28:06.393Z", "firstSessionNullAt": "2026-01-08T03:33:11.260Z", "timeSinceLoginToFirstNullMs": 304867 } ` My Configuration My setup uses the credentials provider to authenticate against a separate Nest.js backend, which returns a JWT. This JWT and user data are then used to create the better-auth session. Here is my [auth.ts](code-assist-path:/config/workspace/vm/js/TCon360/frontend/app/lib/auth.ts) configuration file: `// /app/lib/auth.ts import { config } from '@tcon360/config'; import { betterAuth } from 'better-auth'; import { credentials } from 'better-auth-credentials-plugin'; import { customSession, username } from 'better-auth/plugins'; import z from 'zod/v3'; import { customSignInResponsePlugin } from './plugin/customSignInResponsePlugin'; export const myCustomSchema = z.object({ username: z.string().min(1), password: z.string().min(1), email: z.string().min(1), }); const _tokenExpiration = Number(process.env.TOKEN_MAX_AGE) || 30 * 24 * 60 * 60; // 30 days export const auth = betterAuth({ database: null, advanced: { disableCSRFCheck: true, }, secret: process.env.JWT_SECRET!, user: { additionalFields: { role: { type: "string" }, staff: { type: "json" }, tkn: { type: "string" }, nestJwt: { type: "string" }, username: { type: "string" }, tokenMaxAge: { type: "number" }, }, }, emailAndPassword: { enabled: false, }, session: { strategy: 'jwt', expiresIn: _tokenExpiration, // 30 days cookieCache: { enabled: true, maxAge: 60 * 60 // Set to 1 hour (3600 seconds) } }, cookies: { sessionToken: { name: 'session_token', options: { httpOnly: true, sameSite: 'lax', path: '/', secure: process.env.NODE_ENV === 'production', }, }, }, plugins: [ username({ minUsernameLength: 5 }), customSignInResponsePlugin(), // Custom plugin to shape sign-in response credentials({ autoSignUp: true, path: "/sign-in/credentials", inputSchema: myCustomSchema, async callback(ctx, parsed) { const { email, username, password } = parsed; const identifier = email || username; if (!identifier || !password) return null; try { const backendLoginUrl = `http://localhost:${config.backendport}/api/user/login`; const nestResponse = await fetch(backendLoginUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ identifier, password, excludeViewStaff: true }), }); if (!nestResponse.ok) return null; const nestData = await nestResponse.json(); const nestJwt = nestData.accessToken; if (!nestJwt) return null; const userFromNest = nestData.user; const tokenMaxAge = nestData.tokenMaxAge; return { id: userFromNest.id, email: userFromNest.email, name: userFromNest.name, image: userFromNest.image || null, staff: userFromNest.staff || [], tokenMaxAge, nestJwt, }; } catch (error) { console.error("[Credentials Callback] Error:", error); return null; } }, }), customSession(async (session) => { return { ...session }; }), ], basePath: '/api/bauth', debug: true, }); ` Hypothesis The session becomes null after exactly 5 minutes, which seems too specific to be a coincidence. It feels like there might be a hardcoded or default cache expiration of 300 seconds somewhere within better-auth's session management or cookieCache implementation that is overriding the maxAge value I have configured in [auth.ts](code-assist-path:/config/workspace/vm/js/TCon360/frontend/app/lib/auth.ts). Thank you for looking into this. Let me know if you need any more information.
Author
Owner

@PatrickDai commented on GitHub (Feb 11, 2026):

👋 @bytaesu
I’m experiencing a similar issue with better-auth@v1.4.18 when using TanStack Start.

Minimal configuration

export const auth = betterAuth({
	database: drizzleAdapter(db, {
		provider: "pg",
		schema,
	}),
	socialProviders: {
		github: {
			clientId: process.env.GITHUB_CLIENT_ID || "",
			clientSecret: process.env.GITHUB_CLIENT_SECRET || "",
		},
	},
	session: {
		cookieCache: {
			enabled: true,
			maxAge: 60 * 5, // 5 minutes
			strategy: "jwt",
		},
	},
});

Observed behavior

When { "session.cookieCache.enabled" : true }

Only the session_data cookie is created
The session_token cookie is missing

As a result, session remains null

When I disable cookieCache, both session_token and session_data cookies are created as expected, and the session works correctly.

Reproduction repository

https://github.com/PatrickDai/better-auth-tanstack-testing

<!-- gh-comment-id:3883529393 --> @PatrickDai commented on GitHub (Feb 11, 2026): 👋 @bytaesu I’m experiencing a similar issue with better-auth@v1.4.18 when using TanStack Start. ### Minimal configuration ```js export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema, }), socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID || "", clientSecret: process.env.GITHUB_CLIENT_SECRET || "", }, }, session: { cookieCache: { enabled: true, maxAge: 60 * 5, // 5 minutes strategy: "jwt", }, }, }); ``` ### Observed behavior When `{ "session.cookieCache.enabled" : true }` Only the `session_data` cookie is created The `session_token` cookie is missing As a result, session remains null When I disable cookieCache, both session_token and session_data cookies are created as expected, and the session works correctly. ### Reproduction repository https://github.com/PatrickDai/better-auth-tanstack-testing
Author
Owner

@itajenglish commented on GitHub (Feb 12, 2026):

+1

<!-- gh-comment-id:3888527755 --> @itajenglish commented on GitHub (Feb 12, 2026): +1
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10701