[GH-ISSUE #6993] Session ID missing in KV storage when using secondaryStorage + storeSessionInDatabase #28016

Closed
opened 2026-04-17 19:21:25 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @ethan-huo on GitHub (Dec 25, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6993

Originally assigned to: @bytaesu on GitHub.

Description

When using secondaryStorage with storeSessionInDatabase: true, the session stored in KV storage lacks the id field. This causes OAuth token exchange to fail when @better-auth/oauth-provider tries to query the session by ID.

Steps to Reproduce

  1. Configure better-auth with:
secondaryStorage: {
  get: (key) => kv.get(key),
  set: (key, value, ttl) => kv.put(key, value, { expirationTtl: ttl }),
  delete: (key) => kv.delete(key),
},
session: {
  storeSessionInDatabase: true,
},
  1. Add @better-auth/oauth-provider plugin
  2. Complete OAuth authorization flow
  3. Token exchange fails with:
params: [ undefined ]
Query: select ... from "session" where "session"."id" = $1

Root Cause

In packages/better-auth/src/db/with-hooks.ts:47-57, the custom storage function (fn) is executed before the database create operation:

// Step 1: Custom function executes FIRST
const customCreated = customCreateFn
  ? await customCreateFn.fn(actualData)  // actualData has NO id yet
  : null;

// Step 2: Database create executes SECOND
const created =
  !customCreateFn || customCreateFn.executeMainFn
    ? await adapter.create({ model, data: actualData })  // id generated HERE
    : customCreated;

This means:

  1. fn(sessionData) stores session in KV without id
  2. Database create generates id, but KV is never updated
  3. getSessionFromCtx() returns KV data (cache hit) → no id
  4. oauth-provider/token.ts:694 queries WHERE id = undefinedfails

Expected Behavior

Session stored in KV should include the id field after database creation.

Suggested Fix

Option A: Update KV after DB create (recommended)

Modify internal-adapter.ts:createSession to update KV storage after database creation:

const dbSession = await createWithHooks(data, "session", {
  executeMainFn: options.session?.storeSessionInDatabase,
});

if (secondaryStorage && dbSession.id) {
  await secondaryStorage.set(
    data.token,
    JSON.stringify({ session: dbSession, user }),
    sessionTTL,
  );
}

Option B: Pre-generate ID before storage

Generate session id before calling createWithHooks:

const sessionId = ctx.generateId("session");
const data = { id: sessionId, token: generateId(32), ... };

Environment

  • better-auth: 1.4.5
  • @better-auth/oauth-provider: 1.4.5
  • Runtime: Bun on Cloudflare Workers
Originally created by @ethan-huo on GitHub (Dec 25, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6993 Originally assigned to: @bytaesu on GitHub. ## Description When using `secondaryStorage` with `storeSessionInDatabase: true`, the session stored in KV storage lacks the `id` field. This causes OAuth token exchange to fail when `@better-auth/oauth-provider` tries to query the session by ID. ## Steps to Reproduce 1. Configure better-auth with: ```typescript secondaryStorage: { get: (key) => kv.get(key), set: (key, value, ttl) => kv.put(key, value, { expirationTtl: ttl }), delete: (key) => kv.delete(key), }, session: { storeSessionInDatabase: true, }, ``` 2. Add `@better-auth/oauth-provider` plugin 3. Complete OAuth authorization flow 4. Token exchange fails with: ``` params: [ undefined ] Query: select ... from "session" where "session"."id" = $1 ``` ## Root Cause In `packages/better-auth/src/db/with-hooks.ts:47-57`, the custom storage function (`fn`) is executed **before** the database `create` operation: ```typescript // Step 1: Custom function executes FIRST const customCreated = customCreateFn ? await customCreateFn.fn(actualData) // actualData has NO id yet : null; // Step 2: Database create executes SECOND const created = !customCreateFn || customCreateFn.executeMainFn ? await adapter.create({ model, data: actualData }) // id generated HERE : customCreated; ``` This means: 1. `fn(sessionData)` stores session in KV **without** `id` 2. Database `create` generates `id`, but KV is **never updated** 3. `getSessionFromCtx()` returns KV data (cache hit) → **no `id`** 4. `oauth-provider/token.ts:694` queries `WHERE id = undefined` → **fails** ## Expected Behavior Session stored in KV should include the `id` field after database creation. ## Suggested Fix **Option A**: Update KV after DB create (recommended) Modify `internal-adapter.ts:createSession` to update KV storage after database creation: ```typescript const dbSession = await createWithHooks(data, "session", { executeMainFn: options.session?.storeSessionInDatabase, }); if (secondaryStorage && dbSession.id) { await secondaryStorage.set( data.token, JSON.stringify({ session: dbSession, user }), sessionTTL, ); } ``` **Option B**: Pre-generate ID before storage Generate session `id` before calling `createWithHooks`: ```typescript const sessionId = ctx.generateId("session"); const data = { id: sessionId, token: generateId(32), ... }; ``` ## Environment - better-auth: 1.4.5 - @better-auth/oauth-provider: 1.4.5 - Runtime: Bun on Cloudflare Workers
GiteaMirror added the lockedbug labels 2026-04-17 19:21:25 -05:00
Author
Owner

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

It looks like you've encountered an issue where the session ID is missing in KV storage when using secondaryStorage along with storeSessionInDatabase: true. This leads to OAuth token exchange failures because the session is queried by ID and cannot find it in storage.

To resolve this, the following changes may be necessary:

  1. Update KV after Database Create:
    It is recommended to modify the session creation logic to ensure that after the database session creation, the KV storage is also updated with the generated session ID. This way, the session stored in KV includes the id field.

    Here’s a potential fix:

    const dbSession = await createWithHooks(data, "session", {
        executeMainFn: options.session?.storeSessionInDatabase,
    });
    
    if (secondaryStorage && dbSession.id) {
        await secondaryStorage.set(
            data.token,
            JSON.stringify({ session: dbSession, user }),
            sessionTTL,
        );
    }
    
  2. Pre-generate ID:
    Alternatively, consider generating the session ID before invoking the create function. This approach allows the ID to be available when storing the session in KV:

    const sessionId = ctx.generateId("session");
    const data = { id: sessionId, token: generateId(32), ... };
    

These adjustments should help ensure that the session stored in KV includes an id, facilitating the OAuth token exchange process [1].

Do you need more specific guidance on implementing any of these fixes, or do you have additional questions related to Better Auth?

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

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3690967258 --> @better-auth-agent[bot] commented on GitHub (Dec 25, 2025): It looks like you've encountered an issue where the session ID is missing in KV storage when using `secondaryStorage` along with `storeSessionInDatabase: true`. This leads to OAuth token exchange failures because the session is queried by ID and cannot find it in storage. To resolve this, the following changes may be necessary: 1. **Update KV after Database Create**: It is recommended to modify the session creation logic to ensure that after the database session creation, the KV storage is also updated with the generated session ID. This way, the session stored in KV includes the `id` field. Here’s a potential fix: ```typescript const dbSession = await createWithHooks(data, "session", { executeMainFn: options.session?.storeSessionInDatabase, }); if (secondaryStorage && dbSession.id) { await secondaryStorage.set( data.token, JSON.stringify({ session: dbSession, user }), sessionTTL, ); } ``` 2. **Pre-generate ID**: Alternatively, consider generating the session ID before invoking the create function. This approach allows the ID to be available when storing the session in KV: ```typescript const sessionId = ctx.generateId("session"); const data = { id: sessionId, token: generateId(32), ... }; ``` These adjustments should help ensure that the session stored in KV includes an `id`, facilitating the OAuth token exchange process [[1]](https://github.com/better-auth/better-auth/pull/6978). Do you need more specific guidance on implementing any of these fixes, or do you have additional questions related to Better Auth? _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

@ethan-huo commented on GitHub (Dec 25, 2025):

Related: #6987 - both issues point to sync gaps between secondaryStorage and database. The underlying architecture seems to treat KV as a simple cache without proper sync mechanisms for create/update operations.

<!-- gh-comment-id:3690969962 --> @ethan-huo commented on GitHub (Dec 25, 2025): Related: #6987 - both issues point to sync gaps between secondaryStorage and database. The underlying architecture seems to treat KV as a simple cache without proper sync mechanisms for create/update operations.
Author
Owner

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

Hi @ethan-huo, I'm looking into this

<!-- gh-comment-id:3693862548 --> @bytaesu commented on GitHub (Dec 27, 2025): Hi @ethan-huo, I'm looking into this
Author
Owner

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

Hi @ethan-huo,

Could you let me know if this version fixes the issue?

npm i https://pkg.pr.new/better-auth/better-auth@6e93c9d
<!-- gh-comment-id:3693928720 --> @bytaesu commented on GitHub (Dec 27, 2025): Hi @ethan-huo, Could you let me know if this version fixes the issue? ```sh npm i https://pkg.pr.new/better-auth/better-auth@6e93c9d ```
Author
Owner

@ethan-huo commented on GitHub (Dec 28, 2025):

Hi @bytaesu,

I tested the fix with:

bun add https://pkg.pr.new/better-auth/better-auth@6e93c9d

Unfortunately, it's still not working. I'm getting this error:

2025-12-28T09:15:03.553Z ERROR [Better Auth]: Error Error: No request state found. Please make sure you are calling this function within a `runWithRequestState` callback.

This happens when secondaryStorage is enabled with storeSessionInDatabase: true.

My config:

secondaryStorage: {
  get: (key) => env.KV.get(key),
  set: (key, value, ttl) => env.KV.put(key, value, { expirationTtl: ttl }),
  delete: (key) => env.KV.delete(key),
},

Environment:

  • Runtime: Bun on Cloudflare Workers
  • better-auth: PR preview @6e93c9d
  • @better-auth/oauth-provider: 1.4.9
<!-- gh-comment-id:3694594759 --> @ethan-huo commented on GitHub (Dec 28, 2025): Hi @bytaesu, I tested the fix with: ```shell bun add https://pkg.pr.new/better-auth/better-auth@6e93c9d ``` Unfortunately, it's still not working. I'm getting this error: ``` 2025-12-28T09:15:03.553Z ERROR [Better Auth]: Error Error: No request state found. Please make sure you are calling this function within a `runWithRequestState` callback. ``` This happens when secondaryStorage is enabled with storeSessionInDatabase: true. My config: ```ts secondaryStorage: { get: (key) => env.KV.get(key), set: (key, value, ttl) => env.KV.put(key, value, { expirationTtl: ttl }), delete: (key) => env.KV.delete(key), }, ``` Environment: - Runtime: Bun on Cloudflare Workers - better-auth: PR preview @6e93c9d - @better-auth/oauth-provider: 1.4.9
Author
Owner

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

Hello @ethan-huo,

This error doesn’t seem related to that issue. It looks related to node:async_hooks 🤔
Have you added the Node.js compatibility flag in Wrangler?

https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag

Image
<!-- gh-comment-id:3695422449 --> @bytaesu commented on GitHub (Dec 29, 2025): Hello @ethan-huo, This error doesn’t seem related to that issue. It looks related to `node:async_hooks` 🤔 Have you added the Node.js compatibility flag in Wrangler? https://developers.cloudflare.com/workers/configuration/compatibility-flags/#nodejs-compatibility-flag <img width="708" height="423" alt="Image" src="https://github.com/user-attachments/assets/2a84b00e-f750-4f61-a74e-d1333326319d" />
Author
Owner

@ethan-huo commented on GitHub (Dec 29, 2025):

That's right, this is another issue. My tech stack is tanstack-start + vite cf plugin, and I don't know why switching to the PR dependency causes this problem.

This is my configuration

{	
	"compatibility_date": "2025-11-05",
	"compatibility_flags": ["nodejs_compat"],
}
<!-- gh-comment-id:3696386717 --> @ethan-huo commented on GitHub (Dec 29, 2025): That's right, this is another issue. My tech stack is tanstack-start + vite cf plugin, and I don't know why switching to the PR dependency causes this problem. This is my configuration ```json { "compatibility_date": "2025-11-05", "compatibility_flags": ["nodejs_compat"], } ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28016