[GH-ISSUE #8156] Using secondaryStorage gives TypeError: expiresAt.getTime is not a function when calling auth.api.updateUser() #28330

Closed
opened 2026-04-17 19:45:24 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @hammer-ai on GitHub (Feb 26, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8156

Originally assigned to: @himself65 on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

I have the following setup:

apps/web/src/lib/auth.ts

import {betterAuth, BetterAuthOptions} from 'better-auth/minimal'
import {prismaAdapter} from 'better-auth/adapters/prisma'
import {customSession} from 'better-auth/plugins'
import {prisma} from 'database/src/client'
import {AUTH_COOKIE_PREFIX, DESKTOP_APP_CLIENT_ID, DESKTOP_APP_PROTOCOL, getURL, MAX_PASSWORD_CHARS, MAX_USERNAME_CHARS, MIN_PASSWORD_CHARS, MIN_USERNAME_CHARS} from 'utils'
import {nextCookies} from 'better-auth/next-js'
import {electron} from '@better-auth/electron'
import {waitUntil} from '@vercel/functions'
import {kv} from '@vercel/kv'

const options = {
  appName: APP_NAME,
  database: prismaAdapter(prisma, {
    provider: 'postgresql',
    usePlural: true
  }),
  baseURL: BASE_URL,
  secondaryStorage: {
    get: async (key) => await kv.get(`better-auth:${key}`),
    set: async (key, value, ttl) => {
      if (ttl) {
        await kv.set(`better-auth:${key}`, value, {ex: ttl})
      } else {
        await kv.set(`better-auth:${key}`, value)
      }
    },
    delete: async (key) => {
      await kv.del(`better-auth:${key}`)
    }
  },
  secret: process.env.BETTER_AUTH_SECRET!,
  trustedOrigins: [
    'http://localhost:3000',
    'https://my-app.com',
  ],
  socialProviders: {
    google: {
      prompt: 'select_account',
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!
    }
  },
  emailAndPassword: {
    enabled: true,
  },
  advanced: {
    cookiePrefix: AUTH_COOKIE_PREFIX,
    useSecureCookies: process.env.NODE_ENV === 'production',
    backgroundTasks: {
      handler: (promise) => waitUntil(promise)
    }
  },
  account: {
    modelName: 'account',
    encryptOAuthTokens: true,
    accountLinking: {
      enabled: true,
      trustedProviders: [
        'email-password',
        'google',
        'discord'
      ]
    }
  },
  user: {
    modelName: 'user',
    additionalFields: {
      bio: {
        type: 'string',
        required: false
      },
      backgroundImage: {
        type: 'string',
        required: false
      },
      status: {
        type: 'string',
        required: false
      }
    },
    deleteUser: {
      enabled: true
    }
  },
  session: {
    modelName: 'session',
    expiresIn: 30 * 24 * 60 * 60, // 30 days
    // https://www.better-auth.com/docs/guides/optimizing-for-performance#cookie-cache
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60 // Cache duration in seconds - 5 minutes
    }
  },
  verification: {
    modelName: 'verification'
  },
  rateLimit: {
    enabled: true,
    window: 60,
    max: 100,
    customRules: {
      '/sign-in/email': {window: 60, max: 5},
      '/sign-up/email': {window: 60, max: 5},
      '/forget-password': {window: 60, max: 5}
    }
  },
  ...other code,
  plugins: [
    ...other code,
  ]
} satisfies BetterAuthOptions

export const auth = betterAuth({
  ...options,
  plugins: [
    ...(options.plugins ?? []),
    customSession(({user, session}) => Promise.resolve({
      user: {
        ...user,
        ...other code
      },
      session
    }), options),
    nextCookies() // make sure this is the last plugin in the array
  ]
})

and

apps/web/src/lib/auth-client.ts
import {customSessionClient, inferAdditionalFields, usernameClient} from 'better-auth/client/plugins'
import {createAuthClient} from 'better-auth/react'
import type {auth} from './auth'
import {AUTH_COOKIE_PREFIX, DESKTOP_APP_CLIENT_ID, DESKTOP_APP_PROTOCOL, getURL} from 'utils'
import {electronProxyClient} from '@better-auth/electron/proxy'

export const authClient = createAuthClient({
  baseURL: getURL(),
  plugins: [
    electronProxyClient({
      clientID: DESKTOP_APP_CLIENT_ID,
      cookiePrefix: AUTH_COOKIE_PREFIX,
      protocol: {
        scheme: DESKTOP_APP_PROTOCOL
      }
    }),
    usernameClient(),
    customSessionClient<typeof auth>(),
    inferAdditionalFields<typeof auth>()
  ],
  fetchOptions: {
    onError(e) {
      if (e.error.status === 429) {
        toastIt({text: 'Too many requests. Please try again later.', type: ToastType.Error})
      }
    }
  }
})

Current vs. Expected behavior

When I have secondaryStorage enabled (i.e. not commented out), when I update the user on the server with:

await auth.api.updateUser({
      body: {
        status: 'pending',
      },
      headers: await headers()
    })

I get:

TypeError: expiresAt.getTime is not a function
    at async POST (src/app/api/user/update-user-profile/route.ts:63:5)
  61 |     // Use auth.api.updateUser to update user and session together.
  62 |     // This ensures the session reflects the updated values immediately.
> 63 |     await auth.api.updateUser({
     |     ^
  64 |       body: {
  65 |         status: 'pending',

What version of Better Auth are you using?

1.5.0-beta.19

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin",
    "release": "25.3.0",
    "cpuCount": 12,
    "cpuModel": "Apple M2 Pro",
    "totalMemory": "32.00 GB",
    "freeMemory": "3.64 GB"
  },
  "node": {
    "version": "v21.7.3",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.5.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^15.5.9"
    },
    {
      "name": "react",
      "version": "^19.2.1"
    }
  ],
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null,
    "error": "Missing API key. Pass it to the constructor `new Resend(\"re_123\")`"
  }
}

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

Backend

Originally created by @hammer-ai on GitHub (Feb 26, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8156 Originally assigned to: @himself65 on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce I have the following setup: ``` apps/web/src/lib/auth.ts import {betterAuth, BetterAuthOptions} from 'better-auth/minimal' import {prismaAdapter} from 'better-auth/adapters/prisma' import {customSession} from 'better-auth/plugins' import {prisma} from 'database/src/client' import {AUTH_COOKIE_PREFIX, DESKTOP_APP_CLIENT_ID, DESKTOP_APP_PROTOCOL, getURL, MAX_PASSWORD_CHARS, MAX_USERNAME_CHARS, MIN_PASSWORD_CHARS, MIN_USERNAME_CHARS} from 'utils' import {nextCookies} from 'better-auth/next-js' import {electron} from '@better-auth/electron' import {waitUntil} from '@vercel/functions' import {kv} from '@vercel/kv' const options = { appName: APP_NAME, database: prismaAdapter(prisma, { provider: 'postgresql', usePlural: true }), baseURL: BASE_URL, secondaryStorage: { get: async (key) => await kv.get(`better-auth:${key}`), set: async (key, value, ttl) => { if (ttl) { await kv.set(`better-auth:${key}`, value, {ex: ttl}) } else { await kv.set(`better-auth:${key}`, value) } }, delete: async (key) => { await kv.del(`better-auth:${key}`) } }, secret: process.env.BETTER_AUTH_SECRET!, trustedOrigins: [ 'http://localhost:3000', 'https://my-app.com', ], socialProviders: { google: { prompt: 'select_account', clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! } }, emailAndPassword: { enabled: true, }, advanced: { cookiePrefix: AUTH_COOKIE_PREFIX, useSecureCookies: process.env.NODE_ENV === 'production', backgroundTasks: { handler: (promise) => waitUntil(promise) } }, account: { modelName: 'account', encryptOAuthTokens: true, accountLinking: { enabled: true, trustedProviders: [ 'email-password', 'google', 'discord' ] } }, user: { modelName: 'user', additionalFields: { bio: { type: 'string', required: false }, backgroundImage: { type: 'string', required: false }, status: { type: 'string', required: false } }, deleteUser: { enabled: true } }, session: { modelName: 'session', expiresIn: 30 * 24 * 60 * 60, // 30 days // https://www.better-auth.com/docs/guides/optimizing-for-performance#cookie-cache cookieCache: { enabled: true, maxAge: 5 * 60 // Cache duration in seconds - 5 minutes } }, verification: { modelName: 'verification' }, rateLimit: { enabled: true, window: 60, max: 100, customRules: { '/sign-in/email': {window: 60, max: 5}, '/sign-up/email': {window: 60, max: 5}, '/forget-password': {window: 60, max: 5} } }, ...other code, plugins: [ ...other code, ] } satisfies BetterAuthOptions export const auth = betterAuth({ ...options, plugins: [ ...(options.plugins ?? []), customSession(({user, session}) => Promise.resolve({ user: { ...user, ...other code }, session }), options), nextCookies() // make sure this is the last plugin in the array ] }) ``` and ``` apps/web/src/lib/auth-client.ts import {customSessionClient, inferAdditionalFields, usernameClient} from 'better-auth/client/plugins' import {createAuthClient} from 'better-auth/react' import type {auth} from './auth' import {AUTH_COOKIE_PREFIX, DESKTOP_APP_CLIENT_ID, DESKTOP_APP_PROTOCOL, getURL} from 'utils' import {electronProxyClient} from '@better-auth/electron/proxy' export const authClient = createAuthClient({ baseURL: getURL(), plugins: [ electronProxyClient({ clientID: DESKTOP_APP_CLIENT_ID, cookiePrefix: AUTH_COOKIE_PREFIX, protocol: { scheme: DESKTOP_APP_PROTOCOL } }), usernameClient(), customSessionClient<typeof auth>(), inferAdditionalFields<typeof auth>() ], fetchOptions: { onError(e) { if (e.error.status === 429) { toastIt({text: 'Too many requests. Please try again later.', type: ToastType.Error}) } } } }) ``` ### Current vs. Expected behavior When I have `secondaryStorage` enabled (i.e. not commented out), when I update the user on the server with: ``` await auth.api.updateUser({ body: { status: 'pending', }, headers: await headers() }) ``` I get: ``` TypeError: expiresAt.getTime is not a function at async POST (src/app/api/user/update-user-profile/route.ts:63:5) 61 | // Use auth.api.updateUser to update user and session together. 62 | // This ensures the session reflects the updated values immediately. > 63 | await auth.api.updateUser({ | ^ 64 | body: { 65 | status: 'pending', ``` ### What version of Better Auth are you using? 1.5.0-beta.19 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin", "release": "25.3.0", "cpuCount": 12, "cpuModel": "Apple M2 Pro", "totalMemory": "32.00 GB", "freeMemory": "3.64 GB" }, "node": { "version": "v21.7.3", "env": "development" }, "packageManager": { "name": "npm", "version": "10.5.0" }, "frameworks": [ { "name": "next", "version": "^15.5.9" }, { "name": "react", "version": "^19.2.1" } ], "databases": null, "betterAuth": { "version": "Unknown", "config": null, "error": "Missing API key. Pass it to the constructor `new Resend(\"re_123\")`" } } ``` ### Which area(s) are affected? (Select all that apply) Backend
GiteaMirror added the lockedbug labels 2026-04-17 19:45:24 -05:00
Author
Owner

@himself65 commented on GitHub (Mar 5, 2026):

https://github.com/better-auth/better-auth/pull/8248

<!-- gh-comment-id:4007942988 --> @himself65 commented on GitHub (Mar 5, 2026): https://github.com/better-auth/better-auth/pull/8248
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28330