Skip to content

KV as secondaryStorage puts an eventually consistent store ahead of the database in session lookup, delaying revocation #61

Description

@sns45

Summary

The documented configuration wires kv: env.KV, which becomes Better Auth's secondaryStorage. Better Auth's findSession reads secondary storage before the database and returns on a hit. Because Workers KV is eventually consistent, that means a signed out session can keep authenticating from another colo for up to 60 seconds.

The README warns about KV's 60 second minimum TTL breaking rate limiting. The same 60 second property applied to session reads has a more serious consequence, and I could not find it documented.

Why the ordering matters

From better-auth/dist/db/internal-adapter.mjs:

if (secondaryStorage) {
  const sessionStringified = await secondaryStorage.get(token);
  if (!sessionStringified &&
      (!options.session?.storeSessionInDatabase ||
       ctx.options.session?.preserveSessionInDatabase)) {
    return null;
  }
  if (sessionStringified) {
    // ... returns the session, database never consulted
  }
}

Secondary storage short circuits the database on a hit. So the freshest store cannot correct the stale one; the stale one wins.

The KV side

Cloudflare documents KV as eventually consistent, with changes taking up to 60 seconds to propagate globally, and reads additionally served from a per colo edge cache whose default TTL is also 60 seconds. Their storage guidance positions KV for read heavy data that does not need immediate consistency.

Session revocation is the opposite of that: it is precisely a case where a delete must be visible immediately.

Sequence

  1. User signs out in colo A. Better Auth deletes the session row and the KV key.
  2. Colo B has the value cached, or has not received propagation.
  3. A request arrives at colo B. findSession reads KV, gets a hit, returns the session.
  4. The request authenticates against a session the user revoked.

Window: up to roughly 60 seconds, per request colo.

Suggestion

Two options, either would help:

  1. Document it, alongside the existing rate limiting warning. Something like: KV secondary storage is not suitable when immediate session revocation is required, because it is consulted before the database.
  2. Let kv opt out of session storage while still backing rate limits and verification tokens, so the useful parts of the integration do not force the session read path through an eventually consistent store.

I do not think there is a cacheTtl setting that fixes this, since the floor is 60 seconds either way, but I would be glad to be wrong.

Context

I hit this while moving a gateway off exactly this topology (KV first, database on a miss) after a review found the revocation window. The project now keeps sessions in D1 with no eventually consistent store in the read path, and pins the two settings that would reopen it, secondaryStorage and session.cookieCache, with regression tests.

Thanks for the package; the D1 and Hyperdrive wiring saved me real time even though I ended up not using the KV part.

Versions

  • better-auth-cloudflare 0.3.1
  • better-auth 1.7.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions